From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f46.google.com (mail-ej1-f46.google.com [209.85.218.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1A0A4242D94; Tue, 12 Aug 2025 12:56:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003400; cv=none; b=ee5DaVy/s5CCyn1XLB38FmpMgoo0V+Vjd2DtdMAwsqzcS7sHTBDACd0KElMsHJyz1d/CGdpi3gGgPsSSvntterFJhj8TKKDArkx9NTpgey9CfwR7pzhcd0w/L1/49+gbPhYwVsa53BjWYbrF2jQuiIK38lPfDY9tWb+Ewsqn/ws= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003400; c=relaxed/simple; bh=o+xSp3EubIpQffb3y4D6KiwESyZOriGzen9hJgFzCg8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=etNM1jfIciCudZ6uXYRyv4tbwZud26J2XI80oYKhmS9zUlpw1HdOFZKqo/KzVugofem7dCqKEaY2g/iT+Ocu9StzzVdh1bHmt9w7CKi9ZGZWJFM1W9UI2uHz6QKo43nyWRGfyYj8ii8CbssAhxV7lrL1Y21uOPMbjybLMln+DYY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=c7nsFKCt; arc=none smtp.client-ip=209.85.218.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="c7nsFKCt" Received: by mail-ej1-f46.google.com with SMTP id a640c23a62f3a-af96d097df5so987324066b.3; Tue, 12 Aug 2025 05:56:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003396; x=1755608196; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=oPJ0pN/RvHB5WLTR3vby3H9Qi10hzmuI98P3X9tF0H4=; b=c7nsFKCtD63tEh0+9qq82Cy7GsgZvU+D4aJ7d8XwdaU6TU0gpEgL138MQ8Bqc9TFfQ 4bdZY635bdTg4FaG6g5/0LD6Wr2KdhTO3kNyamKI0mHWQUZrSruz1nZNeIQ4FmX7HrfT +8w6MqT1PBgWP1AlNmmxFhmR7wTuFYctQ4ozZ7ANxdzHXS/1uAZniVjFXXzH+j789m6X I7oUr4dOIM/zscADaIAHHU8O4clhhOQD4gag6CldfoB0CTIvCEGSLBsaUuyvfgrqJcH7 kyv1z+7a5fy15gtCl0D/tj+eZa5/QfQ/Ulawx24Q6nFjKqdBaWQcvqA/OFfaAacx2tgC L3HQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003396; x=1755608196; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=oPJ0pN/RvHB5WLTR3vby3H9Qi10hzmuI98P3X9tF0H4=; b=R3MMkGvHEvhN261V/7pfoCLUsNdgzd98dXgv3ixf+0evBXvu/iPzIFOljdb024jvZC rxD8HA3uzUOyQ98UPj8veWTsrzBL/NCqGNgy0HnDo/nL0H3RgbyDOd9DeU6vCCBhOygc 7hE1LWsN4Wva/HU8qx2z2e9xVZdVdEnOZ7ZP8sf/f3cjHi2mn7tQv6SOw26KswkogJXF d95IGnuN2Oi9XG/svrz0BMhuIJQ7JeZzWzwsr3A/sM0PwAR5kgUMSgCs9he2qnPH7Gi2 q7QKxAVPpbsI4JCkhxRWcniCnGUnVIFkHKgTyMpALyg28SXGOnbSxEyfiH9IhejMm96k YY9g== X-Forwarded-Encrypted: i=1; AJvYcCU6fx4kcKqaPBUVVMqDfmntn1SZK3ganBoqxhqNCk8mZQmKQiywB7RFuCocRK7+Qo7yOvtikXwDwOL5pw==@vger.kernel.org X-Gm-Message-State: AOJu0YyF0YWSuv6CvdI71QtwbUyM4H3/3AKJEBP+CLsDT/6pthlg4IiG DVBqV5W53F/Dk5XWT1ULJDyvPaCnFE+KVJi7/WPehZ8nNVh/hcgdu754 X-Gm-Gg: ASbGnct1THgwebp2LHu627RPROLuR8M1nDtO+oWfXXba2Z5v3qEznzsz2dPRuhsbev+ Q0VfMqn/FKxSm9ZHsvOWf8YBCRokFrKXXNibfEVjx57au7aJbwOCZ0pzTBXUwdNUueUMOIZSiRF C6XKpBXt6JwQEihGbklFVzdRxCAsJfRdVdWl8JqLfTiKTzbrOEkpa09iuHP2rrZhpRT1StAUtA/ tB77zi665kK+VnXwq0o6KcqROUdvX6nzAO9NOCwrj1ANhSK+ltEJcePR0iD/8671Z5vHXyPF5Mg 8EtvDNFCMXDdXWeHrw37HXaUrM35zUIhQ1QvN6LFhVFkb3otY9Ex/kptFojzkGr/63n5a46NPrZ /8vZfZyKfqkbVB+ayjZ5Rl1RNLBYUErlHNqJabnChjcn0sHZb5u0VzlpGEe+vbbSh0LdBvTiahj 09iOSDIA== X-Google-Smtp-Source: AGHT+IHrnSvPpIpgRe8u43wfsCLZPcZzCDj1zTAWOEmIMwzXt/U/OEY34Uuz6f6g50xU0qbi4JAhTw== X-Received: by 2002:a17:907:2da4:b0:ae3:6651:58ba with SMTP id a640c23a62f3a-afa1e102c0fmr305946966b.35.1755003395998; Tue, 12 Aug 2025 05:56:35 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:35 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 1/7] ALSA: usb-audio: Add initial driver for TASCAM US-144MKII Date: Tue, 12 Aug 2025 14:56:21 +0200 Message-Id: <20250812125633.79270-2-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch introduces a new driver for the TASCAM US-144MKII USB audio/MIDI interface. It includes the basic driver structure for probing and disconnecting from the USB device. It correctly identifies the device using its VID/PID, performs the necessary initial handshake, and sets the required USB interface alternate settings to prepare the device for operation. At this point, no ALSA devices (PCM or MIDI) are created. This commit forms the foundational skeleton upon which audio and MIDI functionality will be built. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 248 ++++++++++++++++++++++++++++++++++++ sound/usb/usx2y/us144mkii.h | 38 ++++++ 2 files changed, 286 insertions(+) create mode 100644 sound/usb/usx2y/us144mkii.c create mode 100644 sound/usb/usx2y/us144mkii.h diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c new file mode 100644 index 000000000000..f438f222baec --- /dev/null +++ b/sound/usb/usx2y/us144mkii.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami +/* + * ALSA Driver for TASCAM US-144MKII Audio Interface + */ + +#include "us144mkii.h" + +MODULE_AUTHOR("=C5=A0erif Rami "); +MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); +MODULE_LICENSE("GPL"); + +/** + * @brief Module parameters for ALSA card instantiation. + * + * These parameters allow users to configure how the ALSA sound card + * for the TASCAM US-144MKII is instantiated. + * + * @param index: Array of integers specifying the ALSA card index for each + * device. Defaults to -1 (automatic). + * @param id: Array of strings specifying the ALSA card ID for each device. + * Defaults to "US144MKII". + * @param enable: Array of booleans to enable or disable each device. + * Defaults to {1, 0, ..., 0} (first device enabled). + * @param dev_idx: Internal counter for probed TASCAM devices. + */ +static int index[SNDRV_CARDS] =3D SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] =3D SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] =3D { 1, [1 ...(SNDRV_CARDS - 1)] =3D 0 }; +static int dev_idx; + +static int tascam_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id); +static void tascam_disconnect(struct usb_interface *intf); +static int tascam_suspend(struct usb_interface *intf, pm_message_t message= ); +static int tascam_resume(struct usb_interface *intf); + +/** + * tascam_card_private_free() - Frees private data for the sound card. + * card. + * @card: Pointer to the ALSA sound card instance. + * + * This function is called when the sound card is being freed. It releases + * the reference to the USB device. + */ +static void tascam_card_private_free(struct snd_card *card) +{ + struct tascam_card *tascam =3D card->private_data; + + if (tascam && tascam->dev) { + usb_put_dev(tascam->dev); + tascam->dev =3D NULL; + } +} + +/** + * tascam_probe() - Probes for the TASCAM US-144MKII device. + * @intf: The USB interface being probed. + * @usb_id: The USB device ID. + * + * This function is the entry point for the USB driver on device match. + * is found. It performs initial device setup, including: + * - Checking for the second interface (MIDI) and associating it. + * - Performing a vendor-specific handshake with the device. + * - Setting alternate settings for USB interfaces. + * - Creating and registering the ALSA sound card. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + struct usb_device *dev =3D interface_to_usbdev(intf); + struct snd_card *card; + struct tascam_card *tascam; + int err; + char *handshake_buf __free(kfree) =3D NULL; + + if (dev->speed !=3D USB_SPEED_HIGH) + dev_info( + &dev->dev, + "Device is connected to a USB 1.1 port, this is not supported.\n"); + + /* The device has two interfaces; we drive both from this driver. */ + if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 1) { + tascam =3D usb_get_intfdata(usb_ifnum_to_if(dev, 0)); + if (tascam) { + usb_set_intfdata(intf, tascam); + tascam->iface1 =3D intf; + } + return 0; /* Let the core handle this interface */ + } + + if (dev_idx >=3D SNDRV_CARDS) { + dev_err(&dev->dev, "Too many TASCAM devices present"); + return -ENODEV; + } + + if (!enable[dev_idx]) { + dev_info(&dev->dev, "TASCAM US-144MKII device disabled"); + return -ENOENT; + } + + handshake_buf =3D kmalloc(1, GFP_KERNEL); + if (!handshake_buf) + return -ENOMEM; + + /* Perform vendor-specific handshake */ + err =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_D2H_VENDOR_DEV, + MODE_VAL_HANDSHAKE_READ, 0x0000, handshake_buf, 1, + USB_CTRL_TIMEOUT_MS); + if (err < 0) { + dev_err(&dev->dev, "Handshake read failed with %d\n", err); + return err; + } + + if (handshake_buf[0] !=3D 0x12 && handshake_buf[0] !=3D 0x16 && + handshake_buf[0] !=3D 0x30) { + dev_err(&dev->dev, "Unexpected handshake value: 0x%x\n", + handshake_buf[0]); + return -ENODEV; + } + + /* Set alternate settings to enable audio/MIDI endpoints */ + err =3D usb_set_interface(dev, 0, 1); + if (err < 0) { + dev_err(&dev->dev, + "Failed to set alt setting 1 on interface 0: %d\n", + err); + return err; + } + + err =3D usb_set_interface(dev, 1, 1); + if (err < 0) { + dev_err(&dev->dev, + "Failed to set alt setting 1 on interface 1: %d\n", + err); + return err; + } + + err =3D snd_card_new(&dev->dev, index[dev_idx], id[dev_idx], THIS_MODULE, + sizeof(struct tascam_card), &card); + if (err < 0) { + dev_err(&dev->dev, "Failed to create sound card instance\n"); + return err; + } + + tascam =3D card->private_data; + card->private_free =3D tascam_card_private_free; + tascam->dev =3D usb_get_dev(dev); + tascam->card =3D card; + tascam->iface0 =3D intf; + + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { + strscpy(card->shortname, "TASCAM US-144", + sizeof(card->shortname)); + } else if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144MKII) { + strscpy(card->shortname, "TASCAM US-144MKII", + sizeof(card->shortname)); + } else { + strscpy(card->shortname, "TASCAM Unknown", + sizeof(card->shortname)); + } + snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s", + card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct, + dev_name(&dev->dev)); + + err =3D snd_card_register(card); + if (err < 0) + goto free_card; + + usb_set_intfdata(intf, tascam); + + dev_idx++; + return 0; + +free_card: + snd_card_free(card); + return err; +} + +/** + * tascam_disconnect() - Disconnects the TASCAM US-144MKII device. + * @intf: The USB interface being disconnected. + * + * This function is called when the device is disconnected from the system. + * It cleans up all allocated resources by freeing the sound card. + */ +static void tascam_disconnect(struct usb_interface *intf) +{ + struct tascam_card *tascam =3D usb_get_intfdata(intf); + + if (!tascam) + return; + + if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { + snd_card_disconnect(tascam->card); + snd_card_free(tascam->card); + dev_idx--; + } +} + +/** + * tascam_suspend() - Handles device suspension. + * @intf: The USB interface being suspended. + * @message: Power management message. + * + * This function is a stub for handling device suspension. + * + * Return: 0 on success. + */ +static int tascam_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +/** + * tascam_resume() - Handles device resumption from suspend. + * @intf: The USB interface being resumed. + * + * This function is a stub for handling device resumption. + * + * Return: 0 on success. + */ +static int tascam_resume(struct usb_interface *intf) +{ + return 0; +} + +static const struct usb_device_id tascam_usb_ids[] =3D { + { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144) }, + { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) }, + { /* Terminating entry */ } +}; +MODULE_DEVICE_TABLE(usb, tascam_usb_ids); + +static struct usb_driver tascam_alsa_driver =3D { + .name =3D DRIVER_NAME, + .probe =3D tascam_probe, + .disconnect =3D tascam_disconnect, + .suspend =3D tascam_suspend, + .resume =3D tascam_resume, + .id_table =3D tascam_usb_ids, +}; + +module_usb_driver(tascam_alsa_driver); diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h new file mode 100644 index 000000000000..38731a1285ea --- /dev/null +++ b/sound/usb/usx2y/us144mkii.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +// Copyright (c) 2025 =C5=A0erif Rami + + #ifndef __US144MKII_H + #define __US144MKII_H + + #include + #include + #include + + #define DRIVER_NAME "us144mkii" + +/* --- USB Device Identification --- */ + #define USB_VID_TASCAM 0x0644 + #define USB_PID_TASCAM_US144 0x800f + #define USB_PID_TASCAM_US144MKII 0x8020 + +/* --- USB Control Message Protocol --- */ + #define RT_D2H_VENDOR_DEV (USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_DEVICE) + #define VENDOR_REQ_MODE_CONTROL 0x49 + #define MODE_VAL_HANDSHAKE_READ 0x0000 + #define USB_CTRL_TIMEOUT_MS 1000 + +/** + * struct tascam_card - Driver data structure for TASCAM US-144MKII. + * @dev: Pointer to the USB device. + * @iface0: Pointer to USB interface 0 (audio). + * @iface1: Pointer to USB interface 1 (MIDI). + * @card: Pointer to the ALSA sound card instance. + */ +struct tascam_card { + struct usb_device *dev; + struct usb_interface *iface0; + struct usb_interface *iface1; + struct snd_card *card; +}; + + #endif /* __US144MKII_H */ --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f43.google.com (mail-ej1-f43.google.com [209.85.218.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6AECE25EF81; Tue, 12 Aug 2025 12:56:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003401; cv=none; b=EX2oqi+C4YB3fn1BgPf0zCyGyLUiK+drOdREhNcu2avAnBr1dow9v3A9VNU74BLY1ztcN58ThQQSGgHydixbQAT0YIKfApw1QffCzPbMc/0Mpay54ASfyrLWMBfukuFU+RWG3mlf9pyOp4pU1nxA3KMuRb6UFl9kotNk99MsEUM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003401; c=relaxed/simple; bh=ynoTq1/bwBXqXeejCZ6FKLFpgPLASDXZexFoDUHw91g=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=gYH/7ScV+NO5jDsu/AUZcP3Und7bxL355bY6XA3k6aG/J2uO0SXCkQtpBhK1yqq9bScehAJM5lDjh9UPcOUVoZROwEe7PdKDLtROCecHQTt5dRgjKoSHTGT4YWnAQJ10+0xPlUqEEf+ZakrKUSlvoDy53gIQd/O6hGxIvV+/Wts= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=KFoRCy/C; arc=none smtp.client-ip=209.85.218.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="KFoRCy/C" Received: by mail-ej1-f43.google.com with SMTP id a640c23a62f3a-af93c3bac8fso766022466b.2; Tue, 12 Aug 2025 05:56:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003398; x=1755608198; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dUC1e+3QnP9V3PaLp8zCefmsZcTyYCpnVsVHZkiXKFI=; b=KFoRCy/CZ1Dnv+ayJCbmJan7kmYKP0xrr3qHGvYSSx7xQlOTpS3YFJGesBbKFnxa4m VtY1f3pIKFk+cVC14IXiXQkxE1+1HyNOhesfnyfcTceRSr9oX7Cw9rnxwB3qvPSxvbwK u8bahS1Cr01Eisfj0ftigmENnfQOt7utoTuM0e9yBEzkZ3+OzSqSqw9JWrrKph3bFGZv pARWQ2ZPBAxDUdLicij3gwsjjLVLjH/IcxzgvzX97nWFTlOkgE83kXs2xPDXNayMs853 z0rPYGmSdiSIVMAvoHBn+G/otF+5VHfErf7veeTnHKDK16eHHMB3L9v8Mi9621CfgNOs QGvg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003398; x=1755608198; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dUC1e+3QnP9V3PaLp8zCefmsZcTyYCpnVsVHZkiXKFI=; b=PlKs2VZXYvwAr1pCBF9w/UyPm7Nvo9aIWMNQN4F0Q3LQtCpnib8HiWbhQgTe+Q8vE3 cSHv6DfePyWDu/PMghjH9EnnyQBMvX4Fw/qhrbWcSrvHonanok1vsk7HyxKrNdkJeeua hcMcXWl+u1M8xwPTTKg+hGICZaksprjigCgsSUykegDzLd3+egquBrpWBSLPORsmAPFp bj8EHki/WOSbizRrdAupO+Eg4m0HOJ4nUKLYJVZCrYyXypmAxZNbVm05JaDbFyeHSFME GYGnWP3YND0CcKdH+hWzjEoX9DMctno+0oLRSZFdfX779LditQVc4tRthTBNDKibFeG9 1zkg== X-Forwarded-Encrypted: i=1; AJvYcCVQySybT48i3WhV93CNnK/kJ7hBff9qBnsHioRlKSJB9MjnBOOGtOFV4oXFfDLy9SD9y01FA2X+uPAc2Q==@vger.kernel.org X-Gm-Message-State: AOJu0YyNzOuH1PWJ0NiUduD7HGqcYgxVGLzuGsp0bw4vMuGIXW0QbPR5 /jrTTv8c23k/rk0IPsVys8ImCBCYI7f3dDxiqGDQ++LU8XpAK8KXtXS9g19taoyB/HA= X-Gm-Gg: ASbGnctZ9qHqK8/ysSzhAWvYJzZG0e7/8GRm5puT8vPL4bDJ5pl4bJYtKItlURyV28t NRY8CbV4CtgG2z9GDLn0vF6XYIUwKuRWotxneFi2B49dhO6qbz/S0pdXdW7FtV/X1Y8GXQ26nWL CTtN1Ih4/nn70dMPf0sBzlNqBWT2m5opOeYJI6qMAYcEIuAzAefUe8DBJQXLa9eoSPNsejibboG 2ExEdkjU6wBbGoYf5v5YdA6SeJrbz+u6Noa+cXHuPcKzb5UAgCnoxlsbvIQPcpoxQGmMqrMXn1C RX5bqEn9KcXQoDyhMLHmMtMpMmh28rY6fIAaIhGpq9591o7fkh0SzQIlgZCkEGB06IsaeK3JcmW fP+RPbP0wFpaLMcpJFmJ2cSoF49Nzbuy9dPttzkA94drNLuPkDG3G0HytrcuxwBob/apSpCwsNZ VlYRqYyw== X-Google-Smtp-Source: AGHT+IHcLIjyu6J0yPAonsHbUa6HMGZifNM6A0nWre0/VFv1YDO6U6xEf4aeeMp1f5dccqLnqHhWBQ== X-Received: by 2002:a17:907:d18:b0:ae3:24c:6a21 with SMTP id a640c23a62f3a-af9c643f89bmr1407342366b.26.1755003397200; Tue, 12 Aug 2025 05:56:37 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:36 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 1/6] ALSA: usb-audio: us144mkii: Add PCM core infrastructure Date: Tue, 12 Aug 2025 14:56:22 +0200 Message-Id: <20250812125633.79270-3-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch adds the ALSA PCM device infrastructure. It creates a new PCM device with one playback and one capture stream. The hardware capabilities (formats, rates, channels) are defined in `tascam_pcm_hw`. The core PCM operations (`snd_pcm_ops`) for both playback and capture are implemented, including open, close, hw_params, prepare, and pointer callbacks. The trigger callback is a stub for now. This commit allows user-space applications to interact with the ALSA device, but no audio will be streamed yet. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 20 ++++- sound/usb/usx2y/us144mkii.h | 67 +++++++++++++---- sound/usb/usx2y/us144mkii_capture.c | 108 +++++++++++++++++++++++++++ sound/usb/usx2y/us144mkii_pcm.c | 77 +++++++++++++++++++ sound/usb/usx2y/us144mkii_pcm.h | 76 +++++++++++++++++++ sound/usb/usx2y/us144mkii_playback.c | 108 +++++++++++++++++++++++++++ 6 files changed, 437 insertions(+), 19 deletions(-) create mode 100644 sound/usb/usx2y/us144mkii_capture.c create mode 100644 sound/usb/usx2y/us144mkii_pcm.c create mode 100644 sound/usb/usx2y/us144mkii_pcm.h create mode 100644 sound/usb/usx2y/us144mkii_playback.c diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index f438f222baec..9b14390efb56 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -22,7 +22,7 @@ MODULE_LICENSE("GPL"); * Defaults to "US144MKII". * @param enable: Array of booleans to enable or disable each device. * Defaults to {1, 0, ..., 0} (first device enabled). - * @param dev_idx: Internal counter for probed TASCAM devices. + * @param dev_idx: Internal counter for the number of TASCAM devices probe= d. */ static int index[SNDRV_CARDS] =3D SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] =3D SNDRV_DEFAULT_STR; @@ -36,7 +36,7 @@ static int tascam_suspend(struct usb_interface *intf, pm_= message_t message); static int tascam_resume(struct usb_interface *intf); =20 /** - * tascam_card_private_free() - Frees private data for the sound card. + * tascam_card_private_free() - Frees private data associated with the sou= nd * card. * @card: Pointer to the ALSA sound card instance. * @@ -58,12 +58,12 @@ static void tascam_card_private_free(struct snd_card *c= ard) * @intf: The USB interface being probed. * @usb_id: The USB device ID. * - * This function is the entry point for the USB driver on device match. + * This function is the entry point for the USB driver when a matching dev= ice * is found. It performs initial device setup, including: * - Checking for the second interface (MIDI) and associating it. * - Performing a vendor-specific handshake with the device. * - Setting alternate settings for USB interfaces. - * - Creating and registering the ALSA sound card. + * - Creating and registering the ALSA sound card and PCM device. * * Return: 0 on success, or a negative error code on failure. */ @@ -152,6 +152,18 @@ static int tascam_probe(struct usb_interface *intf, tascam->card =3D card; tascam->iface0 =3D intf; =20 + spin_lock_init(&tascam->lock); + + err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); + if (err < 0) + goto free_card; + tascam->pcm->private_data =3D tascam; + strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); + + err =3D tascam_init_pcm(tascam->pcm); + if (err < 0) + goto free_card; + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { strscpy(card->shortname, "TASCAM US-144", diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index 38731a1285ea..cbfcb062532f 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -1,38 +1,75 @@ /* SPDX-License-Identifier: GPL-2.0-only */ // Copyright (c) 2025 =C5=A0erif Rami =20 - #ifndef __US144MKII_H - #define __US144MKII_H +#ifndef __US144MKII_H +#define __US144MKII_H =20 - #include - #include - #include +#include +#include +#include +#include =20 - #define DRIVER_NAME "us144mkii" +#define DRIVER_NAME "us144mkii" =20 /* --- USB Device Identification --- */ - #define USB_VID_TASCAM 0x0644 - #define USB_PID_TASCAM_US144 0x800f - #define USB_PID_TASCAM_US144MKII 0x8020 +#define USB_VID_TASCAM 0x0644 +#define USB_PID_TASCAM_US144 0x800f +#define USB_PID_TASCAM_US144MKII 0x8020 + +/* --- Audio Format Configuration --- */ +#define BYTES_PER_SAMPLE 3 +#define NUM_CHANNELS 4 +#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) =20 /* --- USB Control Message Protocol --- */ - #define RT_D2H_VENDOR_DEV (USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_DEVICE) - #define VENDOR_REQ_MODE_CONTROL 0x49 - #define MODE_VAL_HANDSHAKE_READ 0x0000 - #define USB_CTRL_TIMEOUT_MS 1000 +#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) +#define VENDOR_REQ_MODE_CONTROL 0x49 +#define MODE_VAL_HANDSHAKE_READ 0x0000 +#define USB_CTRL_TIMEOUT_MS 1000 + +struct tascam_card; + +#include "us144mkii_pcm.h" =20 /** - * struct tascam_card - Driver data structure for TASCAM US-144MKII. + * struct tascam_card - Main driver data structure for the TASCAM US-144MK= II. * @dev: Pointer to the USB device. * @iface0: Pointer to USB interface 0 (audio). * @iface1: Pointer to USB interface 1 (MIDI). * @card: Pointer to the ALSA sound card instance. + * @pcm: Pointer to the ALSA PCM device. + * @playback_substream: Pointer to the active playback PCM substream. + * @capture_substream: Pointer to the active capture PCM substream. + * @playback_active: Atomic flag indicating if playback is active. + * @capture_active: Atomic flag indicating if capture is active. + * @driver_playback_pos: Current position in the ALSA playback buffer (fra= mes). + * @driver_capture_pos: Current position in the ALSA capture buffer (frame= s). + * @playback_frames_consumed: Total frames consumed by playback. + * @capture_frames_processed: Total frames processed for capture. + * @current_rate: Currently configured sample rate of the device. + * @lock: Main spinlock for protecting shared driver state. */ struct tascam_card { struct usb_device *dev; struct usb_interface *iface0; struct usb_interface *iface1; struct snd_card *card; + struct snd_pcm *pcm; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + atomic_t playback_active; + atomic_t capture_active; + + snd_pcm_uframes_t driver_playback_pos; + snd_pcm_uframes_t driver_capture_pos; + + u64 playback_frames_consumed; + u64 capture_frames_processed; + + int current_rate; + spinlock_t lock; }; =20 - #endif /* __US144MKII_H */ +#endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mki= i_capture.c new file mode 100644 index 000000000000..f2ce90743d22 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * tascam_capture_open() - Opens the PCM capture substream. + * @substream: The ALSA PCM substream to open. + * + * This function sets the hardware parameters for the capture substream + * and stores a reference to the substream in the driver's private data. + * + * Return: 0 on success. + */ +static int tascam_capture_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + substream->runtime->hw =3D tascam_pcm_hw; + tascam->capture_substream =3D substream; + atomic_set(&tascam->capture_active, 0); + + return 0; +} + +/** + * tascam_capture_close() - Closes the PCM capture substream. + * @substream: The ALSA PCM substream to close. + * + * This function clears the reference to the capture substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_capture_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->capture_substream =3D NULL; + + return 0; +} + +/** + * tascam_capture_prepare() - Prepares the PCM capture substream for use. + * @substream: The ALSA PCM substream to prepare. + * + * This function initializes capture-related counters. + * + * Return: 0 on success. + */ +static int tascam_capture_prepare(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->driver_capture_pos =3D 0; + tascam->capture_frames_processed =3D 0; + + return 0; +} + +/** + * tascam_capture_pointer() - Returns the current capture pointer position. + * @substream: The ALSA PCM substream. + * + * This function returns the current position of the capture pointer within + * the ALSA ring buffer, in frames. + * + * Return: The current capture pointer position in frames. + */ +static snd_pcm_uframes_t +tascam_capture_pointer(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + u64 pos; + + if (!atomic_read(&tascam->capture_active)) + return 0; + + guard(spinlock_irqsave)(&tascam->lock); + pos =3D tascam->capture_frames_processed; + + if (runtime->buffer_size =3D=3D 0) + return 0; + + u64 remainder =3D do_div(pos, runtime->buffer_size); + + return runtime ? remainder : 0; +} + +/** + * tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operat= ions, + * including open, close, ioctl, hardware parameters, hardware free, prepa= re, + * trigger, and pointer. + */ +const struct snd_pcm_ops tascam_capture_ops =3D { + .open =3D tascam_capture_open, + .close =3D tascam_capture_close, + .ioctl =3D snd_pcm_lib_ioctl, + .hw_params =3D tascam_pcm_hw_params, + .hw_free =3D tascam_pcm_hw_free, + .prepare =3D tascam_capture_prepare, + .trigger =3D tascam_pcm_trigger, + .pointer =3D tascam_capture_pointer, +}; diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c new file mode 100644 index 000000000000..be6f0fa4750b --- /dev/null +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +const struct snd_pcm_hardware tascam_pcm_hw =3D { + .info =3D (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats =3D SNDRV_PCM_FMTBIT_S24_3LE, + .rates =3D (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000), + .rate_min =3D 44100, + .rate_max =3D 96000, + .channels_min =3D NUM_CHANNELS, + .channels_max =3D NUM_CHANNELS, + .buffer_bytes_max =3D 1024 * 1024, + .period_bytes_min =3D 48 * BYTES_PER_FRAME, + .period_bytes_max =3D 1024 * BYTES_PER_FRAME, + .periods_min =3D 2, + .periods_max =3D 1024, +}; + +int tascam_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return 0; +} + +int tascam_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + int err =3D 0; + + guard(spinlock_irqsave)(&tascam->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 1); + atomic_set(&tascam->capture_active, 1); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 0); + atomic_set(&tascam->capture_active, 0); + } + break; + default: + err =3D -EINVAL; + break; + } + + return err; +} + +int tascam_init_pcm(struct snd_pcm *pcm) +{ + struct tascam_card *tascam =3D pcm->private_data; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &tascam_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + tascam->dev->dev.parent, 64 * 1024, + tascam_pcm_hw.buffer_bytes_max); + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pc= m.h new file mode 100644 index 000000000000..bb8b51f9b6fb --- /dev/null +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +// Copyright (c) 2025 =C5=A0erif Rami + +#ifndef __US144MKII_PCM_H +#define __US144MKII_PCM_H + +#include "us144mkii.h" + +/** + * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period s= izes + * for the TASCAM US-144MKII audio interface. + */ +extern const struct snd_pcm_hardware tascam_pcm_hw; + +/** + * tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream opera= tions. + */ +extern const struct snd_pcm_ops tascam_playback_ops; + +/** + * tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operat= ions. + */ +extern const struct snd_pcm_ops tascam_capture_ops; + +/** + * tascam_init_pcm() - Initializes the ALSA PCM device. + * @pcm: Pointer to the ALSA PCM device to initialize. + * + * This function sets up the PCM operations and preallocates pages for the + * PCM buffer. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_init_pcm(struct snd_pcm *pcm); + +/** + * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. + * @substream: The ALSA PCM substream. + * @params: The hardware parameters to apply. + * + * This function is a stub for handling hardware parameter configuration. + * + * Return: 0 on success. + */ +int tascam_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + +/** + * tascam_pcm_hw_free() - Frees hardware parameters for PCM streams. + * @substream: The ALSA PCM substream. + * + * This function is a stub for freeing hardware-related resources. + * + * Return: 0 on success. + */ +int tascam_pcm_hw_free(struct snd_pcm_substream *substream); + +/** + * tascam_pcm_trigger() - Triggers the start or stop of PCM streams. + * @substream: The ALSA PCM substream. + * @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START). + * + * This function handles starting and stopping of playback and capture str= eams + * by setting atomic flags. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd); + +#endif /* __US144MKII_PCM_H */ diff --git a/sound/usb/usx2y/us144mkii_playback.c b/sound/usb/usx2y/us144mk= ii_playback.c new file mode 100644 index 000000000000..ac582a534123 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_playback.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * tascam_playback_open() - Opens the PCM playback substream. + * @substream: The ALSA PCM substream to open. + * + * This function sets the hardware parameters for the playback substream + * and stores a reference to the substream in the driver's private data. + * + * Return: 0 on success. + */ +static int tascam_playback_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + substream->runtime->hw =3D tascam_pcm_hw; + tascam->playback_substream =3D substream; + atomic_set(&tascam->playback_active, 0); + + return 0; +} + +/** + * tascam_playback_close() - Closes the PCM playback substream. + * @substream: The ALSA PCM substream to close. + * + * This function clears the reference to the playback substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_playback_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->playback_substream =3D NULL; + + return 0; +} + +/** + * tascam_playback_prepare() - Prepares the PCM playback substream for use. + * @substream: The ALSA PCM substream to prepare. + * + * This function initializes playback-related counters. + * + * Return: 0 on success. + */ +static int tascam_playback_prepare(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->driver_playback_pos =3D 0; + tascam->playback_frames_consumed =3D 0; + + return 0; +} + +/** + * tascam_playback_pointer() - Returns the current playback pointer positi= on. + * @substream: The ALSA PCM substream. + * + * This function returns the current position of the playback pointer with= in + * the ALSA ring buffer, in frames. + * + * Return: The current playback pointer position in frames. + */ +static snd_pcm_uframes_t +tascam_playback_pointer(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + u64 pos; + + if (!atomic_read(&tascam->playback_active)) + return 0; + + guard(spinlock_irqsave)(&tascam->lock); + pos =3D tascam->playback_frames_consumed; + + if (runtime->buffer_size =3D=3D 0) + return 0; + + u64 remainder =3D do_div(pos, runtime->buffer_size); + + return runtime ? remainder : 0; +} + +/** + * tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream opera= tions, + * including open, close, ioctl, hardware parameters, hardware free, prepa= re, + * trigger, and pointer. + */ +const struct snd_pcm_ops tascam_playback_ops =3D { + .open =3D tascam_playback_open, + .close =3D tascam_playback_close, + .ioctl =3D snd_pcm_lib_ioctl, + .hw_params =3D tascam_pcm_hw_params, + .hw_free =3D tascam_pcm_hw_free, + .prepare =3D tascam_playback_prepare, + .trigger =3D tascam_pcm_trigger, + .pointer =3D tascam_playback_pointer, +}; --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f54.google.com (mail-ej1-f54.google.com [209.85.218.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C7EFC27056E; Tue, 12 Aug 2025 12:56:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003405; cv=none; b=CqsTM7B+Lyzjw9l6a35Tbjglq7UHF5SIRnxI+Tbx5ijq6Kg0Mk8Y0OjJ5B27n8LiqfVu19KwIrHHeUXzTGFvuY++qWHOQq9GZZxSdIblZSx8cmKEbbb2WvAwUt0PwWlYUe8S04Cq6fTR1l2P5XOdDB2Dw9CAixj1tB825KDgDsg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003405; c=relaxed/simple; bh=9HYyS/+VV0aGFLTQ7jU7voH9P7iEGVmf+CqGSSrPOug=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=f5Av10m/zEoXerNQePaENAbSv72By+W9+/PabbN9LclCowwGTtDW05WSARXWvriCGhOHfmY3wFbqRTwMYWtHxJUrKdRkvabsLchAvAlfXhV89rX/wux0migLZP6TKPVH/9DfiQ2ukn94vZRTLVKMSevwCfrDo8ezqKiOr23Wmy4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=gCl4i4pe; arc=none smtp.client-ip=209.85.218.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="gCl4i4pe" Received: by mail-ej1-f54.google.com with SMTP id a640c23a62f3a-af922ab4849so820491866b.3; Tue, 12 Aug 2025 05:56:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003400; x=1755608200; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=zLQpLAfk/bqkKEJyPTfh33jg/C2ldUG2P0qmmtpfjr8=; b=gCl4i4pebFEYe94wEGurVr+qyi9Y3WwUhSeulPiZIzsc0AZcLW98Er/lDfZKthggHA BVzX+LsKsaGuZS3xvdOzNvg8ML5eQGUoIyHFY5fLezfywsHi4/ewgouXviX4HGkIfrva nZxkEPcFSBSW2OD5L5rleFzcAfm9zI6BOuN73F/xFVD+JErVAEjAppBYKPwoxrEB7eE6 cwDmjiLw8IlQABbc6uMGhJcl6NaFiD6G4TMGXo0bwR0wWGnZq2ye7zBLkGvPUkMVKnZG F40g8B8iIa9Oq+0fupK/DaAqvwd/UtYzCLo45H03eJ3A2obcchY8OSoK/5cueMMeAdvo j7xg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003400; x=1755608200; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=zLQpLAfk/bqkKEJyPTfh33jg/C2ldUG2P0qmmtpfjr8=; b=ruwDU3Y6SURqW1A5PMwHiMJXkETbSos5nNLtmh8oyhXfJyhzTAY7JATnoP1ikwEe7g h6H4sQ5BMKTAoZJeSYHZpsXSns26lcpsVhx/wxH5w4208w4GAmGZktTuluj4HvNZy42N 4z1mH3fTTlz4XB5CVPaaddANk12NPp1bwFjBoaRFm+cHov5cyvZ1NPsCj3zLuaFoZmxY ze94flmHmYB9eGp3vxIacTgtJcxeYWb0s+OphT+KU27/vPGbXe5T+6oP3KYv919lzglC JbznwYd2RTeiVM3cseWxkNf2sHYLoTX70zVxLn+HEPL58t+ZUEc0p2eWS3A2h13m5Tpv mKcQ== X-Forwarded-Encrypted: i=1; AJvYcCUnLgx5xfDxGoq9RnoMN1d1+EZ8fre7uSlFsH8ZNDvDPWcu2Ec6D8mK6oGcML7b/5uOi8t9/1cyVOVmQQ==@vger.kernel.org X-Gm-Message-State: AOJu0YyP041kCfwaWGX65vJdizMPabneoeQ7F4txeUlTTqhPTqjYsN5g xzb0oLIopY5jJakBI+PcDigzwu00I9164gplc8oZ+tOkPBGN1Sdkrg8X X-Gm-Gg: ASbGncuY5HHd98o9iMOSalbFGDl/Kad99PTt6IDwA2l+VopTebvxEttTjR0rFBZe0fW XIvNhxgubdahtTT426IWFBQ0UXViGiL0NZdOFLJnHQSmoyYzW5ZUlgrVoEo1/i7cHSJl3cFTb9P sPckFrCmt/Hqe45w1wGRKl5bdmNMBrIZOqQ27u9Wd7w3UD8kXnFp+6Ed3O9IgFUo6IxFKIur/BQ GkR0OyxrWhZU+weeQRCeUT+ZORH2fE8ZCYDYCyfaqZzvmIZIZhsPSmZneW1wTRDgZ+UlpbbubKV ZEYuTQf6ofZXpB30Tlzd9ik2SQn3sq5bUiLnaxUa1EtdwLHIGlTvDNtH9+qnxnMI70gAz9ndIk/ QP06Pji7GpwHErumYm2msZu8/Sw1Ay08AZsu2crbbHBNousQuj6A0Nz9C3NgQSRVbMioJzbe59r tH6hQHCA== X-Google-Smtp-Source: AGHT+IEyC/GUwvKGWjb5QJs3+EFY0MhepNSN4F+jFlNnF4vJ1uzqObDm5qTtnOZM2QD8EADmD2t3SA== X-Received: by 2002:a17:907:7f0f:b0:adb:45eb:7d0b with SMTP id a640c23a62f3a-afa1df5e523mr330950566b.15.1755003399669; Tue, 12 Aug 2025 05:56:39 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:39 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 2/6] ALSA: usb-audio: us144mkii: Implement audio playback and feedback Date: Tue, 12 Aug 2025 14:56:24 +0200 Message-Id: <20250812125633.79270-5-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch implements the full audio playback data path. It introduces the allocation, submission, and completion handling for isochronous playback and feedback URBs. The feedback URB completion handler is the core of the driver's clocking mechanism. It reads the number of samples consumed by the device and uses a pattern-based algorithm to adjust the size of outgoing playback packets. This keeps the host and device synchronized and prevents xruns. The patch also adds the necessary vendor and UAC control messages to configure the device's sample rate. The PCM trigger is updated to start and stop the playback and feedback URBs. Basic suspend and resume handlers are included to manage stream state across power cycles. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 171 ++++++++++++- sound/usb/usx2y/us144mkii.h | 178 +++++++++++-- sound/usb/usx2y/us144mkii_capture.c | 1 + sound/usb/usx2y/us144mkii_pcm.c | 262 ++++++++++++++++++-- sound/usb/usx2y/us144mkii_pcm.h | 64 ++++- sound/usb/usx2y/us144mkii_playback.c | 357 ++++++++++++++++++++++++++- 6 files changed, 993 insertions(+), 40 deletions(-) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 9b14390efb56..3a147fba873e 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -35,6 +35,110 @@ static void tascam_disconnect(struct usb_interface *int= f); static int tascam_suspend(struct usb_interface *intf, pm_message_t message= ); static int tascam_resume(struct usb_interface *intf); =20 +void tascam_free_urbs(struct tascam_card *tascam) +{ + int i; + + usb_kill_anchored_urbs(&tascam->playback_anchor); + for (i =3D 0; i < NUM_PLAYBACK_URBS; i++) { + if (tascam->playback_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->playback_urb_alloc_size, + tascam->playback_urbs[i]->transfer_buffer, + tascam->playback_urbs[i]->transfer_dma); + usb_free_urb(tascam->playback_urbs[i]); + tascam->playback_urbs[i] =3D NULL; + } + } + + usb_kill_anchored_urbs(&tascam->feedback_anchor); + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + if (tascam->feedback_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->feedback_urb_alloc_size, + tascam->feedback_urbs[i]->transfer_buffer, + tascam->feedback_urbs[i]->transfer_dma); + usb_free_urb(tascam->feedback_urbs[i]); + tascam->feedback_urbs[i] =3D NULL; + } + } +} + +int tascam_alloc_urbs(struct tascam_card *tascam) +{ + int i; + size_t max_packet_size; + + max_packet_size =3D ((96000 / 8000) + 2) * BYTES_PER_FRAME; + tascam->playback_urb_alloc_size =3D + max_packet_size * PLAYBACK_URB_PACKETS; + + for (i =3D 0; i < NUM_PLAYBACK_URBS; i++) { + struct urb *urb =3D + usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL); + + if (!urb) + goto error; + tascam->playback_urbs[i] =3D urb; + + urb->transfer_buffer =3D usb_alloc_coherent( + tascam->dev, tascam->playback_urb_alloc_size, + GFP_KERNEL, &urb->transfer_dma); + if (!urb->transfer_buffer) + goto error; + + urb->dev =3D tascam->dev; + urb->pipe =3D usb_sndisocpipe(tascam->dev, EP_AUDIO_OUT); + urb->transfer_flags =3D URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval =3D 1; + urb->context =3D tascam; + urb->complete =3D playback_urb_complete; + } + + tascam->feedback_urb_alloc_size =3D + FEEDBACK_PACKET_SIZE * FEEDBACK_URB_PACKETS; + + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + struct urb *f_urb =3D + usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL); + + if (!f_urb) + goto error; + tascam->feedback_urbs[i] =3D f_urb; + + f_urb->transfer_buffer =3D usb_alloc_coherent( + tascam->dev, tascam->feedback_urb_alloc_size, + GFP_KERNEL, &f_urb->transfer_dma); + if (!f_urb->transfer_buffer) + goto error; + + f_urb->dev =3D tascam->dev; + f_urb->pipe =3D + usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK); + f_urb->transfer_flags =3D URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + f_urb->interval =3D 4; + f_urb->context =3D tascam; + f_urb->complete =3D feedback_urb_complete; + } + + return 0; + +error: + dev_err(tascam->card->dev, "Failed to allocate URBs\n"); + tascam_free_urbs(tascam); + return -ENOMEM; +} + +void tascam_stop_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, stop_work); + + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + atomic_set(&tascam->active_urbs, 0); +} + /** * tascam_card_private_free() - Frees private data associated with the sou= nd * card. @@ -64,6 +168,7 @@ static void tascam_card_private_free(struct snd_card *ca= rd) * - Performing a vendor-specific handshake with the device. * - Setting alternate settings for USB interfaces. * - Creating and registering the ALSA sound card and PCM device. + * - Allocating and initializing URBs for audio transfers. * * Return: 0 on success, or a negative error code on failure. */ @@ -153,6 +258,11 @@ static int tascam_probe(struct usb_interface *intf, tascam->iface0 =3D intf; =20 spin_lock_init(&tascam->lock); + init_usb_anchor(&tascam->playback_anchor); + init_usb_anchor(&tascam->feedback_anchor); + + INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); + INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); =20 err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); if (err < 0) @@ -164,6 +274,10 @@ static int tascam_probe(struct usb_interface *intf, if (err < 0) goto free_card; =20 + err =3D tascam_alloc_urbs(tascam); + if (err < 0) + goto free_card; + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { strscpy(card->shortname, "TASCAM US-144", @@ -189,6 +303,7 @@ static int tascam_probe(struct usb_interface *intf, return 0; =20 free_card: + tascam_free_urbs(tascam); snd_card_free(card); return err; } @@ -198,7 +313,8 @@ static int tascam_probe(struct usb_interface *intf, * @intf: The USB interface being disconnected. * * This function is called when the device is disconnected from the system. - * It cleans up all allocated resources by freeing the sound card. + * It cleans up all allocated resources by freeing the sound card, which in + * turn triggers freeing of URBs and other resources. */ static void tascam_disconnect(struct usb_interface *intf) { @@ -209,6 +325,9 @@ static void tascam_disconnect(struct usb_interface *int= f) =20 if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { snd_card_disconnect(tascam->card); + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->stop_pcm_work); + tascam_free_urbs(tascam); snd_card_free(tascam->card); dev_idx--; } @@ -219,12 +338,25 @@ static void tascam_disconnect(struct usb_interface *i= ntf) * @intf: The USB interface being suspended. * @message: Power management message. * - * This function is a stub for handling device suspension. + * This function is called when the device is suspended. It stops all acti= ve + * streams and kills all URBs. * * Return: 0 on success. */ static int tascam_suspend(struct usb_interface *intf, pm_message_t message) { + struct tascam_card *tascam =3D usb_get_intfdata(intf); + + if (!tascam) + return 0; + + snd_pcm_suspend_all(tascam->pcm); + + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->stop_pcm_work); + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + return 0; } =20 @@ -232,12 +364,43 @@ static int tascam_suspend(struct usb_interface *intf,= pm_message_t message) * tascam_resume() - Handles device resumption from suspend. * @intf: The USB interface being resumed. * - * This function is a stub for handling device resumption. + * This function is called when the device resumes from suspend. It + * re-establishes the active USB interface settings and re-configures the = sample + * rate if it was previously active. * - * Return: 0 on success. + * Return: 0 on success, or a negative error code on failure. */ static int tascam_resume(struct usb_interface *intf) { + struct tascam_card *tascam =3D usb_get_intfdata(intf); + int err; + + if (!tascam) + return 0; + + dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); + + /* Re-establish the active USB interface settings. */ + err =3D usb_set_interface(tascam->dev, 0, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 0: %d\n", + err); + return err; + } + err =3D usb_set_interface(tascam->dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 1: %d\n", + err); + return err; + } + + /* Re-configure the sample rate if one was previously active */ + if (tascam->current_rate > 0) + us144mkii_configure_device_for_rate(tascam, + tascam->current_rate); + return 0; } =20 diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index cbfcb062532f..257ab22dafc1 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -5,6 +5,7 @@ #define __US144MKII_H =20 #include +#include #include #include #include @@ -16,21 +17,87 @@ #define USB_PID_TASCAM_US144 0x800f #define USB_PID_TASCAM_US144MKII 0x8020 =20 -/* --- Audio Format Configuration --- */ -#define BYTES_PER_SAMPLE 3 -#define NUM_CHANNELS 4 -#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) +/* --- USB Endpoints (Alternate Setting 1) --- */ +#define EP_PLAYBACK_FEEDBACK 0x81 +#define EP_AUDIO_OUT 0x02 =20 /* --- USB Control Message Protocol --- */ +#define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) #define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) -#define VENDOR_REQ_MODE_CONTROL 0x49 -#define MODE_VAL_HANDSHAKE_READ 0x0000 +#define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVIC= E) + +enum uac_request { + UAC_SET_CUR =3D 0x01, +}; + +enum uac_control_selector { + UAC_SAMPLING_FREQ_CONTROL =3D 0x0100, +}; + +enum tascam_vendor_request { + VENDOR_REQ_REGISTER_WRITE =3D 0x41, + VENDOR_REQ_MODE_CONTROL =3D 0x49, +}; + +enum tascam_mode_value { + MODE_VAL_HANDSHAKE_READ =3D 0x0000, + MODE_VAL_CONFIG =3D 0x0010, + MODE_VAL_STREAM_START =3D 0x0030, +}; + +enum tascam_register { + REG_ADDR_UNKNOWN_0D =3D 0x0d04, + REG_ADDR_UNKNOWN_0E =3D 0x0e00, + REG_ADDR_UNKNOWN_0F =3D 0x0f00, + REG_ADDR_RATE_44100 =3D 0x1000, + REG_ADDR_RATE_48000 =3D 0x1002, + REG_ADDR_RATE_88200 =3D 0x1008, + REG_ADDR_RATE_96000 =3D 0x100a, + REG_ADDR_UNKNOWN_11 =3D 0x110b, +}; + +#define REG_VAL_ENABLE 0x0101 + +/* --- URB Configuration --- */ +#define NUM_PLAYBACK_URBS 4 +#define PLAYBACK_URB_PACKETS 8 +#define NUM_FEEDBACK_URBS 4 +#define FEEDBACK_URB_PACKETS 1 +#define FEEDBACK_PACKET_SIZE 3 #define USB_CTRL_TIMEOUT_MS 1000 +#define FEEDBACK_SYNC_LOSS_THRESHOLD 41 + +/* --- Audio Format Configuration --- */ +#define BYTES_PER_SAMPLE 3 +#define NUM_CHANNELS 4 +#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) +#define FEEDBACK_ACCUMULATOR_SIZE 128 =20 struct tascam_card; =20 #include "us144mkii_pcm.h" =20 +/** + * struct us144mkii_frame_pattern_observer - State for dynamic feedback + * patterns. + * @sample_rate_khz: The current sample rate in kHz. + * @base_feedback_value: The nominal feedback value for the current rate. + * @feedback_offset: An offset to align the feedback value range. + * @full_frame_patterns: A 2D array of pre-calculated packet size patterns. + * @current_index: The current index into the pattern array. + * @previous_index: The previous index, used for state tracking. + * @sync_locked: A flag indicating if the pattern has locked to the stream. + */ +struct us144mkii_frame_pattern_observer { + unsigned int sample_rate_khz; + unsigned int base_feedback_value; + int feedback_offset; + unsigned int full_frame_patterns[5][8]; + unsigned int current_index; + unsigned int previous_index; + bool sync_locked; +}; + /** * struct tascam_card - Main driver data structure for the TASCAM US-144MK= II. * @dev: Pointer to the USB device. @@ -38,38 +105,121 @@ struct tascam_card; * @iface1: Pointer to USB interface 1 (MIDI). * @card: Pointer to the ALSA sound card instance. * @pcm: Pointer to the ALSA PCM device. + * * @playback_substream: Pointer to the active playback PCM substream. - * @capture_substream: Pointer to the active capture PCM substream. + * @playback_urbs: Array of URBs for playback. + * @playback_urb_alloc_size: Size of allocated buffer for each playback UR= B. + * @feedback_urbs: Array of URBs for feedback. + * @feedback_urb_alloc_size: Size of allocated buffer for each feedback UR= B. * @playback_active: Atomic flag indicating if playback is active. - * @capture_active: Atomic flag indicating if capture is active. + * @playback_frames_consumed: Total frames consumed by playback. * @driver_playback_pos: Current position in the ALSA playback buffer (fra= mes). + * @last_period_pos: Last reported period position for playback. + * + * @capture_substream: Pointer to the active capture PCM substream. + * @capture_active: Atomic flag indicating if capture is active. * @driver_capture_pos: Current position in the ALSA capture buffer (frame= s). - * @playback_frames_consumed: Total frames consumed by playback. * @capture_frames_processed: Total frames processed for capture. - * @current_rate: Currently configured sample rate of the device. + * @last_capture_period_pos: Last reported period position for capture. + * + * @stop_work: Work struct for deferred stream stopping. + * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. + * xrun). + * * @lock: Main spinlock for protecting shared driver state. + * @active_urbs: Atomic counter for active URBs. + * @current_rate: Currently configured sample rate of the device. + * + * @feedback_accumulator_pattern: Stores the calculated frames per packet = for + * feedback. + * @feedback_pattern_out_idx: Read index for feedback_accumulator_pattern. + * @feedback_pattern_in_idx: Write index for feedback_accumulator_pattern. + * @feedback_synced: Flag indicating if feedback is synced. + * @feedback_consecutive_errors: Counter for consecutive feedback errors. + * @feedback_urb_skip_count: Number of feedback URBs to skip initially for + * stabilization. + * @fpo: Holds the state for the dynamic feedback pattern generation. + * + * @playback_anchor: USB anchor for playback URBs. + * @feedback_anchor: USB anchor for feedback URBs. */ struct tascam_card { + /* --- Core device pointers --- */ struct usb_device *dev; struct usb_interface *iface0; struct usb_interface *iface1; struct snd_card *card; struct snd_pcm *pcm; =20 + /* --- PCM Substreams --- */ struct snd_pcm_substream *playback_substream; struct snd_pcm_substream *capture_substream; =20 + /* --- URBs and Anchors --- */ + struct urb *playback_urbs[NUM_PLAYBACK_URBS]; + size_t playback_urb_alloc_size; + struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; + size_t feedback_urb_alloc_size; + struct usb_anchor playback_anchor; + struct usb_anchor feedback_anchor; + + /* --- Stream State --- */ + spinlock_t lock; atomic_t playback_active; atomic_t capture_active; + atomic_t active_urbs; + int current_rate; =20 + /* --- Playback State --- */ + u64 playback_frames_consumed; snd_pcm_uframes_t driver_playback_pos; - snd_pcm_uframes_t driver_capture_pos; + u64 last_period_pos; =20 - u64 playback_frames_consumed; + /* --- Capture State --- */ u64 capture_frames_processed; + snd_pcm_uframes_t driver_capture_pos; + u64 last_capture_period_pos; =20 - int current_rate; - spinlock_t lock; + /* --- Feedback Sync State --- */ + unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; + unsigned int feedback_pattern_out_idx; + unsigned int feedback_pattern_in_idx; + bool feedback_synced; + unsigned int feedback_consecutive_errors; + unsigned int feedback_urb_skip_count; + struct us144mkii_frame_pattern_observer fpo; + + /* --- Workqueues --- */ + struct work_struct stop_work; + struct work_struct stop_pcm_work; }; =20 +/** + * tascam_free_urbs() - Free all allocated URBs and associated buffers. + * @tascam: the tascam_card instance + * + * This function kills, unlinks, and frees all playback and feedback URBs, + * along with their transfer buffers. + */ +void tascam_free_urbs(struct tascam_card *tascam); + +/** + * tascam_alloc_urbs() - Allocate all URBs and associated buffers. + * @tascam: the tascam_card instance + * + * This function allocates and initializes all URBs for playback and feedb= ack. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_alloc_urbs(struct tascam_card *tascam); + +/** + * tascam_stop_work_handler() - Work handler to stop all active streams. + * @work: Pointer to the work_struct. + * + * This function is scheduled to stop all active URBs (playback, feedback) + * and reset the active_urbs counter. + */ +void tascam_stop_work_handler(struct work_struct *work); + #endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mki= i_capture.c index f2ce90743d22..22b8faa9bbe8 100644 --- a/sound/usb/usx2y/us144mkii_capture.c +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -55,6 +55,7 @@ static int tascam_capture_prepare(struct snd_pcm_substrea= m *substream) =20 tascam->driver_capture_pos =3D 0; tascam->capture_frames_processed =3D 0; + tascam->last_capture_period_pos =3D 0; =20 return 0; } diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c index be6f0fa4750b..7d1bbd547504 100644 --- a/sound/usb/usx2y/us144mkii_pcm.c +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -3,6 +3,37 @@ =20 #include "us144mkii.h" =20 +/** + * fpoInitPattern() - Generates a packet distribution pattern. + * @size: The number of elements in the pattern array (e.g., 8). + * @pattern_array: Pointer to the array to be populated. + * @initial_value: The base value to initialize each element with. + * @target_sum: The desired sum of all elements in the final array. + * + * This function initializes an array with a base value and then iterative= ly + * adjusts the elements to match a target sum, distributing the difference + * as evenly as possible. + */ +static void fpo_init_pattern(unsigned int size, unsigned int *pattern_arra= y, + unsigned int initial_value, int target_sum) +{ + int diff, i; + + if (!size) + return; + + for (i =3D 0; i < size; ++i) + pattern_array[i] =3D initial_value; + + diff =3D target_sum - (size * initial_value); + for (i =3D 0; i < abs(diff); ++i) { + if (diff > 0) + pattern_array[i]++; + else + pattern_array[i]--; + } +} + const struct snd_pcm_hardware tascam_pcm_hw =3D { .info =3D (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | @@ -21,9 +52,152 @@ const struct snd_pcm_hardware tascam_pcm_hw =3D { .periods_max =3D 1024, }; =20 +void process_playback_routing_us144mkii(struct tascam_card *tascam, + const u8 *src_buffer, u8 *dst_buffer, + size_t frames) +{ + /* This is a stub. Routing will be added in a later commit. */ + if (src_buffer !=3D dst_buffer) + memcpy(dst_buffer, src_buffer, frames * BYTES_PER_FRAME); +} + +int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te) +{ + struct usb_device *dev =3D tascam->dev; + + u8 *rate_payload_buf __free(kfree) =3D NULL; + u16 rate_vendor_wValue; + int err =3D 0; + const u8 *current_payload_src; + + static const u8 payload_44100[] =3D { 0x44, 0xac, 0x00 }; + static const u8 payload_48000[] =3D { 0x80, 0xbb, 0x00 }; + static const u8 payload_88200[] =3D { 0x88, 0x58, 0x01 }; + static const u8 payload_96000[] =3D { 0x00, 0x77, 0x01 }; + + switch (rate) { + case 44100: + current_payload_src =3D payload_44100; + rate_vendor_wValue =3D REG_ADDR_RATE_44100; + break; + case 48000: + current_payload_src =3D payload_48000; + rate_vendor_wValue =3D REG_ADDR_RATE_48000; + break; + case 88200: + current_payload_src =3D payload_88200; + rate_vendor_wValue =3D REG_ADDR_RATE_88200; + break; + case 96000: + current_payload_src =3D payload_96000; + rate_vendor_wValue =3D REG_ADDR_RATE_96000; + break; + default: + dev_err(&dev->dev, + "Unsupported sample rate %d for configuration\n", rate); + return -EINVAL; + } + + rate_payload_buf =3D kmemdup(current_payload_src, 3, GFP_KERNEL); + if (!rate_payload_buf) + return -ENOMEM; + + dev_info(&dev->dev, "Configuring device for %d Hz\n", rate); + + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV, + MODE_VAL_CONFIG, 0x0000, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, + EP_AUDIO_OUT, rate_payload_buf, 3, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0D, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0E, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0F, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + rate_vendor_wValue, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_11, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV, + MODE_VAL_STREAM_START, 0x0000, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + + kfree(rate_payload_buf); + return 0; + +fail: + dev_err(&dev->dev, + "Device configuration failed at rate %d with error %d\n", rate, + err); + kfree(rate_payload_buf); + return err; +} + int tascam_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + int err; + unsigned int rate =3D params_rate(params); + + if (substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK) { + tascam->fpo.sample_rate_khz =3D rate / 1000; + tascam->fpo.base_feedback_value =3D tascam->fpo.sample_rate_khz; + tascam->fpo.feedback_offset =3D 2; + tascam->fpo.current_index =3D 0; + tascam->fpo.previous_index =3D 0; + tascam->fpo.sync_locked =3D false; + + unsigned int initial_value =3D tascam->fpo.sample_rate_khz / 8; + + for (int i =3D 0; i < 5; i++) { + int target_sum =3D tascam->fpo.sample_rate_khz - + tascam->fpo.feedback_offset + i; + fpo_init_pattern(8, tascam->fpo.full_frame_patterns[i], + initial_value, target_sum); + } + } + + if (tascam->current_rate !=3D rate) { + err =3D us144mkii_configure_device_for_rate(tascam, rate); + if (err < 0) { + tascam->current_rate =3D 0; + return err; + } + tascam->current_rate =3D rate; + } + return 0; } =20 @@ -36,29 +210,81 @@ int tascam_pcm_trigger(struct snd_pcm_substream *subst= ream, int cmd) { struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); int err =3D 0; + int i; + bool do_start =3D false; + bool do_stop =3D false; =20 - guard(spinlock_irqsave)(&tascam->lock); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - if (!atomic_read(&tascam->playback_active)) { - atomic_set(&tascam->playback_active, 1); - atomic_set(&tascam->capture_active, 1); + scoped_guard(spinlock_irqsave, &tascam->lock) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 1); + atomic_set(&tascam->capture_active, 1); + do_start =3D true; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 0); + atomic_set(&tascam->capture_active, 0); + do_stop =3D true; + } + break; + default: + err =3D -EINVAL; + break; } - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (atomic_read(&tascam->playback_active)) { - atomic_set(&tascam->playback_active, 0); - atomic_set(&tascam->capture_active, 0); + } + + if (do_start) { + if (atomic_read(&tascam->active_urbs) > 0) { + dev_WARN(tascam->card->dev, + "Cannot start, URBs still active.\n"); + return -EAGAIN; } - break; - default: - err =3D -EINVAL; - break; + + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + usb_get_urb(tascam->feedback_urbs[i]); + usb_anchor_urb(tascam->feedback_urbs[i], + &tascam->feedback_anchor); + err =3D usb_submit_urb(tascam->feedback_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->feedback_urbs[i]); + usb_put_urb(tascam->feedback_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + for (i =3D 0; i < NUM_PLAYBACK_URBS; i++) { + usb_get_urb(tascam->playback_urbs[i]); + usb_anchor_urb(tascam->playback_urbs[i], + &tascam->playback_anchor); + err =3D usb_submit_urb(tascam->playback_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->playback_urbs[i]); + usb_put_urb(tascam->playback_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + + return 0; +start_rollback: + dev_err(tascam->card->dev, + "Failed to submit URBs to start stream: %d\n", err); + do_stop =3D true; } =20 + if (do_stop) + schedule_work(&tascam->stop_work); + return err; } =20 diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pc= m.h index bb8b51f9b6fb..6ca00c3ce53d 100644 --- a/sound/usb/usx2y/us144mkii_pcm.h +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -28,6 +28,38 @@ extern const struct snd_pcm_ops tascam_playback_ops; */ extern const struct snd_pcm_ops tascam_capture_ops; =20 +/** + * playback_urb_complete() - Completion handler for playback isochronous U= RBs. + * @urb: the completed URB + * + * This function runs in interrupt context. It calculates the number of by= tes + * to send in the next set of packets based on the feedback-driven clock, + * copies the audio data from the ALSA ring buffer, and resubmits the URB. + */ +void playback_urb_complete(struct urb *urb); + +/** + * feedback_urb_complete() - Completion handler for feedback isochronous U= RBs. + * @urb: the completed URB + * + * This is the master clock for the driver. It runs in interrupt context. + * It reads the feedback value from the device, which indicates how many + * samples the device has consumed. This information is used to adjust the + * playback rate and to advance the capture stream pointer, keeping both + * streams in sync. It then calls snd_pcm_period_elapsed if necessary and + * resubmits itself. + */ +void feedback_urb_complete(struct urb *urb); + +/** + * tascam_stop_pcm_work_handler() - Work handler to stop PCM streams. + * @work: Pointer to the work_struct. + * + * This function is scheduled to stop PCM streams (playback and capture) + * from a workqueue context, avoiding blocking operations in interrupt con= text. + */ +void tascam_stop_pcm_work_handler(struct work_struct *work); + /** * tascam_init_pcm() - Initializes the ALSA PCM device. * @pcm: Pointer to the ALSA PCM device to initialize. @@ -39,14 +71,40 @@ extern const struct snd_pcm_ops tascam_capture_ops; */ int tascam_init_pcm(struct snd_pcm *pcm); =20 +/** + * us144mkii_configure_device_for_rate() - Set sample rate via USB control= msgs + * @tascam: the tascam_card instance + * @rate: the target sample rate (e.g., 44100, 96000) + * + * This function sends a sequence of vendor-specific and UAC control messa= ges + * to configure the device hardware for the specified sample rate. + * + * Return: 0 on success, or a negative error code on failure. + */ +int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te); + +/** + * process_playback_routing_us144mkii() - Apply playback routing matrix + * @tascam: The driver instance. + * @src_buffer: Buffer containing 4 channels of S24_3LE audio from ALSA. + * @dst_buffer: Buffer to be filled for the USB device. + * @frames: Number of frames to process. + */ +void process_playback_routing_us144mkii(struct tascam_card *tascam, + const u8 *src_buffer, u8 *dst_buffer, + size_t frames); + /** * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. * @substream: The ALSA PCM substream. * @params: The hardware parameters to apply. * - * This function is a stub for handling hardware parameter configuration. + * This function allocates pages for the PCM buffer and, for playback stre= ams, + * selects the appropriate feedback patterns based on the requested sample= rate. + * It also configures the device hardware for the selected sample rate if = it + * has changed. * - * Return: 0 on success. + * Return: 0 on success, or a negative error code on failure. */ int tascam_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); @@ -67,7 +125,7 @@ int tascam_pcm_hw_free(struct snd_pcm_substream *substre= am); * @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START). * * This function handles starting and stopping of playback and capture str= eams - * by setting atomic flags. + * by submitting or killing the associated URBs. * * Return: 0 on success, or a negative error code on failure. */ diff --git a/sound/usb/usx2y/us144mkii_playback.c b/sound/usb/usx2y/us144mk= ii_playback.c index ac582a534123..17d8a43cc735 100644 --- a/sound/usb/usx2y/us144mkii_playback.c +++ b/sound/usb/usx2y/us144mkii_playback.c @@ -45,16 +45,65 @@ static int tascam_playback_close(struct snd_pcm_substre= am *substream) * tascam_playback_prepare() - Prepares the PCM playback substream for use. * @substream: The ALSA PCM substream to prepare. * - * This function initializes playback-related counters. + * This function initializes playback-related counters and flags, and conf= igures + * the playback URBs with appropriate packet sizes based on the nominal fr= ame + * rate. * * Return: 0 on success. */ static int tascam_playback_prepare(struct snd_pcm_substream *substream) { struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + int i, u; + size_t nominal_frames_per_packet, nominal_bytes_per_packet; + size_t total_bytes_in_urb; =20 tascam->driver_playback_pos =3D 0; tascam->playback_frames_consumed =3D 0; + tascam->last_period_pos =3D 0; + tascam->feedback_pattern_in_idx =3D 0; + tascam->feedback_pattern_out_idx =3D 0; + tascam->feedback_synced =3D false; + tascam->feedback_consecutive_errors =3D 0; + tascam->feedback_urb_skip_count =3D NUM_FEEDBACK_URBS; + + nominal_frames_per_packet =3D runtime->rate / 8000; + for (i =3D 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) + tascam->feedback_accumulator_pattern[i] =3D + nominal_frames_per_packet; + + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + struct urb *f_urb =3D tascam->feedback_urbs[i]; + int j; + + f_urb->number_of_packets =3D FEEDBACK_URB_PACKETS; + f_urb->transfer_buffer_length =3D + FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE; + for (j =3D 0; j < FEEDBACK_URB_PACKETS; j++) { + f_urb->iso_frame_desc[j].offset =3D + j * FEEDBACK_PACKET_SIZE; + f_urb->iso_frame_desc[j].length =3D FEEDBACK_PACKET_SIZE; + } + } + + nominal_bytes_per_packet =3D nominal_frames_per_packet * BYTES_PER_FRAME; + total_bytes_in_urb =3D nominal_bytes_per_packet * PLAYBACK_URB_PACKETS; + + for (u =3D 0; u < NUM_PLAYBACK_URBS; u++) { + struct urb *urb =3D tascam->playback_urbs[u]; + + memset(urb->transfer_buffer, 0, + tascam->playback_urb_alloc_size); + urb->transfer_buffer_length =3D total_bytes_in_urb; + urb->number_of_packets =3D PLAYBACK_URB_PACKETS; + for (i =3D 0; i < PLAYBACK_URB_PACKETS; i++) { + urb->iso_frame_desc[i].offset =3D + i * nominal_bytes_per_packet; + urb->iso_frame_desc[i].length =3D + nominal_bytes_per_packet; + } + } =20 return 0; } @@ -106,3 +155,309 @@ const struct snd_pcm_ops tascam_playback_ops =3D { .trigger =3D tascam_pcm_trigger, .pointer =3D tascam_playback_pointer, }; + +void playback_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + size_t total_bytes_for_urb =3D 0; + snd_pcm_uframes_t offset_frames; + snd_pcm_uframes_t frames_to_copy; + int ret, i; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -ENODEV) + dev_err_ratelimited(tascam->card->dev, + "Playback URB failed: %d\n", + urb->status); + goto out; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + goto out; + + substream =3D tascam->playback_substream; + if (!substream || !substream->runtime) + goto out; + runtime =3D substream->runtime; + + { + guard(spinlock_irqsave)(&tascam->lock); + + for (i =3D 0; i < urb->number_of_packets; i++) { + unsigned int frames_for_packet; + size_t bytes_for_packet; + + if (tascam->feedback_synced) { + frames_for_packet =3D + tascam->feedback_accumulator_pattern + [tascam->feedback_pattern_out_idx]; + tascam->feedback_pattern_out_idx =3D + (tascam->feedback_pattern_out_idx + 1) % + FEEDBACK_ACCUMULATOR_SIZE; + } else { + frames_for_packet =3D runtime->rate / 8000; + } + bytes_for_packet =3D frames_for_packet * BYTES_PER_FRAME; + + urb->iso_frame_desc[i].offset =3D total_bytes_for_urb; + urb->iso_frame_desc[i].length =3D bytes_for_packet; + total_bytes_for_urb +=3D bytes_for_packet; + } + urb->transfer_buffer_length =3D total_bytes_for_urb; + + offset_frames =3D tascam->driver_playback_pos; + frames_to_copy =3D bytes_to_frames(runtime, total_bytes_for_urb); + tascam->driver_playback_pos =3D + (offset_frames + frames_to_copy) % runtime->buffer_size; + } + + if (total_bytes_for_urb > 0) { + u8 *dst_buf =3D urb->transfer_buffer; + + /* Handle ring buffer wrap-around */ + if (offset_frames + frames_to_copy > runtime->buffer_size) { + size_t first_chunk_bytes =3D frames_to_bytes( + runtime, runtime->buffer_size - offset_frames); + size_t second_chunk_bytes =3D + total_bytes_for_urb - first_chunk_bytes; + + memcpy(dst_buf, + runtime->dma_area + + frames_to_bytes(runtime, offset_frames), + first_chunk_bytes); + memcpy(dst_buf + first_chunk_bytes, runtime->dma_area, + second_chunk_bytes); + } else { + memcpy(dst_buf, + runtime->dma_area + + frames_to_bytes(runtime, offset_frames), + total_bytes_for_urb); + } + + process_playback_routing_us144mkii(tascam, dst_buf, dst_buf, + frames_to_copy); + } + + urb->dev =3D tascam->dev; + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->playback_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit playback URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } +out: + usb_put_urb(urb); +} + +void feedback_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + struct snd_pcm_substream *playback_ss, *capture_ss; + struct snd_pcm_runtime *playback_rt, *capture_rt; + u64 total_frames_in_urb =3D 0; + int ret, p; + unsigned int old_in_idx, new_in_idx; + bool playback_period_elapsed =3D false; + bool capture_period_elapsed =3D false; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -ENODEV) { + dev_err_ratelimited(tascam->card->dev, + "Feedback URB failed: %d\n", + urb->status); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } + goto out; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + goto out; + + playback_ss =3D tascam->playback_substream; + if (!playback_ss || !playback_ss->runtime) + goto out; + playback_rt =3D playback_ss->runtime; + + capture_ss =3D tascam->capture_substream; + capture_rt =3D capture_ss ? capture_ss->runtime : NULL; + + { + guard(spinlock_irqsave)(&tascam->lock); + + if (tascam->feedback_urb_skip_count > 0) { + tascam->feedback_urb_skip_count--; + goto continue_unlock; + } + + old_in_idx =3D tascam->feedback_pattern_in_idx; + + for (p =3D 0; p < urb->number_of_packets; p++) { + u8 feedback_value =3D 0; + const unsigned int *pattern; + bool packet_ok =3D + (urb->iso_frame_desc[p].status =3D=3D 0 && + urb->iso_frame_desc[p].actual_length >=3D 1); + + if (packet_ok) + feedback_value =3D + *((u8 *)urb->transfer_buffer + + urb->iso_frame_desc[p].offset); + + if (packet_ok) { + int delta =3D feedback_value - + tascam->fpo.base_feedback_value + + tascam->fpo.feedback_offset; + int pattern_idx; + + if (delta < 0) { + pattern_idx =3D + 0; // Clamp to the lowest pattern + } else if (delta >=3D 5) { + pattern_idx =3D + 4; // Clamp to the highest pattern + } else { + pattern_idx =3D delta; + } + + pattern =3D + tascam->fpo + .full_frame_patterns[pattern_idx]; + tascam->feedback_consecutive_errors =3D 0; + int i; + + for (i =3D 0; i < 8; i++) { + unsigned int in_idx =3D + (tascam->feedback_pattern_in_idx + + i) % + FEEDBACK_ACCUMULATOR_SIZE; + + tascam->feedback_accumulator_pattern + [in_idx] =3D pattern[i]; + total_frames_in_urb +=3D pattern[i]; + } + } else { + unsigned int nominal_frames =3D + playback_rt->rate / 8000; + int i; + + if (tascam->feedback_synced) { + tascam->feedback_consecutive_errors++; + if (tascam->feedback_consecutive_errors > + FEEDBACK_SYNC_LOSS_THRESHOLD) { + dev_err(tascam->card->dev, + "Fatal: Feedback sync lost. Stopping stream.\n"); + schedule_work( + &tascam->stop_pcm_work); + tascam->feedback_synced =3D false; + goto continue_unlock; + } + } + for (i =3D 0; i < 8; i++) { + unsigned int in_idx =3D + (tascam->feedback_pattern_in_idx + + i) % + FEEDBACK_ACCUMULATOR_SIZE; + + tascam->feedback_accumulator_pattern + [in_idx] =3D nominal_frames; + total_frames_in_urb +=3D nominal_frames; + } + } + tascam->feedback_pattern_in_idx =3D + (tascam->feedback_pattern_in_idx + 8) % + FEEDBACK_ACCUMULATOR_SIZE; + } + + new_in_idx =3D tascam->feedback_pattern_in_idx; + + if (!tascam->feedback_synced) { + unsigned int out_idx =3D tascam->feedback_pattern_out_idx; + bool is_ahead =3D (new_in_idx - out_idx) % + FEEDBACK_ACCUMULATOR_SIZE < + (FEEDBACK_ACCUMULATOR_SIZE / 2); + bool was_behind =3D (old_in_idx - out_idx) % + FEEDBACK_ACCUMULATOR_SIZE >=3D + (FEEDBACK_ACCUMULATOR_SIZE / 2); + + if (is_ahead && was_behind) { + dev_dbg(tascam->card->dev, + "Sync Acquired! (in: %u, out: %u)\n", + new_in_idx, out_idx); + tascam->feedback_synced =3D true; + tascam->feedback_consecutive_errors =3D 0; + } + } + + if (total_frames_in_urb > 0) { + tascam->playback_frames_consumed +=3D total_frames_in_urb; + if (atomic_read(&tascam->capture_active)) + tascam->capture_frames_processed +=3D + total_frames_in_urb; + } + + if (playback_rt->period_size > 0) { + u64 current_period =3D + div_u64(tascam->playback_frames_consumed, + playback_rt->period_size); + + if (current_period > tascam->last_period_pos) { + tascam->last_period_pos =3D current_period; + playback_period_elapsed =3D true; + } + } + + if (atomic_read(&tascam->capture_active) && capture_rt && + capture_rt->period_size > 0) { + u64 current_capture_period =3D + div_u64(tascam->capture_frames_processed, + capture_rt->period_size); + + if (current_capture_period > + tascam->last_capture_period_pos) { + tascam->last_capture_period_pos =3D + current_capture_period; + capture_period_elapsed =3D true; + } + } + } + +continue_unlock: + if (playback_period_elapsed) + snd_pcm_period_elapsed(playback_ss); + if (capture_period_elapsed) + snd_pcm_period_elapsed(capture_ss); + + urb->dev =3D tascam->dev; + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->feedback_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit feedback URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + } +out: + usb_put_urb(urb); +} + +void tascam_stop_pcm_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, stop_pcm_work); + + if (tascam->playback_substream) + snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN); + if (tascam->capture_substream) + snd_pcm_stop(tascam->capture_substream, SNDRV_PCM_STATE_XRUN); +} --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f47.google.com (mail-ej1-f47.google.com [209.85.218.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9873A26AA91; Tue, 12 Aug 2025 12:56:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003403; cv=none; b=nJhLNp/CPNG1d+sgZ/Fp8GztdKZ4MRAtau1id23yyvQ4zn9O6HpzwZ4WsAziAjVqYZSVhiOZU0zxwxuuImo2ZEUJaX7PN1/dtsvvLvWzdLQGeb93UvbDAMdY4cK0sav2GionEaDq/lBT9pcy8xLJaoPyt6iJTWUZWn95OikjuMo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003403; c=relaxed/simple; bh=ynoTq1/bwBXqXeejCZ6FKLFpgPLASDXZexFoDUHw91g=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=dSrTn9DfleTc0ap2ETyMMsG3u8G2Oz+Rbb6pJzvA4WhyL1wbsKrNK9Ff7v/DU242deRoibD71UNSAB392VCmlnmuOeJE+Qh48anfUfPOYbvQIR2XdgQuvYyQZnirLKGjnCuJwZmt/0bh4D8pU+smdeh44vEfxK6V/BWUjB0M3lU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=kvWAt7Al; arc=none smtp.client-ip=209.85.218.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kvWAt7Al" Received: by mail-ej1-f47.google.com with SMTP id a640c23a62f3a-af93381a1d2so865762766b.3; Tue, 12 Aug 2025 05:56:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003399; x=1755608199; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dUC1e+3QnP9V3PaLp8zCefmsZcTyYCpnVsVHZkiXKFI=; b=kvWAt7AlN6EeDI36F4pLkZ8WVIvmcO4I2JdCmyJSWLsgNKmM8qLcU1ZtbC9R4YYLwo fD10nijOFimwGGm3LRqHx3qGzPfWhk3FAUj23lqfGfMqrhvA4J0quePzyk/K/h76fBO7 SW0gOkz0Kt11a1eJpES1PJblsm2oQvh5Urf/jJkWsmFXBBRSBwUcry+jGNcMUQOT80fk ij+XG9zd8Zw09YqVpYhrw0AxfObqu8290TO8c2o7NOBHv0Snzv6cMx7DLlQPiPZmo9QM p+JNmvIn2iNgdUg8UreQ/BEAkt/fqQ4UmAVOuWbp1LJ85R5w8nE/n6xpZRFzxYUuLU21 W8Mw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003399; x=1755608199; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dUC1e+3QnP9V3PaLp8zCefmsZcTyYCpnVsVHZkiXKFI=; b=AXwlyZriQPhuguEm4RLXZ7ooRcHvuzAPa3ju5VGtkTd/MLxByyDsr6nZ/VOFB3o0Gy IaoQrH9M1zj/mUUwypRGuseFyM02nmxfTJ82HBiclRypL4s0G5TO8/G7FZqZ40Pz+FL8 +7knnUIsexAAbJ2lDv0eIFzhqOYLuL3J/o4Xch+VZsh6t3VSIETVKFNrZFWocW3XqxrN sVWt6GHcb2/UIA+2O/cM9hCDJXiFxqsY2lxT9HgecJOwIDXnwaIaBCfQP57oJL+K25g2 zh5ZJS+YD2jsfXTRLUzycM9Eq4Ra/iNJn/F0JzhTrbWA954zLSkl2HGZXjsiDn+7hQBs LlWA== X-Forwarded-Encrypted: i=1; AJvYcCWHTysGWD8PDq3jYwffcUSbk1U2EnlB9noF+Mvpc5QclXCy2kfSrnGh+L8q15H8S4KmgXmOW42Kle0/HA==@vger.kernel.org X-Gm-Message-State: AOJu0Ywm+ozEhjH8mxCli8TmOCkYbSWVKy2Rfb3DZbQU9t2hzPwbMa8e W6oqE24RnlKXJwAoKiF9utcVCm9M8WV4jbykT94h2x0b1Q24E5zXzXBw X-Gm-Gg: ASbGnctiQ1yGxDgUry4jsRLacKCO7MS24i3cLvJ3zQHye0O2G/DbXECE98Xq3byGSno B3AM789q1YMuJMScfZqxcXO0AuHSvXk2iAjmWHxBVrVJhqgV5HldR7uboTIwDtHKJbslx8XD0bE uvvX4BLllCED1PLOwyYAs7ILRQgjSTZCoUOwl6AI0UTDSXZ29Nqfdd4aDzenvJzzlYf44BWCGUK qc9COFsx3HoQNsO9iI6fxPyyMl5FwvFDFppckAMDg9I4FUIg3uOxRJdOSWA3/SuFTdPD6hrpoCV SnRACAGiOmCrvz3Nuj2s6el3p8j+LwymKpyOqrRPmDNtzqPsdJLK/xPzKvXSivy97raeV5RM7Yv XEP6dq7/wAJ0WKhRmdDb5w7S942ZeYXxQZa7PdRYhUgCocmWS+6R/9MHPSHh6dMeI2ow20Ff4tR I1y9cSpQ== X-Google-Smtp-Source: AGHT+IFymyRXSSeDravsogOpxhWxe6PWgybrfe5XqZUG5Qqv478BHKTyCKF7IQDUaG0ThaDVXxBgsQ== X-Received: by 2002:a17:907:980a:b0:af7:37d1:93b6 with SMTP id a640c23a62f3a-afa1dff7b28mr309765866b.15.1755003398217; Tue, 12 Aug 2025 05:56:38 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:37 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 2/7] ALSA: usb-audio: us144mkii: Add PCM core infrastructure Date: Tue, 12 Aug 2025 14:56:23 +0200 Message-Id: <20250812125633.79270-4-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch adds the ALSA PCM device infrastructure. It creates a new PCM device with one playback and one capture stream. The hardware capabilities (formats, rates, channels) are defined in `tascam_pcm_hw`. The core PCM operations (`snd_pcm_ops`) for both playback and capture are implemented, including open, close, hw_params, prepare, and pointer callbacks. The trigger callback is a stub for now. This commit allows user-space applications to interact with the ALSA device, but no audio will be streamed yet. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 20 ++++- sound/usb/usx2y/us144mkii.h | 67 +++++++++++++---- sound/usb/usx2y/us144mkii_capture.c | 108 +++++++++++++++++++++++++++ sound/usb/usx2y/us144mkii_pcm.c | 77 +++++++++++++++++++ sound/usb/usx2y/us144mkii_pcm.h | 76 +++++++++++++++++++ sound/usb/usx2y/us144mkii_playback.c | 108 +++++++++++++++++++++++++++ 6 files changed, 437 insertions(+), 19 deletions(-) create mode 100644 sound/usb/usx2y/us144mkii_capture.c create mode 100644 sound/usb/usx2y/us144mkii_pcm.c create mode 100644 sound/usb/usx2y/us144mkii_pcm.h create mode 100644 sound/usb/usx2y/us144mkii_playback.c diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index f438f222baec..9b14390efb56 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -22,7 +22,7 @@ MODULE_LICENSE("GPL"); * Defaults to "US144MKII". * @param enable: Array of booleans to enable or disable each device. * Defaults to {1, 0, ..., 0} (first device enabled). - * @param dev_idx: Internal counter for probed TASCAM devices. + * @param dev_idx: Internal counter for the number of TASCAM devices probe= d. */ static int index[SNDRV_CARDS] =3D SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] =3D SNDRV_DEFAULT_STR; @@ -36,7 +36,7 @@ static int tascam_suspend(struct usb_interface *intf, pm_= message_t message); static int tascam_resume(struct usb_interface *intf); =20 /** - * tascam_card_private_free() - Frees private data for the sound card. + * tascam_card_private_free() - Frees private data associated with the sou= nd * card. * @card: Pointer to the ALSA sound card instance. * @@ -58,12 +58,12 @@ static void tascam_card_private_free(struct snd_card *c= ard) * @intf: The USB interface being probed. * @usb_id: The USB device ID. * - * This function is the entry point for the USB driver on device match. + * This function is the entry point for the USB driver when a matching dev= ice * is found. It performs initial device setup, including: * - Checking for the second interface (MIDI) and associating it. * - Performing a vendor-specific handshake with the device. * - Setting alternate settings for USB interfaces. - * - Creating and registering the ALSA sound card. + * - Creating and registering the ALSA sound card and PCM device. * * Return: 0 on success, or a negative error code on failure. */ @@ -152,6 +152,18 @@ static int tascam_probe(struct usb_interface *intf, tascam->card =3D card; tascam->iface0 =3D intf; =20 + spin_lock_init(&tascam->lock); + + err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); + if (err < 0) + goto free_card; + tascam->pcm->private_data =3D tascam; + strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); + + err =3D tascam_init_pcm(tascam->pcm); + if (err < 0) + goto free_card; + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { strscpy(card->shortname, "TASCAM US-144", diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index 38731a1285ea..cbfcb062532f 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -1,38 +1,75 @@ /* SPDX-License-Identifier: GPL-2.0-only */ // Copyright (c) 2025 =C5=A0erif Rami =20 - #ifndef __US144MKII_H - #define __US144MKII_H +#ifndef __US144MKII_H +#define __US144MKII_H =20 - #include - #include - #include +#include +#include +#include +#include =20 - #define DRIVER_NAME "us144mkii" +#define DRIVER_NAME "us144mkii" =20 /* --- USB Device Identification --- */ - #define USB_VID_TASCAM 0x0644 - #define USB_PID_TASCAM_US144 0x800f - #define USB_PID_TASCAM_US144MKII 0x8020 +#define USB_VID_TASCAM 0x0644 +#define USB_PID_TASCAM_US144 0x800f +#define USB_PID_TASCAM_US144MKII 0x8020 + +/* --- Audio Format Configuration --- */ +#define BYTES_PER_SAMPLE 3 +#define NUM_CHANNELS 4 +#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) =20 /* --- USB Control Message Protocol --- */ - #define RT_D2H_VENDOR_DEV (USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_DEVICE) - #define VENDOR_REQ_MODE_CONTROL 0x49 - #define MODE_VAL_HANDSHAKE_READ 0x0000 - #define USB_CTRL_TIMEOUT_MS 1000 +#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) +#define VENDOR_REQ_MODE_CONTROL 0x49 +#define MODE_VAL_HANDSHAKE_READ 0x0000 +#define USB_CTRL_TIMEOUT_MS 1000 + +struct tascam_card; + +#include "us144mkii_pcm.h" =20 /** - * struct tascam_card - Driver data structure for TASCAM US-144MKII. + * struct tascam_card - Main driver data structure for the TASCAM US-144MK= II. * @dev: Pointer to the USB device. * @iface0: Pointer to USB interface 0 (audio). * @iface1: Pointer to USB interface 1 (MIDI). * @card: Pointer to the ALSA sound card instance. + * @pcm: Pointer to the ALSA PCM device. + * @playback_substream: Pointer to the active playback PCM substream. + * @capture_substream: Pointer to the active capture PCM substream. + * @playback_active: Atomic flag indicating if playback is active. + * @capture_active: Atomic flag indicating if capture is active. + * @driver_playback_pos: Current position in the ALSA playback buffer (fra= mes). + * @driver_capture_pos: Current position in the ALSA capture buffer (frame= s). + * @playback_frames_consumed: Total frames consumed by playback. + * @capture_frames_processed: Total frames processed for capture. + * @current_rate: Currently configured sample rate of the device. + * @lock: Main spinlock for protecting shared driver state. */ struct tascam_card { struct usb_device *dev; struct usb_interface *iface0; struct usb_interface *iface1; struct snd_card *card; + struct snd_pcm *pcm; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + atomic_t playback_active; + atomic_t capture_active; + + snd_pcm_uframes_t driver_playback_pos; + snd_pcm_uframes_t driver_capture_pos; + + u64 playback_frames_consumed; + u64 capture_frames_processed; + + int current_rate; + spinlock_t lock; }; =20 - #endif /* __US144MKII_H */ +#endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mki= i_capture.c new file mode 100644 index 000000000000..f2ce90743d22 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * tascam_capture_open() - Opens the PCM capture substream. + * @substream: The ALSA PCM substream to open. + * + * This function sets the hardware parameters for the capture substream + * and stores a reference to the substream in the driver's private data. + * + * Return: 0 on success. + */ +static int tascam_capture_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + substream->runtime->hw =3D tascam_pcm_hw; + tascam->capture_substream =3D substream; + atomic_set(&tascam->capture_active, 0); + + return 0; +} + +/** + * tascam_capture_close() - Closes the PCM capture substream. + * @substream: The ALSA PCM substream to close. + * + * This function clears the reference to the capture substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_capture_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->capture_substream =3D NULL; + + return 0; +} + +/** + * tascam_capture_prepare() - Prepares the PCM capture substream for use. + * @substream: The ALSA PCM substream to prepare. + * + * This function initializes capture-related counters. + * + * Return: 0 on success. + */ +static int tascam_capture_prepare(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->driver_capture_pos =3D 0; + tascam->capture_frames_processed =3D 0; + + return 0; +} + +/** + * tascam_capture_pointer() - Returns the current capture pointer position. + * @substream: The ALSA PCM substream. + * + * This function returns the current position of the capture pointer within + * the ALSA ring buffer, in frames. + * + * Return: The current capture pointer position in frames. + */ +static snd_pcm_uframes_t +tascam_capture_pointer(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + u64 pos; + + if (!atomic_read(&tascam->capture_active)) + return 0; + + guard(spinlock_irqsave)(&tascam->lock); + pos =3D tascam->capture_frames_processed; + + if (runtime->buffer_size =3D=3D 0) + return 0; + + u64 remainder =3D do_div(pos, runtime->buffer_size); + + return runtime ? remainder : 0; +} + +/** + * tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operat= ions, + * including open, close, ioctl, hardware parameters, hardware free, prepa= re, + * trigger, and pointer. + */ +const struct snd_pcm_ops tascam_capture_ops =3D { + .open =3D tascam_capture_open, + .close =3D tascam_capture_close, + .ioctl =3D snd_pcm_lib_ioctl, + .hw_params =3D tascam_pcm_hw_params, + .hw_free =3D tascam_pcm_hw_free, + .prepare =3D tascam_capture_prepare, + .trigger =3D tascam_pcm_trigger, + .pointer =3D tascam_capture_pointer, +}; diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c new file mode 100644 index 000000000000..be6f0fa4750b --- /dev/null +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +const struct snd_pcm_hardware tascam_pcm_hw =3D { + .info =3D (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats =3D SNDRV_PCM_FMTBIT_S24_3LE, + .rates =3D (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000), + .rate_min =3D 44100, + .rate_max =3D 96000, + .channels_min =3D NUM_CHANNELS, + .channels_max =3D NUM_CHANNELS, + .buffer_bytes_max =3D 1024 * 1024, + .period_bytes_min =3D 48 * BYTES_PER_FRAME, + .period_bytes_max =3D 1024 * BYTES_PER_FRAME, + .periods_min =3D 2, + .periods_max =3D 1024, +}; + +int tascam_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return 0; +} + +int tascam_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + int err =3D 0; + + guard(spinlock_irqsave)(&tascam->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 1); + atomic_set(&tascam->capture_active, 1); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 0); + atomic_set(&tascam->capture_active, 0); + } + break; + default: + err =3D -EINVAL; + break; + } + + return err; +} + +int tascam_init_pcm(struct snd_pcm *pcm) +{ + struct tascam_card *tascam =3D pcm->private_data; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &tascam_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + tascam->dev->dev.parent, 64 * 1024, + tascam_pcm_hw.buffer_bytes_max); + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pc= m.h new file mode 100644 index 000000000000..bb8b51f9b6fb --- /dev/null +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +// Copyright (c) 2025 =C5=A0erif Rami + +#ifndef __US144MKII_PCM_H +#define __US144MKII_PCM_H + +#include "us144mkii.h" + +/** + * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period s= izes + * for the TASCAM US-144MKII audio interface. + */ +extern const struct snd_pcm_hardware tascam_pcm_hw; + +/** + * tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream opera= tions. + */ +extern const struct snd_pcm_ops tascam_playback_ops; + +/** + * tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operat= ions. + */ +extern const struct snd_pcm_ops tascam_capture_ops; + +/** + * tascam_init_pcm() - Initializes the ALSA PCM device. + * @pcm: Pointer to the ALSA PCM device to initialize. + * + * This function sets up the PCM operations and preallocates pages for the + * PCM buffer. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_init_pcm(struct snd_pcm *pcm); + +/** + * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. + * @substream: The ALSA PCM substream. + * @params: The hardware parameters to apply. + * + * This function is a stub for handling hardware parameter configuration. + * + * Return: 0 on success. + */ +int tascam_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + +/** + * tascam_pcm_hw_free() - Frees hardware parameters for PCM streams. + * @substream: The ALSA PCM substream. + * + * This function is a stub for freeing hardware-related resources. + * + * Return: 0 on success. + */ +int tascam_pcm_hw_free(struct snd_pcm_substream *substream); + +/** + * tascam_pcm_trigger() - Triggers the start or stop of PCM streams. + * @substream: The ALSA PCM substream. + * @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START). + * + * This function handles starting and stopping of playback and capture str= eams + * by setting atomic flags. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd); + +#endif /* __US144MKII_PCM_H */ diff --git a/sound/usb/usx2y/us144mkii_playback.c b/sound/usb/usx2y/us144mk= ii_playback.c new file mode 100644 index 000000000000..ac582a534123 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_playback.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * tascam_playback_open() - Opens the PCM playback substream. + * @substream: The ALSA PCM substream to open. + * + * This function sets the hardware parameters for the playback substream + * and stores a reference to the substream in the driver's private data. + * + * Return: 0 on success. + */ +static int tascam_playback_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + substream->runtime->hw =3D tascam_pcm_hw; + tascam->playback_substream =3D substream; + atomic_set(&tascam->playback_active, 0); + + return 0; +} + +/** + * tascam_playback_close() - Closes the PCM playback substream. + * @substream: The ALSA PCM substream to close. + * + * This function clears the reference to the playback substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_playback_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->playback_substream =3D NULL; + + return 0; +} + +/** + * tascam_playback_prepare() - Prepares the PCM playback substream for use. + * @substream: The ALSA PCM substream to prepare. + * + * This function initializes playback-related counters. + * + * Return: 0 on success. + */ +static int tascam_playback_prepare(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + + tascam->driver_playback_pos =3D 0; + tascam->playback_frames_consumed =3D 0; + + return 0; +} + +/** + * tascam_playback_pointer() - Returns the current playback pointer positi= on. + * @substream: The ALSA PCM substream. + * + * This function returns the current position of the playback pointer with= in + * the ALSA ring buffer, in frames. + * + * Return: The current playback pointer position in frames. + */ +static snd_pcm_uframes_t +tascam_playback_pointer(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + u64 pos; + + if (!atomic_read(&tascam->playback_active)) + return 0; + + guard(spinlock_irqsave)(&tascam->lock); + pos =3D tascam->playback_frames_consumed; + + if (runtime->buffer_size =3D=3D 0) + return 0; + + u64 remainder =3D do_div(pos, runtime->buffer_size); + + return runtime ? remainder : 0; +} + +/** + * tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream opera= tions, + * including open, close, ioctl, hardware parameters, hardware free, prepa= re, + * trigger, and pointer. + */ +const struct snd_pcm_ops tascam_playback_ops =3D { + .open =3D tascam_playback_open, + .close =3D tascam_playback_close, + .ioctl =3D snd_pcm_lib_ioctl, + .hw_params =3D tascam_pcm_hw_params, + .hw_free =3D tascam_pcm_hw_free, + .prepare =3D tascam_playback_prepare, + .trigger =3D tascam_pcm_trigger, + .pointer =3D tascam_playback_pointer, +}; --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ed1-f53.google.com (mail-ed1-f53.google.com [209.85.208.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9F0C5270ED7; Tue, 12 Aug 2025 12:56:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003407; cv=none; b=JWN61Wy1/yMpbTtfpitH2lHNvvpBMYjZEfdCAXAefYXVgJ+2y6wk7L7iaYKCezoHe6xAF3r98PqpTicz9F9KZsmzgmR599i4d5g6DDLBUWvw6+FRnC+px3W+XHea5Lo1RhvdNAwDCStIFsmHJG9oBa01y5Kk/8LV19cXngilFyM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003407; c=relaxed/simple; bh=fEVl6dr/OTv7oiBvGgjw4LMV8VYApwKCRV0uvvNPm9M=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=cavSiVmyUUlup4iC99B+eOszTGRENOTfaGTHKOSieY/MDyvV6RHSin8TNIawXtD70bRlFTyycTnkhnx4GQd9JFrXVt5ll/ykJqNU0u4DcmBOkY8Na14a3JS2W63N3NfOBALJhnJGCR5BKI4QUjE36ELuMJJ9FzrQ6bM9xnIlyUY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=QwCi43cc; arc=none smtp.client-ip=209.85.208.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="QwCi43cc" Received: by mail-ed1-f53.google.com with SMTP id 4fb4d7f45d1cf-615d0b11621so11230267a12.1; Tue, 12 Aug 2025 05:56:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003401; x=1755608201; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=fg0v+2hv5mjXiI0Fnsf0f/fkTLS5cbrfyLOw8faegMY=; b=QwCi43ccHZz4aFc5S7eqrD2X6JEZeUgjCmXfMWr8FAURUtUqEpK6juIsVdhHi5dwA9 PVpbOzPjQpDHBS5907tIbnqiViwUHDVUiqNGJ/17FRU7Xazjn4AwcniK7In8worU5UqL Dcpt3fGjVI0Q0QCmGlrmSk1E0RtCGAmcwp8Z8elst2xaw9h1TdbpdSvhFG1XHyFCc91R 2A+sDtU82MX96U4Z5Mt5l2tbYdu14l22p4ght/mU20awRAuqP9zczDhGl/RCqSAsGWkR SKio3lr5rDMDpo979XuLCiaR/fFfxf6L4RygoAoC4HHodS3vbCcQA/s1CFqJGHC0pD+q ZEMw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003401; x=1755608201; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=fg0v+2hv5mjXiI0Fnsf0f/fkTLS5cbrfyLOw8faegMY=; b=xEj+d8jpfNL1FBA53WtxnmrSW8O3eryZjLa2tGbgCbt00y3Wb9xaPMSCBLgc1j+tZJ YuISQtMdLuQKYxJHA/VHiqowELYZWBPaBiO+w5k2tNQlcQjl5jWHbzI7WdWL0mlXpMeJ iUd0oE0G/UuPrgozj2EwwYXu4tBYjUCKjC9AAb8DmtoqnqhOeUIqQ9iR5c9Xv7wrt32j 0PDVSkyXTOYcY3eN6iNRiy5eHXQ9gqlrZb0xhwWueLHC3CoJVg7l7sqAKIwhXkhoCSSV o9IXVOs5nxsjO+O9KKGSvBNRZeEfiNAdAdlJiF11rAVquNhg7u2QIANVHJJe3T0EQylK nKOw== X-Forwarded-Encrypted: i=1; AJvYcCU8+87kDLca+KZOWhGlDditq0OjML4RRGtYXyyTIpaf8pwFfrZAtBSI72Mcd+5Pqobq8krg62PuaP+spA==@vger.kernel.org X-Gm-Message-State: AOJu0YxDDFuQR9w8c0iCEXZnjfhXXCcKJskv0qL9xsor4tu9WnAEGD5i e8wTmuoxSe+9QwqgC8oYUpZ3b59muVq4k6oajd2ydp8hncXtO+LXqOVt X-Gm-Gg: ASbGnctLysaMQlZGXkwKyn09mT+E/T+VN6kFhitImeahI0SIAcPLFea0P9HTSaSq3cI A+nD8YPucbwJvXI7FuSlHYzduuM8twIP80vcyrqI5UI+8awmvnUaEPbgwCvWDMt0Z9yiaadFoC4 ccDQ2hQ1dAwvrq5hee/38OklpLk5K0EwAFpCScpoU3mkYvHFJDyB5MKwVEw7H0i0V/8ueuGHBJt rzVBvDiIlREQkMuCX8efP2hXC/bIAQl53IEzwl2VyJZbjzjbBwmIWbg+jHPND4FjHc+DxusvF0L agYelpFRDtddznrJ9NwiabccIcq+wGZD6lDIHLnS2P8mTf1mgr4xHGu7JWHAhrC9gKapXjh5mhF qeTrF6HYRxFv7tkL1W5UMrAk9Ej/1JnhtaPhasXr1W3F0eMba5HEn4wL/NizjsASGZPpdi9ZSzp wQr/utyerxCgfOojLc X-Google-Smtp-Source: AGHT+IHtJ1x4U8iIcEzaVR8FvdBf4qQxeIFLyLsd3vib0jOf50ilhAwetzeSUrFTspfNmsA2Z2Xlrw== X-Received: by 2002:a17:906:1502:b0:af1:4c23:c8c8 with SMTP id a640c23a62f3a-afa1d6a6223mr262536766b.12.1755003400694; Tue, 12 Aug 2025 05:56:40 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:40 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 3/6] ALSA: usb-audio: us144mkii: Implement audio capture and decoding Date: Tue, 12 Aug 2025 14:56:25 +0200 Message-Id: <20250812125633.79270-6-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch adds the full audio capture data path. It allocates and manages bulk URBs to receive audio data from the device. The incoming data is in a custom multiplexed format. A workqueue is used to offload the decoding process from the interrupt context. The `capture_urb_complete` handler copies raw data into a ring buffer and schedules the work. The `tascam_capture_work_handler` then reads from this buffer, decodes the data into standard S32_LE samples, and copies it to the ALSA capture buffer. The PCM trigger is updated to manage the submission of capture URBs, and the feedback handler now also advances the capture stream pointer to keep it synchronized with playback. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 73 ++++++++++ sound/usb/usx2y/us144mkii.h | 44 +++++- sound/usb/usx2y/us144mkii_capture.c | 218 +++++++++++++++++++++++++++- sound/usb/usx2y/us144mkii_pcm.c | 30 ++++ sound/usb/usx2y/us144mkii_pcm.h | 35 ++++- 5 files changed, 392 insertions(+), 8 deletions(-) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 3a147fba873e..7a114e64fb1f 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -62,6 +62,27 @@ void tascam_free_urbs(struct tascam_card *tascam) tascam->feedback_urbs[i] =3D NULL; } } + + usb_kill_anchored_urbs(&tascam->capture_anchor); + for (i =3D 0; i < NUM_CAPTURE_URBS; i++) { + if (tascam->capture_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->capture_urb_alloc_size, + tascam->capture_urbs[i]->transfer_buffer, + tascam->capture_urbs[i]->transfer_dma); + usb_free_urb(tascam->capture_urbs[i]); + tascam->capture_urbs[i] =3D NULL; + } + } + + kfree(tascam->capture_routing_buffer); + tascam->capture_routing_buffer =3D NULL; + kfree(tascam->capture_decode_dst_block); + tascam->capture_decode_dst_block =3D NULL; + kfree(tascam->capture_decode_raw_block); + tascam->capture_decode_raw_block =3D NULL; + kfree(tascam->capture_ring_buffer); + tascam->capture_ring_buffer =3D NULL; } =20 int tascam_alloc_urbs(struct tascam_card *tascam) @@ -121,6 +142,52 @@ int tascam_alloc_urbs(struct tascam_card *tascam) f_urb->complete =3D feedback_urb_complete; } =20 + tascam->capture_urb_alloc_size =3D CAPTURE_URB_SIZE; + for (i =3D 0; i < NUM_CAPTURE_URBS; i++) { + struct urb *c_urb =3D usb_alloc_urb(0, GFP_KERNEL); + + if (!c_urb) + goto error; + tascam->capture_urbs[i] =3D c_urb; + + c_urb->transfer_buffer =3D usb_alloc_coherent( + tascam->dev, tascam->capture_urb_alloc_size, GFP_KERNEL, + &c_urb->transfer_dma); + if (!c_urb->transfer_buffer) + goto error; + + usb_fill_bulk_urb(c_urb, tascam->dev, + usb_rcvbulkpipe(tascam->dev, EP_AUDIO_IN), + c_urb->transfer_buffer, + tascam->capture_urb_alloc_size, + capture_urb_complete, tascam); + c_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; + } + + tascam->capture_ring_buffer =3D + kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL); + if (!tascam->capture_ring_buffer) + goto error; + + tascam->capture_decode_raw_block =3D + kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_KERNEL); + if (!tascam->capture_decode_raw_block) + goto error; + + tascam->capture_decode_dst_block =3D + kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE, + GFP_KERNEL); + if (!tascam->capture_decode_dst_block) + goto error; + + tascam->capture_routing_buffer =3D + kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE, + GFP_KERNEL); + if (!tascam->capture_routing_buffer) + goto error; + return 0; =20 error: @@ -136,6 +203,7 @@ void tascam_stop_work_handler(struct work_struct *work) =20 usb_kill_anchored_urbs(&tascam->playback_anchor); usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); atomic_set(&tascam->active_urbs, 0); } =20 @@ -259,10 +327,12 @@ static int tascam_probe(struct usb_interface *intf, =20 spin_lock_init(&tascam->lock); init_usb_anchor(&tascam->playback_anchor); + init_usb_anchor(&tascam->capture_anchor); init_usb_anchor(&tascam->feedback_anchor); =20 INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); + INIT_WORK(&tascam->capture_work, tascam_capture_work_handler); =20 err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); if (err < 0) @@ -326,6 +396,7 @@ static void tascam_disconnect(struct usb_interface *int= f) if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { snd_card_disconnect(tascam->card); cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); cancel_work_sync(&tascam->stop_pcm_work); tascam_free_urbs(tascam); snd_card_free(tascam->card); @@ -353,8 +424,10 @@ static int tascam_suspend(struct usb_interface *intf, = pm_message_t message) snd_pcm_suspend_all(tascam->pcm); =20 cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); cancel_work_sync(&tascam->stop_pcm_work); usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); usb_kill_anchored_urbs(&tascam->feedback_anchor); =20 return 0; diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index 257ab22dafc1..34b9b275b905 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -20,14 +20,17 @@ /* --- USB Endpoints (Alternate Setting 1) --- */ #define EP_PLAYBACK_FEEDBACK 0x81 #define EP_AUDIO_OUT 0x02 +#define EP_AUDIO_IN 0x86 =20 /* --- USB Control Message Protocol --- */ #define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) +#define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) #define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) #define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVIC= E) =20 enum uac_request { UAC_SET_CUR =3D 0x01, + UAC_GET_CUR =3D 0x81, }; =20 enum uac_control_selector { @@ -64,6 +67,9 @@ enum tascam_register { #define NUM_FEEDBACK_URBS 4 #define FEEDBACK_URB_PACKETS 1 #define FEEDBACK_PACKET_SIZE 3 +#define NUM_CAPTURE_URBS 8 +#define CAPTURE_URB_SIZE 512 +#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4) #define USB_CTRL_TIMEOUT_MS 1000 #define FEEDBACK_SYNC_LOSS_THRESHOLD 41 =20 @@ -73,6 +79,12 @@ enum tascam_register { #define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) #define FEEDBACK_ACCUMULATOR_SIZE 128 =20 +/* --- Capture Decoding Defines --- */ +#define DECODED_CHANNELS_PER_FRAME 4 +#define DECODED_SAMPLE_SIZE 4 +#define FRAMES_PER_DECODE_BLOCK 8 +#define RAW_BYTES_PER_DECODE_BLOCK 512 + struct tascam_card; =20 #include "us144mkii_pcm.h" @@ -117,10 +129,19 @@ struct us144mkii_frame_pattern_observer { * @last_period_pos: Last reported period position for playback. * * @capture_substream: Pointer to the active capture PCM substream. + * @capture_urbs: Array of URBs for capture. + * @capture_urb_alloc_size: Size of allocated buffer for each capture URB. * @capture_active: Atomic flag indicating if capture is active. * @driver_capture_pos: Current position in the ALSA capture buffer (frame= s). * @capture_frames_processed: Total frames processed for capture. * @last_capture_period_pos: Last reported period position for capture. + * @capture_ring_buffer: Ring buffer for raw capture data from USB. + * @capture_ring_buffer_read_ptr: Read pointer for the capture ring buffer. + * @capture_ring_buffer_write_ptr: Write pointer for the capture ring buff= er. + * @capture_decode_raw_block: Buffer for a raw 512-byte capture block. + * @capture_decode_dst_block: Buffer for decoded 32-bit capture samples. + * @capture_routing_buffer: Intermediate buffer for capture routing. + * @capture_work: Work struct for deferred capture processing. * * @stop_work: Work struct for deferred stream stopping. * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. @@ -141,6 +162,7 @@ struct us144mkii_frame_pattern_observer { * @fpo: Holds the state for the dynamic feedback pattern generation. * * @playback_anchor: USB anchor for playback URBs. + * @capture_anchor: USB anchor for capture URBs. * @feedback_anchor: USB anchor for feedback URBs. */ struct tascam_card { @@ -160,7 +182,10 @@ struct tascam_card { size_t playback_urb_alloc_size; struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; size_t feedback_urb_alloc_size; + struct urb *capture_urbs[NUM_CAPTURE_URBS]; + size_t capture_urb_alloc_size; struct usb_anchor playback_anchor; + struct usb_anchor capture_anchor; struct usb_anchor feedback_anchor; =20 /* --- Stream State --- */ @@ -179,6 +204,12 @@ struct tascam_card { u64 capture_frames_processed; snd_pcm_uframes_t driver_capture_pos; u64 last_capture_period_pos; + u8 *capture_ring_buffer; + size_t capture_ring_buffer_read_ptr; + size_t capture_ring_buffer_write_ptr; + u8 *capture_decode_raw_block; + s32 *capture_decode_dst_block; + s32 *capture_routing_buffer; =20 /* --- Feedback Sync State --- */ unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; @@ -192,14 +223,16 @@ struct tascam_card { /* --- Workqueues --- */ struct work_struct stop_work; struct work_struct stop_pcm_work; + struct work_struct capture_work; }; =20 /** * tascam_free_urbs() - Free all allocated URBs and associated buffers. * @tascam: the tascam_card instance * - * This function kills, unlinks, and frees all playback and feedback URBs, - * along with their transfer buffers. + * This function kills, unlinks, and frees all playback, feedback, and + * capture URBs, along with their transfer buffers and the capture + * ring/decode buffers. */ void tascam_free_urbs(struct tascam_card *tascam); =20 @@ -207,7 +240,8 @@ void tascam_free_urbs(struct tascam_card *tascam); * tascam_alloc_urbs() - Allocate all URBs and associated buffers. * @tascam: the tascam_card instance * - * This function allocates and initializes all URBs for playback and feedb= ack. + * This function allocates and initializes all URBs for playback, feedback, + * and capture, as well as the necessary buffers for data processing. * * Return: 0 on success, or a negative error code on failure. */ @@ -217,8 +251,8 @@ int tascam_alloc_urbs(struct tascam_card *tascam); * tascam_stop_work_handler() - Work handler to stop all active streams. * @work: Pointer to the work_struct. * - * This function is scheduled to stop all active URBs (playback, feedback) - * and reset the active_urbs counter. + * This function is scheduled to stop all active URBs (playback, feedback, + * capture) and reset the active_urbs counter. */ void tascam_stop_work_handler(struct work_struct *work); =20 diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mki= i_capture.c index 22b8faa9bbe8..68ff07d7e441 100644 --- a/sound/usb/usx2y/us144mkii_capture.c +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -45,7 +45,7 @@ static int tascam_capture_close(struct snd_pcm_substream = *substream) * tascam_capture_prepare() - Prepares the PCM capture substream for use. * @substream: The ALSA PCM substream to prepare. * - * This function initializes capture-related counters. + * This function initializes capture-related counters and ring buffer poin= ters. * * Return: 0 on success. */ @@ -56,6 +56,8 @@ static int tascam_capture_prepare(struct snd_pcm_substrea= m *substream) tascam->driver_capture_pos =3D 0; tascam->capture_frames_processed =3D 0; tascam->last_capture_period_pos =3D 0; + tascam->capture_ring_buffer_read_ptr =3D 0; + tascam->capture_ring_buffer_write_ptr =3D 0; =20 return 0; } @@ -107,3 +109,217 @@ const struct snd_pcm_ops tascam_capture_ops =3D { .trigger =3D tascam_pcm_trigger, .pointer =3D tascam_capture_pointer, }; + +/** + * decode_tascam_capture_block() - Decodes a raw 512-byte block from the d= evice. + * @src_block: Pointer to the 512-byte raw source block. + * @dst_block: Pointer to the destination buffer for decoded audio frames. + * + * The device sends audio data in a complex, multiplexed format. This func= tion + * demultiplexes the bits from the raw block into 8 frames of 4-channel, + * 24-bit audio (stored in 32-bit containers). + */ +static void decode_tascam_capture_block(const u8 *src_block, s32 *dst_bloc= k) +{ + int frame, bit; + + memset(dst_block, 0, + FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE); + + for (frame =3D 0; frame < FRAMES_PER_DECODE_BLOCK; ++frame) { + const u8 *p_src_frame_base =3D src_block + frame * 64; + s32 *p_dst_frame =3D dst_block + frame * 4; + + s32 ch[4] =3D { 0 }; + + for (bit =3D 0; bit < 24; ++bit) { + u8 byte1 =3D p_src_frame_base[bit]; + u8 byte2 =3D p_src_frame_base[bit + 32]; + + ch[0] =3D (ch[0] << 1) | (byte1 & 1); + ch[2] =3D (ch[2] << 1) | ((byte1 >> 1) & 1); + + ch[1] =3D (ch[1] << 1) | (byte2 & 1); + ch[3] =3D (ch[3] << 1) | ((byte2 >> 1) & 1); + } + + /* + * The result is a 24-bit sample. Shift left by 8 to align it to + * the most significant bits of a 32-bit integer (S32_LE format). + */ + p_dst_frame[0] =3D ch[0] << 8; + p_dst_frame[1] =3D ch[1] << 8; + p_dst_frame[2] =3D ch[2] << 8; + p_dst_frame[3] =3D ch[3] << 8; + } +} + +void tascam_capture_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, capture_work); + struct snd_pcm_substream *substream =3D tascam->capture_substream; + struct snd_pcm_runtime *runtime; + u8 *raw_block =3D tascam->capture_decode_raw_block; + s32 *decoded_block =3D tascam->capture_decode_dst_block; + s32 *routed_block =3D tascam->capture_routing_buffer; + + if (!substream || !substream->runtime) + return; + runtime =3D substream->runtime; + + if (!raw_block || !decoded_block || !routed_block) { + dev_err(tascam->card->dev, + "Capture decode/routing buffers not allocated!\n"); + return; + } + + while (atomic_read(&tascam->capture_active)) { + size_t write_ptr, read_ptr, available_data; + bool can_process; + + { + guard(spinlock_irqsave)(&tascam->lock); + write_ptr =3D tascam->capture_ring_buffer_write_ptr; + read_ptr =3D tascam->capture_ring_buffer_read_ptr; + available_data =3D (write_ptr >=3D read_ptr) ? + (write_ptr - read_ptr) : + (CAPTURE_RING_BUFFER_SIZE - + read_ptr + write_ptr); + can_process =3D + (available_data >=3D RAW_BYTES_PER_DECODE_BLOCK); + + if (can_process) { + size_t bytes_to_end =3D + CAPTURE_RING_BUFFER_SIZE - read_ptr; + if (bytes_to_end >=3D + RAW_BYTES_PER_DECODE_BLOCK) { + memcpy(raw_block, + tascam->capture_ring_buffer + + read_ptr, + RAW_BYTES_PER_DECODE_BLOCK); + } else { + memcpy(raw_block, + tascam->capture_ring_buffer + + read_ptr, + bytes_to_end); + memcpy(raw_block + bytes_to_end, + tascam->capture_ring_buffer, + RAW_BYTES_PER_DECODE_BLOCK - + bytes_to_end); + } + tascam->capture_ring_buffer_read_ptr =3D + (read_ptr + + RAW_BYTES_PER_DECODE_BLOCK) % + CAPTURE_RING_BUFFER_SIZE; + } + } + + if (!can_process) + break; + + decode_tascam_capture_block(raw_block, decoded_block); + process_capture_routing_us144mkii(tascam, decoded_block, + routed_block); + + { + guard(spinlock_irqsave)(&tascam->lock); + if (atomic_read(&tascam->capture_active)) { + int f; + + for (f =3D 0; f < FRAMES_PER_DECODE_BLOCK; ++f) { + u8 *dst_frame_start =3D + runtime->dma_area + + frames_to_bytes( + runtime, + tascam->driver_capture_pos); + s32 *routed_frame_start =3D + routed_block + + (f * NUM_CHANNELS); + int c; + + for (c =3D 0; c < NUM_CHANNELS; c++) { + u8 *dst_channel =3D + dst_frame_start + + (c * BYTES_PER_SAMPLE); + s32 *src_channel_s32 =3D + routed_frame_start + c; + + memcpy(dst_channel, + ((char *)src_channel_s32) + + 1, + 3); + } + + tascam->driver_capture_pos =3D + (tascam->driver_capture_pos + + 1) % + runtime->buffer_size; + } + } + } + } +} + +void capture_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + int ret; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -ENODEV && + urb->status !=3D -EPROTO) + dev_err_ratelimited(tascam->card->dev, + "Capture URB failed: %d\n", + urb->status); + goto out; + } + if (!tascam || !atomic_read(&tascam->capture_active)) + goto out; + + if (urb->actual_length > 0) { + size_t write_ptr; + size_t bytes_to_end; + + { + guard(spinlock_irqsave)(&tascam->lock); + write_ptr =3D tascam->capture_ring_buffer_write_ptr; + bytes_to_end =3D CAPTURE_RING_BUFFER_SIZE - write_ptr; + + if (urb->actual_length > bytes_to_end) { + memcpy(tascam->capture_ring_buffer + write_ptr, + urb->transfer_buffer, bytes_to_end); + memcpy(tascam->capture_ring_buffer, + urb->transfer_buffer + bytes_to_end, + urb->actual_length - bytes_to_end); + } else { + memcpy(tascam->capture_ring_buffer + write_ptr, + urb->transfer_buffer, + urb->actual_length); + } + + tascam->capture_ring_buffer_write_ptr =3D + (write_ptr + urb->actual_length) % + CAPTURE_RING_BUFFER_SIZE; + } + + schedule_work(&tascam->capture_work); + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->capture_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit capture URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } +out: + usb_put_urb(urb); +} diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c index 7d1bbd547504..7bee8b4210a6 100644 --- a/sound/usb/usx2y/us144mkii_pcm.c +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -61,6 +61,16 @@ void process_playback_routing_us144mkii(struct tascam_ca= rd *tascam, memcpy(dst_buffer, src_buffer, frames * BYTES_PER_FRAME); } =20 +void process_capture_routing_us144mkii(struct tascam_card *tascam, + const s32 *decoded_block, + s32 *routed_block) +{ + /* This is a stub. Routing will be added in a later commit. */ + memcpy(routed_block, decoded_block, + FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE); +} + int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te) { struct usb_device *dev =3D tascam->dev; @@ -110,6 +120,12 @@ int us144mkii_configure_device_for_rate(struct tascam_= card *tascam, int rate) USB_CTRL_TIMEOUT_MS); if (err < 0) goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, + EP_AUDIO_IN, rate_payload_buf, 3, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_OUT, rate_payload_buf, 3, @@ -274,6 +290,20 @@ int tascam_pcm_trigger(struct snd_pcm_substream *subst= ream, int cmd) } atomic_inc(&tascam->active_urbs); } + for (i =3D 0; i < NUM_CAPTURE_URBS; i++) { + usb_get_urb(tascam->capture_urbs[i]); + usb_anchor_urb(tascam->capture_urbs[i], + &tascam->capture_anchor); + err =3D usb_submit_urb(tascam->capture_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->capture_urbs[i]); + usb_put_urb(tascam->capture_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } =20 return 0; start_rollback: diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pc= m.h index 6ca00c3ce53d..74da8564431b 100644 --- a/sound/usb/usx2y/us144mkii_pcm.h +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -51,6 +51,16 @@ void playback_urb_complete(struct urb *urb); */ void feedback_urb_complete(struct urb *urb); =20 +/** + * capture_urb_complete() - Completion handler for capture bulk URBs. + * @urb: the completed URB + * + * This function runs in interrupt context. It copies the received raw data + * into an intermediate ring buffer and then schedules the workqueue to pr= ocess + * it. It then resubmits the URB to receive more data. + */ +void capture_urb_complete(struct urb *urb); + /** * tascam_stop_pcm_work_handler() - Work handler to stop PCM streams. * @work: Pointer to the work_struct. @@ -64,8 +74,8 @@ void tascam_stop_pcm_work_handler(struct work_struct *wor= k); * tascam_init_pcm() - Initializes the ALSA PCM device. * @pcm: Pointer to the ALSA PCM device to initialize. * - * This function sets up the PCM operations and preallocates pages for the - * PCM buffer. + * This function sets up the PCM operations, adds ALSA controls for routing + * and sample rate, and preallocates pages for the PCM buffer. * * Return: 0 on success, or a negative error code on failure. */ @@ -94,6 +104,16 @@ void process_playback_routing_us144mkii(struct tascam_c= ard *tascam, const u8 *src_buffer, u8 *dst_buffer, size_t frames); =20 +/** + * process_capture_routing_us144mkii() - Apply capture routing matrix + * @tascam: The driver instance. + * @decoded_block: Buffer containing 4 channels of S32LE decoded audio. + * @routed_block: Buffer to be filled for ALSA. + */ +void process_capture_routing_us144mkii(struct tascam_card *tascam, + const s32 *decoded_block, + s32 *routed_block); + /** * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. * @substream: The ALSA PCM substream. @@ -131,4 +151,15 @@ int tascam_pcm_hw_free(struct snd_pcm_substream *subst= ream); */ int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd); =20 +/** + * tascam_capture_work_handler() - Deferred work for processing capture da= ta. + * @work: the work_struct instance + * + * This function runs in a kernel thread context, not an IRQ context. It r= eads + * raw data from the capture ring buffer, decodes it, applies routing, and + * copies the final audio data into the ALSA capture ring buffer. This off= loads + * the CPU-intensive decoding from the time-sensitive URB completion handl= ers. + */ +void tascam_capture_work_handler(struct work_struct *work); + #endif /* __US144MKII_PCM_H */ --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f49.google.com (mail-ej1-f49.google.com [209.85.218.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2325627875C; Tue, 12 Aug 2025 12:56:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003407; cv=none; b=dcUl+tRSj204Ro6Vn9fLIl/1lWcugFZOc7uXn3aVv5tCAFH3IAnyUAlCr5t5M5fYwLqnjtETIrQp+unZlYyiYLdGd5PnF1n7WiHDGMhPDeiEo+/USYCY4j1pwQj5QfVFScQGAQpeq7cC3TP/XT7MRF+9mZGq5u4ZfIiKagYyJzI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003407; c=relaxed/simple; bh=9HYyS/+VV0aGFLTQ7jU7voH9P7iEGVmf+CqGSSrPOug=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=SwBOZSo6R+EP+UtOz2YeBAridsAMoLwDtAaOQyvM4U5y1pWxe0Sqb7V0PCSktn8Ae4Qy9+nVyNvm05tTh7nekh2/+MlU+wqU8QPUNzwGhHVI+K0yylDYWY7ke3eyEZh8zgu3mWay2kjUiWCKkQhONPo45j8Ga5c/OtxBSBftiNo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=IBHy8ODx; arc=none smtp.client-ip=209.85.218.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="IBHy8ODx" Received: by mail-ej1-f49.google.com with SMTP id a640c23a62f3a-af968aa2de4so979984366b.1; Tue, 12 Aug 2025 05:56:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003402; x=1755608202; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=zLQpLAfk/bqkKEJyPTfh33jg/C2ldUG2P0qmmtpfjr8=; b=IBHy8ODxEqwSdz1KzoqtPZbUEQ1N4y+HL7iB9By4Q+oWI5TCwvSE588fK35TcycpNu vqSJcI+L4sSgKHRzg5pZCGRQlKDXKMVuy2lR6FaBIqGpu6EHqb1makxAVz42yqhPGcXf zu0NDNITCBcDvG6N84CLrtesOVm1yJTK+EilkLzqFQB2cNhvObzlBK3/wBpPyU7jMIW+ aK9XP4m+/IJbHQzxVTeGp9MOnNl0fwn775I6n/1Onpey5SL1jEYxNzbKOALg8giGM1zy V5AgsFCzGIvoQc4i31LZKOUgKDXRlP0SbvtE954JLqPKykFAr2ErFPIsnQVacDUxvmBv RCrA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003402; x=1755608202; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=zLQpLAfk/bqkKEJyPTfh33jg/C2ldUG2P0qmmtpfjr8=; b=suDLh4OO5tMsuJYUVKmv1kL0i4Hm6CFYBsXvcc38/tUFQ0RQg1y9C84kyNXpNibjVW /YLIcf81kBZxMS8NYN34QhH9l+1PJd7BioYuY7hjDQ1Z2alxgH4nx9MBkjCQfNF2w3In 17Nr/6nABFmeGb41LPC/yaoGu3Ot55/RHAdCprj2PYwmkRrQ4NuuiRQKGp9TDu2HGNmm YtULsMfC/cRLqc0W1VImoo8ggKKCgbzNPXcbIUrt//ZtwWt5BtbX7WX4mJtw9vEnehZu TN74O6QUy0+DtsKbj8KlPnsFds1PB9zGn1oRuQdBZfr+gXS8dMCabsQRjVlfEcFA5fk9 hXpw== X-Forwarded-Encrypted: i=1; AJvYcCW3BDXtigysRk5w9gqzgJBRBqIkVjPnIPJI3YzgvzKV5Qyv6GtZrx10h8DFTj+DTuSChs1fJ6bjxpTXbA==@vger.kernel.org X-Gm-Message-State: AOJu0Yx1/wv9Y/Z3W70pyM8RUJ7VG4Hrudd5BPfHwE2vC1RENkIIjDdV gIlQErr+l1wg5EQNRIgVJnrN7NMg6HVocNbmSnKD1lQ8jCqHnRFAn1NZ X-Gm-Gg: ASbGnctYtN84hpV5BmmXfesnPEINqSqUkLivjt+w0+UdqKN7sVNcQEW/CDM+B58Yvkq gbiHVT+LCR6vz9NKfeS0CC8sDUJCmrsJHzkq8FGkKOVN3KDre0BgoR1TnsHx95QnRsIA04P4snu JTyxb3CGaPY4XgoWkLg4sugy0Do/Vb9rKpMvvlWFFeam6oPkKz6b3E/WPyO9BSrqxg/h3VkcNiL 7/moVrKjwZbqtSbPFWQFIPX6Hihacu807eIaH/Xr8Wrpcv+uuHfe7V5y4DPndzW/WnRbxeMfl/h pxpveJF31nbOtx0jiX65kDZsKG79AbKLu+uUcWvMyg7hXk55c1H0gmOx4Z+4QnjNx9QqbySdSLJ XtvfZ4ybtpRnK4CkY3mV/WDueubaZ5TRsC+mmq3GAf6PAeT6LnzK6MH4blt0Rwqt69W4sYIX6Jb m84HeYIg== X-Google-Smtp-Source: AGHT+IGO5oP9MzFueJnegLJo/RLWOv8jIHLg76L+d6Ees1NCtGanMz45K/9Um76iWfqXsjqk3uZvwg== X-Received: by 2002:a17:907:f496:b0:ae0:dfa5:3520 with SMTP id a640c23a62f3a-af9c65186c8mr1416606766b.31.1755003401956; Tue, 12 Aug 2025 05:56:41 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:41 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 3/7] ALSA: usb-audio: us144mkii: Implement audio playback and feedback Date: Tue, 12 Aug 2025 14:56:26 +0200 Message-Id: <20250812125633.79270-7-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch implements the full audio playback data path. It introduces the allocation, submission, and completion handling for isochronous playback and feedback URBs. The feedback URB completion handler is the core of the driver's clocking mechanism. It reads the number of samples consumed by the device and uses a pattern-based algorithm to adjust the size of outgoing playback packets. This keeps the host and device synchronized and prevents xruns. The patch also adds the necessary vendor and UAC control messages to configure the device's sample rate. The PCM trigger is updated to start and stop the playback and feedback URBs. Basic suspend and resume handlers are included to manage stream state across power cycles. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 171 ++++++++++++- sound/usb/usx2y/us144mkii.h | 178 +++++++++++-- sound/usb/usx2y/us144mkii_capture.c | 1 + sound/usb/usx2y/us144mkii_pcm.c | 262 ++++++++++++++++++-- sound/usb/usx2y/us144mkii_pcm.h | 64 ++++- sound/usb/usx2y/us144mkii_playback.c | 357 ++++++++++++++++++++++++++- 6 files changed, 993 insertions(+), 40 deletions(-) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 9b14390efb56..3a147fba873e 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -35,6 +35,110 @@ static void tascam_disconnect(struct usb_interface *int= f); static int tascam_suspend(struct usb_interface *intf, pm_message_t message= ); static int tascam_resume(struct usb_interface *intf); =20 +void tascam_free_urbs(struct tascam_card *tascam) +{ + int i; + + usb_kill_anchored_urbs(&tascam->playback_anchor); + for (i =3D 0; i < NUM_PLAYBACK_URBS; i++) { + if (tascam->playback_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->playback_urb_alloc_size, + tascam->playback_urbs[i]->transfer_buffer, + tascam->playback_urbs[i]->transfer_dma); + usb_free_urb(tascam->playback_urbs[i]); + tascam->playback_urbs[i] =3D NULL; + } + } + + usb_kill_anchored_urbs(&tascam->feedback_anchor); + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + if (tascam->feedback_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->feedback_urb_alloc_size, + tascam->feedback_urbs[i]->transfer_buffer, + tascam->feedback_urbs[i]->transfer_dma); + usb_free_urb(tascam->feedback_urbs[i]); + tascam->feedback_urbs[i] =3D NULL; + } + } +} + +int tascam_alloc_urbs(struct tascam_card *tascam) +{ + int i; + size_t max_packet_size; + + max_packet_size =3D ((96000 / 8000) + 2) * BYTES_PER_FRAME; + tascam->playback_urb_alloc_size =3D + max_packet_size * PLAYBACK_URB_PACKETS; + + for (i =3D 0; i < NUM_PLAYBACK_URBS; i++) { + struct urb *urb =3D + usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL); + + if (!urb) + goto error; + tascam->playback_urbs[i] =3D urb; + + urb->transfer_buffer =3D usb_alloc_coherent( + tascam->dev, tascam->playback_urb_alloc_size, + GFP_KERNEL, &urb->transfer_dma); + if (!urb->transfer_buffer) + goto error; + + urb->dev =3D tascam->dev; + urb->pipe =3D usb_sndisocpipe(tascam->dev, EP_AUDIO_OUT); + urb->transfer_flags =3D URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->interval =3D 1; + urb->context =3D tascam; + urb->complete =3D playback_urb_complete; + } + + tascam->feedback_urb_alloc_size =3D + FEEDBACK_PACKET_SIZE * FEEDBACK_URB_PACKETS; + + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + struct urb *f_urb =3D + usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL); + + if (!f_urb) + goto error; + tascam->feedback_urbs[i] =3D f_urb; + + f_urb->transfer_buffer =3D usb_alloc_coherent( + tascam->dev, tascam->feedback_urb_alloc_size, + GFP_KERNEL, &f_urb->transfer_dma); + if (!f_urb->transfer_buffer) + goto error; + + f_urb->dev =3D tascam->dev; + f_urb->pipe =3D + usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK); + f_urb->transfer_flags =3D URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + f_urb->interval =3D 4; + f_urb->context =3D tascam; + f_urb->complete =3D feedback_urb_complete; + } + + return 0; + +error: + dev_err(tascam->card->dev, "Failed to allocate URBs\n"); + tascam_free_urbs(tascam); + return -ENOMEM; +} + +void tascam_stop_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, stop_work); + + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + atomic_set(&tascam->active_urbs, 0); +} + /** * tascam_card_private_free() - Frees private data associated with the sou= nd * card. @@ -64,6 +168,7 @@ static void tascam_card_private_free(struct snd_card *ca= rd) * - Performing a vendor-specific handshake with the device. * - Setting alternate settings for USB interfaces. * - Creating and registering the ALSA sound card and PCM device. + * - Allocating and initializing URBs for audio transfers. * * Return: 0 on success, or a negative error code on failure. */ @@ -153,6 +258,11 @@ static int tascam_probe(struct usb_interface *intf, tascam->iface0 =3D intf; =20 spin_lock_init(&tascam->lock); + init_usb_anchor(&tascam->playback_anchor); + init_usb_anchor(&tascam->feedback_anchor); + + INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); + INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); =20 err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); if (err < 0) @@ -164,6 +274,10 @@ static int tascam_probe(struct usb_interface *intf, if (err < 0) goto free_card; =20 + err =3D tascam_alloc_urbs(tascam); + if (err < 0) + goto free_card; + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { strscpy(card->shortname, "TASCAM US-144", @@ -189,6 +303,7 @@ static int tascam_probe(struct usb_interface *intf, return 0; =20 free_card: + tascam_free_urbs(tascam); snd_card_free(card); return err; } @@ -198,7 +313,8 @@ static int tascam_probe(struct usb_interface *intf, * @intf: The USB interface being disconnected. * * This function is called when the device is disconnected from the system. - * It cleans up all allocated resources by freeing the sound card. + * It cleans up all allocated resources by freeing the sound card, which in + * turn triggers freeing of URBs and other resources. */ static void tascam_disconnect(struct usb_interface *intf) { @@ -209,6 +325,9 @@ static void tascam_disconnect(struct usb_interface *int= f) =20 if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { snd_card_disconnect(tascam->card); + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->stop_pcm_work); + tascam_free_urbs(tascam); snd_card_free(tascam->card); dev_idx--; } @@ -219,12 +338,25 @@ static void tascam_disconnect(struct usb_interface *i= ntf) * @intf: The USB interface being suspended. * @message: Power management message. * - * This function is a stub for handling device suspension. + * This function is called when the device is suspended. It stops all acti= ve + * streams and kills all URBs. * * Return: 0 on success. */ static int tascam_suspend(struct usb_interface *intf, pm_message_t message) { + struct tascam_card *tascam =3D usb_get_intfdata(intf); + + if (!tascam) + return 0; + + snd_pcm_suspend_all(tascam->pcm); + + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->stop_pcm_work); + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + return 0; } =20 @@ -232,12 +364,43 @@ static int tascam_suspend(struct usb_interface *intf,= pm_message_t message) * tascam_resume() - Handles device resumption from suspend. * @intf: The USB interface being resumed. * - * This function is a stub for handling device resumption. + * This function is called when the device resumes from suspend. It + * re-establishes the active USB interface settings and re-configures the = sample + * rate if it was previously active. * - * Return: 0 on success. + * Return: 0 on success, or a negative error code on failure. */ static int tascam_resume(struct usb_interface *intf) { + struct tascam_card *tascam =3D usb_get_intfdata(intf); + int err; + + if (!tascam) + return 0; + + dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); + + /* Re-establish the active USB interface settings. */ + err =3D usb_set_interface(tascam->dev, 0, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 0: %d\n", + err); + return err; + } + err =3D usb_set_interface(tascam->dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 1: %d\n", + err); + return err; + } + + /* Re-configure the sample rate if one was previously active */ + if (tascam->current_rate > 0) + us144mkii_configure_device_for_rate(tascam, + tascam->current_rate); + return 0; } =20 diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index cbfcb062532f..257ab22dafc1 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -5,6 +5,7 @@ #define __US144MKII_H =20 #include +#include #include #include #include @@ -16,21 +17,87 @@ #define USB_PID_TASCAM_US144 0x800f #define USB_PID_TASCAM_US144MKII 0x8020 =20 -/* --- Audio Format Configuration --- */ -#define BYTES_PER_SAMPLE 3 -#define NUM_CHANNELS 4 -#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) +/* --- USB Endpoints (Alternate Setting 1) --- */ +#define EP_PLAYBACK_FEEDBACK 0x81 +#define EP_AUDIO_OUT 0x02 =20 /* --- USB Control Message Protocol --- */ +#define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) #define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) -#define VENDOR_REQ_MODE_CONTROL 0x49 -#define MODE_VAL_HANDSHAKE_READ 0x0000 +#define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVIC= E) + +enum uac_request { + UAC_SET_CUR =3D 0x01, +}; + +enum uac_control_selector { + UAC_SAMPLING_FREQ_CONTROL =3D 0x0100, +}; + +enum tascam_vendor_request { + VENDOR_REQ_REGISTER_WRITE =3D 0x41, + VENDOR_REQ_MODE_CONTROL =3D 0x49, +}; + +enum tascam_mode_value { + MODE_VAL_HANDSHAKE_READ =3D 0x0000, + MODE_VAL_CONFIG =3D 0x0010, + MODE_VAL_STREAM_START =3D 0x0030, +}; + +enum tascam_register { + REG_ADDR_UNKNOWN_0D =3D 0x0d04, + REG_ADDR_UNKNOWN_0E =3D 0x0e00, + REG_ADDR_UNKNOWN_0F =3D 0x0f00, + REG_ADDR_RATE_44100 =3D 0x1000, + REG_ADDR_RATE_48000 =3D 0x1002, + REG_ADDR_RATE_88200 =3D 0x1008, + REG_ADDR_RATE_96000 =3D 0x100a, + REG_ADDR_UNKNOWN_11 =3D 0x110b, +}; + +#define REG_VAL_ENABLE 0x0101 + +/* --- URB Configuration --- */ +#define NUM_PLAYBACK_URBS 4 +#define PLAYBACK_URB_PACKETS 8 +#define NUM_FEEDBACK_URBS 4 +#define FEEDBACK_URB_PACKETS 1 +#define FEEDBACK_PACKET_SIZE 3 #define USB_CTRL_TIMEOUT_MS 1000 +#define FEEDBACK_SYNC_LOSS_THRESHOLD 41 + +/* --- Audio Format Configuration --- */ +#define BYTES_PER_SAMPLE 3 +#define NUM_CHANNELS 4 +#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) +#define FEEDBACK_ACCUMULATOR_SIZE 128 =20 struct tascam_card; =20 #include "us144mkii_pcm.h" =20 +/** + * struct us144mkii_frame_pattern_observer - State for dynamic feedback + * patterns. + * @sample_rate_khz: The current sample rate in kHz. + * @base_feedback_value: The nominal feedback value for the current rate. + * @feedback_offset: An offset to align the feedback value range. + * @full_frame_patterns: A 2D array of pre-calculated packet size patterns. + * @current_index: The current index into the pattern array. + * @previous_index: The previous index, used for state tracking. + * @sync_locked: A flag indicating if the pattern has locked to the stream. + */ +struct us144mkii_frame_pattern_observer { + unsigned int sample_rate_khz; + unsigned int base_feedback_value; + int feedback_offset; + unsigned int full_frame_patterns[5][8]; + unsigned int current_index; + unsigned int previous_index; + bool sync_locked; +}; + /** * struct tascam_card - Main driver data structure for the TASCAM US-144MK= II. * @dev: Pointer to the USB device. @@ -38,38 +105,121 @@ struct tascam_card; * @iface1: Pointer to USB interface 1 (MIDI). * @card: Pointer to the ALSA sound card instance. * @pcm: Pointer to the ALSA PCM device. + * * @playback_substream: Pointer to the active playback PCM substream. - * @capture_substream: Pointer to the active capture PCM substream. + * @playback_urbs: Array of URBs for playback. + * @playback_urb_alloc_size: Size of allocated buffer for each playback UR= B. + * @feedback_urbs: Array of URBs for feedback. + * @feedback_urb_alloc_size: Size of allocated buffer for each feedback UR= B. * @playback_active: Atomic flag indicating if playback is active. - * @capture_active: Atomic flag indicating if capture is active. + * @playback_frames_consumed: Total frames consumed by playback. * @driver_playback_pos: Current position in the ALSA playback buffer (fra= mes). + * @last_period_pos: Last reported period position for playback. + * + * @capture_substream: Pointer to the active capture PCM substream. + * @capture_active: Atomic flag indicating if capture is active. * @driver_capture_pos: Current position in the ALSA capture buffer (frame= s). - * @playback_frames_consumed: Total frames consumed by playback. * @capture_frames_processed: Total frames processed for capture. - * @current_rate: Currently configured sample rate of the device. + * @last_capture_period_pos: Last reported period position for capture. + * + * @stop_work: Work struct for deferred stream stopping. + * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. + * xrun). + * * @lock: Main spinlock for protecting shared driver state. + * @active_urbs: Atomic counter for active URBs. + * @current_rate: Currently configured sample rate of the device. + * + * @feedback_accumulator_pattern: Stores the calculated frames per packet = for + * feedback. + * @feedback_pattern_out_idx: Read index for feedback_accumulator_pattern. + * @feedback_pattern_in_idx: Write index for feedback_accumulator_pattern. + * @feedback_synced: Flag indicating if feedback is synced. + * @feedback_consecutive_errors: Counter for consecutive feedback errors. + * @feedback_urb_skip_count: Number of feedback URBs to skip initially for + * stabilization. + * @fpo: Holds the state for the dynamic feedback pattern generation. + * + * @playback_anchor: USB anchor for playback URBs. + * @feedback_anchor: USB anchor for feedback URBs. */ struct tascam_card { + /* --- Core device pointers --- */ struct usb_device *dev; struct usb_interface *iface0; struct usb_interface *iface1; struct snd_card *card; struct snd_pcm *pcm; =20 + /* --- PCM Substreams --- */ struct snd_pcm_substream *playback_substream; struct snd_pcm_substream *capture_substream; =20 + /* --- URBs and Anchors --- */ + struct urb *playback_urbs[NUM_PLAYBACK_URBS]; + size_t playback_urb_alloc_size; + struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; + size_t feedback_urb_alloc_size; + struct usb_anchor playback_anchor; + struct usb_anchor feedback_anchor; + + /* --- Stream State --- */ + spinlock_t lock; atomic_t playback_active; atomic_t capture_active; + atomic_t active_urbs; + int current_rate; =20 + /* --- Playback State --- */ + u64 playback_frames_consumed; snd_pcm_uframes_t driver_playback_pos; - snd_pcm_uframes_t driver_capture_pos; + u64 last_period_pos; =20 - u64 playback_frames_consumed; + /* --- Capture State --- */ u64 capture_frames_processed; + snd_pcm_uframes_t driver_capture_pos; + u64 last_capture_period_pos; =20 - int current_rate; - spinlock_t lock; + /* --- Feedback Sync State --- */ + unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; + unsigned int feedback_pattern_out_idx; + unsigned int feedback_pattern_in_idx; + bool feedback_synced; + unsigned int feedback_consecutive_errors; + unsigned int feedback_urb_skip_count; + struct us144mkii_frame_pattern_observer fpo; + + /* --- Workqueues --- */ + struct work_struct stop_work; + struct work_struct stop_pcm_work; }; =20 +/** + * tascam_free_urbs() - Free all allocated URBs and associated buffers. + * @tascam: the tascam_card instance + * + * This function kills, unlinks, and frees all playback and feedback URBs, + * along with their transfer buffers. + */ +void tascam_free_urbs(struct tascam_card *tascam); + +/** + * tascam_alloc_urbs() - Allocate all URBs and associated buffers. + * @tascam: the tascam_card instance + * + * This function allocates and initializes all URBs for playback and feedb= ack. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_alloc_urbs(struct tascam_card *tascam); + +/** + * tascam_stop_work_handler() - Work handler to stop all active streams. + * @work: Pointer to the work_struct. + * + * This function is scheduled to stop all active URBs (playback, feedback) + * and reset the active_urbs counter. + */ +void tascam_stop_work_handler(struct work_struct *work); + #endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mki= i_capture.c index f2ce90743d22..22b8faa9bbe8 100644 --- a/sound/usb/usx2y/us144mkii_capture.c +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -55,6 +55,7 @@ static int tascam_capture_prepare(struct snd_pcm_substrea= m *substream) =20 tascam->driver_capture_pos =3D 0; tascam->capture_frames_processed =3D 0; + tascam->last_capture_period_pos =3D 0; =20 return 0; } diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c index be6f0fa4750b..7d1bbd547504 100644 --- a/sound/usb/usx2y/us144mkii_pcm.c +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -3,6 +3,37 @@ =20 #include "us144mkii.h" =20 +/** + * fpoInitPattern() - Generates a packet distribution pattern. + * @size: The number of elements in the pattern array (e.g., 8). + * @pattern_array: Pointer to the array to be populated. + * @initial_value: The base value to initialize each element with. + * @target_sum: The desired sum of all elements in the final array. + * + * This function initializes an array with a base value and then iterative= ly + * adjusts the elements to match a target sum, distributing the difference + * as evenly as possible. + */ +static void fpo_init_pattern(unsigned int size, unsigned int *pattern_arra= y, + unsigned int initial_value, int target_sum) +{ + int diff, i; + + if (!size) + return; + + for (i =3D 0; i < size; ++i) + pattern_array[i] =3D initial_value; + + diff =3D target_sum - (size * initial_value); + for (i =3D 0; i < abs(diff); ++i) { + if (diff > 0) + pattern_array[i]++; + else + pattern_array[i]--; + } +} + const struct snd_pcm_hardware tascam_pcm_hw =3D { .info =3D (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | @@ -21,9 +52,152 @@ const struct snd_pcm_hardware tascam_pcm_hw =3D { .periods_max =3D 1024, }; =20 +void process_playback_routing_us144mkii(struct tascam_card *tascam, + const u8 *src_buffer, u8 *dst_buffer, + size_t frames) +{ + /* This is a stub. Routing will be added in a later commit. */ + if (src_buffer !=3D dst_buffer) + memcpy(dst_buffer, src_buffer, frames * BYTES_PER_FRAME); +} + +int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te) +{ + struct usb_device *dev =3D tascam->dev; + + u8 *rate_payload_buf __free(kfree) =3D NULL; + u16 rate_vendor_wValue; + int err =3D 0; + const u8 *current_payload_src; + + static const u8 payload_44100[] =3D { 0x44, 0xac, 0x00 }; + static const u8 payload_48000[] =3D { 0x80, 0xbb, 0x00 }; + static const u8 payload_88200[] =3D { 0x88, 0x58, 0x01 }; + static const u8 payload_96000[] =3D { 0x00, 0x77, 0x01 }; + + switch (rate) { + case 44100: + current_payload_src =3D payload_44100; + rate_vendor_wValue =3D REG_ADDR_RATE_44100; + break; + case 48000: + current_payload_src =3D payload_48000; + rate_vendor_wValue =3D REG_ADDR_RATE_48000; + break; + case 88200: + current_payload_src =3D payload_88200; + rate_vendor_wValue =3D REG_ADDR_RATE_88200; + break; + case 96000: + current_payload_src =3D payload_96000; + rate_vendor_wValue =3D REG_ADDR_RATE_96000; + break; + default: + dev_err(&dev->dev, + "Unsupported sample rate %d for configuration\n", rate); + return -EINVAL; + } + + rate_payload_buf =3D kmemdup(current_payload_src, 3, GFP_KERNEL); + if (!rate_payload_buf) + return -ENOMEM; + + dev_info(&dev->dev, "Configuring device for %d Hz\n", rate); + + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV, + MODE_VAL_CONFIG, 0x0000, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, + EP_AUDIO_OUT, rate_payload_buf, 3, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0D, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0E, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_0F, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + rate_vendor_wValue, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV, + REG_ADDR_UNKNOWN_11, REG_VAL_ENABLE, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV, + MODE_VAL_STREAM_START, 0x0000, NULL, 0, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; + + kfree(rate_payload_buf); + return 0; + +fail: + dev_err(&dev->dev, + "Device configuration failed at rate %d with error %d\n", rate, + err); + kfree(rate_payload_buf); + return err; +} + int tascam_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { + struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + int err; + unsigned int rate =3D params_rate(params); + + if (substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK) { + tascam->fpo.sample_rate_khz =3D rate / 1000; + tascam->fpo.base_feedback_value =3D tascam->fpo.sample_rate_khz; + tascam->fpo.feedback_offset =3D 2; + tascam->fpo.current_index =3D 0; + tascam->fpo.previous_index =3D 0; + tascam->fpo.sync_locked =3D false; + + unsigned int initial_value =3D tascam->fpo.sample_rate_khz / 8; + + for (int i =3D 0; i < 5; i++) { + int target_sum =3D tascam->fpo.sample_rate_khz - + tascam->fpo.feedback_offset + i; + fpo_init_pattern(8, tascam->fpo.full_frame_patterns[i], + initial_value, target_sum); + } + } + + if (tascam->current_rate !=3D rate) { + err =3D us144mkii_configure_device_for_rate(tascam, rate); + if (err < 0) { + tascam->current_rate =3D 0; + return err; + } + tascam->current_rate =3D rate; + } + return 0; } =20 @@ -36,29 +210,81 @@ int tascam_pcm_trigger(struct snd_pcm_substream *subst= ream, int cmd) { struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); int err =3D 0; + int i; + bool do_start =3D false; + bool do_stop =3D false; =20 - guard(spinlock_irqsave)(&tascam->lock); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - if (!atomic_read(&tascam->playback_active)) { - atomic_set(&tascam->playback_active, 1); - atomic_set(&tascam->capture_active, 1); + scoped_guard(spinlock_irqsave, &tascam->lock) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (!atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 1); + atomic_set(&tascam->capture_active, 1); + do_start =3D true; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (atomic_read(&tascam->playback_active)) { + atomic_set(&tascam->playback_active, 0); + atomic_set(&tascam->capture_active, 0); + do_stop =3D true; + } + break; + default: + err =3D -EINVAL; + break; } - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (atomic_read(&tascam->playback_active)) { - atomic_set(&tascam->playback_active, 0); - atomic_set(&tascam->capture_active, 0); + } + + if (do_start) { + if (atomic_read(&tascam->active_urbs) > 0) { + dev_WARN(tascam->card->dev, + "Cannot start, URBs still active.\n"); + return -EAGAIN; } - break; - default: - err =3D -EINVAL; - break; + + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + usb_get_urb(tascam->feedback_urbs[i]); + usb_anchor_urb(tascam->feedback_urbs[i], + &tascam->feedback_anchor); + err =3D usb_submit_urb(tascam->feedback_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->feedback_urbs[i]); + usb_put_urb(tascam->feedback_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + for (i =3D 0; i < NUM_PLAYBACK_URBS; i++) { + usb_get_urb(tascam->playback_urbs[i]); + usb_anchor_urb(tascam->playback_urbs[i], + &tascam->playback_anchor); + err =3D usb_submit_urb(tascam->playback_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->playback_urbs[i]); + usb_put_urb(tascam->playback_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } + + return 0; +start_rollback: + dev_err(tascam->card->dev, + "Failed to submit URBs to start stream: %d\n", err); + do_stop =3D true; } =20 + if (do_stop) + schedule_work(&tascam->stop_work); + return err; } =20 diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pc= m.h index bb8b51f9b6fb..6ca00c3ce53d 100644 --- a/sound/usb/usx2y/us144mkii_pcm.h +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -28,6 +28,38 @@ extern const struct snd_pcm_ops tascam_playback_ops; */ extern const struct snd_pcm_ops tascam_capture_ops; =20 +/** + * playback_urb_complete() - Completion handler for playback isochronous U= RBs. + * @urb: the completed URB + * + * This function runs in interrupt context. It calculates the number of by= tes + * to send in the next set of packets based on the feedback-driven clock, + * copies the audio data from the ALSA ring buffer, and resubmits the URB. + */ +void playback_urb_complete(struct urb *urb); + +/** + * feedback_urb_complete() - Completion handler for feedback isochronous U= RBs. + * @urb: the completed URB + * + * This is the master clock for the driver. It runs in interrupt context. + * It reads the feedback value from the device, which indicates how many + * samples the device has consumed. This information is used to adjust the + * playback rate and to advance the capture stream pointer, keeping both + * streams in sync. It then calls snd_pcm_period_elapsed if necessary and + * resubmits itself. + */ +void feedback_urb_complete(struct urb *urb); + +/** + * tascam_stop_pcm_work_handler() - Work handler to stop PCM streams. + * @work: Pointer to the work_struct. + * + * This function is scheduled to stop PCM streams (playback and capture) + * from a workqueue context, avoiding blocking operations in interrupt con= text. + */ +void tascam_stop_pcm_work_handler(struct work_struct *work); + /** * tascam_init_pcm() - Initializes the ALSA PCM device. * @pcm: Pointer to the ALSA PCM device to initialize. @@ -39,14 +71,40 @@ extern const struct snd_pcm_ops tascam_capture_ops; */ int tascam_init_pcm(struct snd_pcm *pcm); =20 +/** + * us144mkii_configure_device_for_rate() - Set sample rate via USB control= msgs + * @tascam: the tascam_card instance + * @rate: the target sample rate (e.g., 44100, 96000) + * + * This function sends a sequence of vendor-specific and UAC control messa= ges + * to configure the device hardware for the specified sample rate. + * + * Return: 0 on success, or a negative error code on failure. + */ +int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te); + +/** + * process_playback_routing_us144mkii() - Apply playback routing matrix + * @tascam: The driver instance. + * @src_buffer: Buffer containing 4 channels of S24_3LE audio from ALSA. + * @dst_buffer: Buffer to be filled for the USB device. + * @frames: Number of frames to process. + */ +void process_playback_routing_us144mkii(struct tascam_card *tascam, + const u8 *src_buffer, u8 *dst_buffer, + size_t frames); + /** * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. * @substream: The ALSA PCM substream. * @params: The hardware parameters to apply. * - * This function is a stub for handling hardware parameter configuration. + * This function allocates pages for the PCM buffer and, for playback stre= ams, + * selects the appropriate feedback patterns based on the requested sample= rate. + * It also configures the device hardware for the selected sample rate if = it + * has changed. * - * Return: 0 on success. + * Return: 0 on success, or a negative error code on failure. */ int tascam_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); @@ -67,7 +125,7 @@ int tascam_pcm_hw_free(struct snd_pcm_substream *substre= am); * @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START). * * This function handles starting and stopping of playback and capture str= eams - * by setting atomic flags. + * by submitting or killing the associated URBs. * * Return: 0 on success, or a negative error code on failure. */ diff --git a/sound/usb/usx2y/us144mkii_playback.c b/sound/usb/usx2y/us144mk= ii_playback.c index ac582a534123..17d8a43cc735 100644 --- a/sound/usb/usx2y/us144mkii_playback.c +++ b/sound/usb/usx2y/us144mkii_playback.c @@ -45,16 +45,65 @@ static int tascam_playback_close(struct snd_pcm_substre= am *substream) * tascam_playback_prepare() - Prepares the PCM playback substream for use. * @substream: The ALSA PCM substream to prepare. * - * This function initializes playback-related counters. + * This function initializes playback-related counters and flags, and conf= igures + * the playback URBs with appropriate packet sizes based on the nominal fr= ame + * rate. * * Return: 0 on success. */ static int tascam_playback_prepare(struct snd_pcm_substream *substream) { struct tascam_card *tascam =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + int i, u; + size_t nominal_frames_per_packet, nominal_bytes_per_packet; + size_t total_bytes_in_urb; =20 tascam->driver_playback_pos =3D 0; tascam->playback_frames_consumed =3D 0; + tascam->last_period_pos =3D 0; + tascam->feedback_pattern_in_idx =3D 0; + tascam->feedback_pattern_out_idx =3D 0; + tascam->feedback_synced =3D false; + tascam->feedback_consecutive_errors =3D 0; + tascam->feedback_urb_skip_count =3D NUM_FEEDBACK_URBS; + + nominal_frames_per_packet =3D runtime->rate / 8000; + for (i =3D 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) + tascam->feedback_accumulator_pattern[i] =3D + nominal_frames_per_packet; + + for (i =3D 0; i < NUM_FEEDBACK_URBS; i++) { + struct urb *f_urb =3D tascam->feedback_urbs[i]; + int j; + + f_urb->number_of_packets =3D FEEDBACK_URB_PACKETS; + f_urb->transfer_buffer_length =3D + FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE; + for (j =3D 0; j < FEEDBACK_URB_PACKETS; j++) { + f_urb->iso_frame_desc[j].offset =3D + j * FEEDBACK_PACKET_SIZE; + f_urb->iso_frame_desc[j].length =3D FEEDBACK_PACKET_SIZE; + } + } + + nominal_bytes_per_packet =3D nominal_frames_per_packet * BYTES_PER_FRAME; + total_bytes_in_urb =3D nominal_bytes_per_packet * PLAYBACK_URB_PACKETS; + + for (u =3D 0; u < NUM_PLAYBACK_URBS; u++) { + struct urb *urb =3D tascam->playback_urbs[u]; + + memset(urb->transfer_buffer, 0, + tascam->playback_urb_alloc_size); + urb->transfer_buffer_length =3D total_bytes_in_urb; + urb->number_of_packets =3D PLAYBACK_URB_PACKETS; + for (i =3D 0; i < PLAYBACK_URB_PACKETS; i++) { + urb->iso_frame_desc[i].offset =3D + i * nominal_bytes_per_packet; + urb->iso_frame_desc[i].length =3D + nominal_bytes_per_packet; + } + } =20 return 0; } @@ -106,3 +155,309 @@ const struct snd_pcm_ops tascam_playback_ops =3D { .trigger =3D tascam_pcm_trigger, .pointer =3D tascam_playback_pointer, }; + +void playback_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + size_t total_bytes_for_urb =3D 0; + snd_pcm_uframes_t offset_frames; + snd_pcm_uframes_t frames_to_copy; + int ret, i; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -ENODEV) + dev_err_ratelimited(tascam->card->dev, + "Playback URB failed: %d\n", + urb->status); + goto out; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + goto out; + + substream =3D tascam->playback_substream; + if (!substream || !substream->runtime) + goto out; + runtime =3D substream->runtime; + + { + guard(spinlock_irqsave)(&tascam->lock); + + for (i =3D 0; i < urb->number_of_packets; i++) { + unsigned int frames_for_packet; + size_t bytes_for_packet; + + if (tascam->feedback_synced) { + frames_for_packet =3D + tascam->feedback_accumulator_pattern + [tascam->feedback_pattern_out_idx]; + tascam->feedback_pattern_out_idx =3D + (tascam->feedback_pattern_out_idx + 1) % + FEEDBACK_ACCUMULATOR_SIZE; + } else { + frames_for_packet =3D runtime->rate / 8000; + } + bytes_for_packet =3D frames_for_packet * BYTES_PER_FRAME; + + urb->iso_frame_desc[i].offset =3D total_bytes_for_urb; + urb->iso_frame_desc[i].length =3D bytes_for_packet; + total_bytes_for_urb +=3D bytes_for_packet; + } + urb->transfer_buffer_length =3D total_bytes_for_urb; + + offset_frames =3D tascam->driver_playback_pos; + frames_to_copy =3D bytes_to_frames(runtime, total_bytes_for_urb); + tascam->driver_playback_pos =3D + (offset_frames + frames_to_copy) % runtime->buffer_size; + } + + if (total_bytes_for_urb > 0) { + u8 *dst_buf =3D urb->transfer_buffer; + + /* Handle ring buffer wrap-around */ + if (offset_frames + frames_to_copy > runtime->buffer_size) { + size_t first_chunk_bytes =3D frames_to_bytes( + runtime, runtime->buffer_size - offset_frames); + size_t second_chunk_bytes =3D + total_bytes_for_urb - first_chunk_bytes; + + memcpy(dst_buf, + runtime->dma_area + + frames_to_bytes(runtime, offset_frames), + first_chunk_bytes); + memcpy(dst_buf + first_chunk_bytes, runtime->dma_area, + second_chunk_bytes); + } else { + memcpy(dst_buf, + runtime->dma_area + + frames_to_bytes(runtime, offset_frames), + total_bytes_for_urb); + } + + process_playback_routing_us144mkii(tascam, dst_buf, dst_buf, + frames_to_copy); + } + + urb->dev =3D tascam->dev; + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->playback_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit playback URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } +out: + usb_put_urb(urb); +} + +void feedback_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + struct snd_pcm_substream *playback_ss, *capture_ss; + struct snd_pcm_runtime *playback_rt, *capture_rt; + u64 total_frames_in_urb =3D 0; + int ret, p; + unsigned int old_in_idx, new_in_idx; + bool playback_period_elapsed =3D false; + bool capture_period_elapsed =3D false; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -ENODEV) { + dev_err_ratelimited(tascam->card->dev, + "Feedback URB failed: %d\n", + urb->status); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } + goto out; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + goto out; + + playback_ss =3D tascam->playback_substream; + if (!playback_ss || !playback_ss->runtime) + goto out; + playback_rt =3D playback_ss->runtime; + + capture_ss =3D tascam->capture_substream; + capture_rt =3D capture_ss ? capture_ss->runtime : NULL; + + { + guard(spinlock_irqsave)(&tascam->lock); + + if (tascam->feedback_urb_skip_count > 0) { + tascam->feedback_urb_skip_count--; + goto continue_unlock; + } + + old_in_idx =3D tascam->feedback_pattern_in_idx; + + for (p =3D 0; p < urb->number_of_packets; p++) { + u8 feedback_value =3D 0; + const unsigned int *pattern; + bool packet_ok =3D + (urb->iso_frame_desc[p].status =3D=3D 0 && + urb->iso_frame_desc[p].actual_length >=3D 1); + + if (packet_ok) + feedback_value =3D + *((u8 *)urb->transfer_buffer + + urb->iso_frame_desc[p].offset); + + if (packet_ok) { + int delta =3D feedback_value - + tascam->fpo.base_feedback_value + + tascam->fpo.feedback_offset; + int pattern_idx; + + if (delta < 0) { + pattern_idx =3D + 0; // Clamp to the lowest pattern + } else if (delta >=3D 5) { + pattern_idx =3D + 4; // Clamp to the highest pattern + } else { + pattern_idx =3D delta; + } + + pattern =3D + tascam->fpo + .full_frame_patterns[pattern_idx]; + tascam->feedback_consecutive_errors =3D 0; + int i; + + for (i =3D 0; i < 8; i++) { + unsigned int in_idx =3D + (tascam->feedback_pattern_in_idx + + i) % + FEEDBACK_ACCUMULATOR_SIZE; + + tascam->feedback_accumulator_pattern + [in_idx] =3D pattern[i]; + total_frames_in_urb +=3D pattern[i]; + } + } else { + unsigned int nominal_frames =3D + playback_rt->rate / 8000; + int i; + + if (tascam->feedback_synced) { + tascam->feedback_consecutive_errors++; + if (tascam->feedback_consecutive_errors > + FEEDBACK_SYNC_LOSS_THRESHOLD) { + dev_err(tascam->card->dev, + "Fatal: Feedback sync lost. Stopping stream.\n"); + schedule_work( + &tascam->stop_pcm_work); + tascam->feedback_synced =3D false; + goto continue_unlock; + } + } + for (i =3D 0; i < 8; i++) { + unsigned int in_idx =3D + (tascam->feedback_pattern_in_idx + + i) % + FEEDBACK_ACCUMULATOR_SIZE; + + tascam->feedback_accumulator_pattern + [in_idx] =3D nominal_frames; + total_frames_in_urb +=3D nominal_frames; + } + } + tascam->feedback_pattern_in_idx =3D + (tascam->feedback_pattern_in_idx + 8) % + FEEDBACK_ACCUMULATOR_SIZE; + } + + new_in_idx =3D tascam->feedback_pattern_in_idx; + + if (!tascam->feedback_synced) { + unsigned int out_idx =3D tascam->feedback_pattern_out_idx; + bool is_ahead =3D (new_in_idx - out_idx) % + FEEDBACK_ACCUMULATOR_SIZE < + (FEEDBACK_ACCUMULATOR_SIZE / 2); + bool was_behind =3D (old_in_idx - out_idx) % + FEEDBACK_ACCUMULATOR_SIZE >=3D + (FEEDBACK_ACCUMULATOR_SIZE / 2); + + if (is_ahead && was_behind) { + dev_dbg(tascam->card->dev, + "Sync Acquired! (in: %u, out: %u)\n", + new_in_idx, out_idx); + tascam->feedback_synced =3D true; + tascam->feedback_consecutive_errors =3D 0; + } + } + + if (total_frames_in_urb > 0) { + tascam->playback_frames_consumed +=3D total_frames_in_urb; + if (atomic_read(&tascam->capture_active)) + tascam->capture_frames_processed +=3D + total_frames_in_urb; + } + + if (playback_rt->period_size > 0) { + u64 current_period =3D + div_u64(tascam->playback_frames_consumed, + playback_rt->period_size); + + if (current_period > tascam->last_period_pos) { + tascam->last_period_pos =3D current_period; + playback_period_elapsed =3D true; + } + } + + if (atomic_read(&tascam->capture_active) && capture_rt && + capture_rt->period_size > 0) { + u64 current_capture_period =3D + div_u64(tascam->capture_frames_processed, + capture_rt->period_size); + + if (current_capture_period > + tascam->last_capture_period_pos) { + tascam->last_capture_period_pos =3D + current_capture_period; + capture_period_elapsed =3D true; + } + } + } + +continue_unlock: + if (playback_period_elapsed) + snd_pcm_period_elapsed(playback_ss); + if (capture_period_elapsed) + snd_pcm_period_elapsed(capture_ss); + + urb->dev =3D tascam->dev; + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->feedback_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit feedback URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + } +out: + usb_put_urb(urb); +} + +void tascam_stop_pcm_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, stop_pcm_work); + + if (tascam->playback_substream) + snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN); + if (tascam->capture_substream) + snd_pcm_stop(tascam->capture_substream, SNDRV_PCM_STATE_XRUN); +} --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f42.google.com (mail-ej1-f42.google.com [209.85.218.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 69D0127A107; Tue, 12 Aug 2025 12:56:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003409; cv=none; b=sE4jgAMt21w4YHBsp85zO/NPKsCBNYyaXFfqpb5pDPcYLivLdnFHIIInQPsvwD7GPeH8PzryB+/s54HHh0qFLcI+vLd6CGrH/+8fPqzySMueuYeqSy4ZZH9tXJoeB+PjP4qavK7EDF0MTtqmGg0STWfBbVUCFHEd54TchHuZgTo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003409; c=relaxed/simple; bh=fEVl6dr/OTv7oiBvGgjw4LMV8VYApwKCRV0uvvNPm9M=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=HWqovJikWEs9AVr3sX5S9bEBEJVUffeVw3b3ZVg2DIs1KYlKHSnWiA4CxsQYff/sSLKSmlYUGXGxPj52Og+Ghr+A3cnzmGo3bsEHQrRvDYeEdQ0lEsrmn9Ua6WQvOkjB6F+CSEqQ9+9AGjq2mIRQ+80T9DmFHkey11/sOLja5lk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OszGWezo; arc=none smtp.client-ip=209.85.218.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OszGWezo" Received: by mail-ej1-f42.google.com with SMTP id a640c23a62f3a-af98b77d2f0so1010648366b.3; Tue, 12 Aug 2025 05:56:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003405; x=1755608205; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=fg0v+2hv5mjXiI0Fnsf0f/fkTLS5cbrfyLOw8faegMY=; b=OszGWezoa8YKKcMikncSd1BnEwMTUtx6sf//udkGdnB50bRRs5szbt3vX3LSnRKZEP 8NoOlMIQzlHK7rtt4p5aIOs2+HgqUTWfK7dm1cnpKY7rWhDjgqfpTEYS3LXw8TZOZPeQ 09dAZEOXkEJa/Jbf3V9wDj74s9IF1wSwgpFT3rX4mN1dKXMsywX+g8Clzdb+IJAJ8RiT r8NcnSK1NuhvWUv2wuTYh7qTSqm25oSGQkB49iNNdbxNdgQy7ZsvymE4joXhG5r0EEli UZHxGUP0GmM/hltrYAo9ahofGVplocOYWHULZujteiaeQSbi5h2COX9uQAhLAhvKbUmB O/lA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003405; x=1755608205; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=fg0v+2hv5mjXiI0Fnsf0f/fkTLS5cbrfyLOw8faegMY=; b=eyUcl4bRTA2EDSvIEjOMX/fiuskpWbDH1kNXjz+IaBcJrXvHx9Vopndvd24AmOUVV6 wmDual5wikmSM1kSuq+uBYSbVP7t5tjOvJBjfHVYB8VWHqXucYZb0teposSSATFxl6TL XLhWRWVkDWI4b7156BA3FHzO+hVBWFLt/Fk0ECEURABTyQGWP/2EHPsfBVqGpJkjIYHk RxU1w6jYeVyvP5dTNCyYDfwxDg/zvWUgsNicyAmH2sL8eSTtGmdPl9ZqRb929nPgr4Dk OyhoHlfeOBSdQ31rDKfdBCeTh8JZL5Nl5iz87Qug+EH3595gvd44Zcea5uzsBe/mIh1e kpaw== X-Forwarded-Encrypted: i=1; AJvYcCWQZur5QwlJFJSCgrDLAv0XVfTy2IMNNMf5nxFlYWnshH/k5TY4X+M4F4O7hLX5fJFNs3C6f2JpBuanbw==@vger.kernel.org X-Gm-Message-State: AOJu0Yxm29hnldkqoXFLy5gac6/FJsAnAu1Mp2cYiCHDGOlJj2hlOP8O 9qoIm0p7t4iUYw5TpUBlFOyL0iDRzhnnxsSATVOKZ2AXKvGBeTORV+Dt X-Gm-Gg: ASbGncuwAs90YFXy6/DhqDuOQ5UvYTWL8BJtz3A4u+Fc+2hMILK3SlVIYl+11B2NrHi fy/TlNqfxtdmL5J9X8ozvSaniwCe4QSdKN9bDxU9OYnWSE70cZZu+vu4Mbdbe3zPBvrlK+ylsg/ furbL3bQ5pA3EoD5z9H7R37jI3ZhXpBZpiyw7xcl4D6pA0TYda62YNyWMGEC0ZliYgNIUsj0kRN 67ulaaYHLO75K7zLCg8jboeNSatSEalgUq0pptdS9CnO9W3gk8CmhSNmUdNlqJeUhctjmPO2uw1 gzoRCymtTQ14Yf2eP2PswdWFDA3MN2lGJ5B2xRCztbvJn+8yt1DCEj+gIn9bz+KyDIYCBopZKY2 jePwUUjIKtFWl/h1zpHEefJQMZ1AhbaZh/Hw9O8SpOgyH/2ksV5UHOkQF4+GBiMNGi6sKjT3mZu EwPBADrA== X-Google-Smtp-Source: AGHT+IH4FpzbActkwiCSIa1u3lmCoTream+ryG18f0vT8/a8YyoiLT2zoJE+15kYOYiydXKbaSA4kA== X-Received: by 2002:a17:907:3d4b:b0:ae0:c441:d54b with SMTP id a640c23a62f3a-af9c63b09a5mr1585867166b.9.1755003404318; Tue, 12 Aug 2025 05:56:44 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:43 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 4/7] ALSA: usb-audio: us144mkii: Implement audio capture and decoding Date: Tue, 12 Aug 2025 14:56:28 +0200 Message-Id: <20250812125633.79270-9-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch adds the full audio capture data path. It allocates and manages bulk URBs to receive audio data from the device. The incoming data is in a custom multiplexed format. A workqueue is used to offload the decoding process from the interrupt context. The `capture_urb_complete` handler copies raw data into a ring buffer and schedules the work. The `tascam_capture_work_handler` then reads from this buffer, decodes the data into standard S32_LE samples, and copies it to the ALSA capture buffer. The PCM trigger is updated to manage the submission of capture URBs, and the feedback handler now also advances the capture stream pointer to keep it synchronized with playback. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 73 ++++++++++ sound/usb/usx2y/us144mkii.h | 44 +++++- sound/usb/usx2y/us144mkii_capture.c | 218 +++++++++++++++++++++++++++- sound/usb/usx2y/us144mkii_pcm.c | 30 ++++ sound/usb/usx2y/us144mkii_pcm.h | 35 ++++- 5 files changed, 392 insertions(+), 8 deletions(-) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 3a147fba873e..7a114e64fb1f 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -62,6 +62,27 @@ void tascam_free_urbs(struct tascam_card *tascam) tascam->feedback_urbs[i] =3D NULL; } } + + usb_kill_anchored_urbs(&tascam->capture_anchor); + for (i =3D 0; i < NUM_CAPTURE_URBS; i++) { + if (tascam->capture_urbs[i]) { + usb_free_coherent( + tascam->dev, tascam->capture_urb_alloc_size, + tascam->capture_urbs[i]->transfer_buffer, + tascam->capture_urbs[i]->transfer_dma); + usb_free_urb(tascam->capture_urbs[i]); + tascam->capture_urbs[i] =3D NULL; + } + } + + kfree(tascam->capture_routing_buffer); + tascam->capture_routing_buffer =3D NULL; + kfree(tascam->capture_decode_dst_block); + tascam->capture_decode_dst_block =3D NULL; + kfree(tascam->capture_decode_raw_block); + tascam->capture_decode_raw_block =3D NULL; + kfree(tascam->capture_ring_buffer); + tascam->capture_ring_buffer =3D NULL; } =20 int tascam_alloc_urbs(struct tascam_card *tascam) @@ -121,6 +142,52 @@ int tascam_alloc_urbs(struct tascam_card *tascam) f_urb->complete =3D feedback_urb_complete; } =20 + tascam->capture_urb_alloc_size =3D CAPTURE_URB_SIZE; + for (i =3D 0; i < NUM_CAPTURE_URBS; i++) { + struct urb *c_urb =3D usb_alloc_urb(0, GFP_KERNEL); + + if (!c_urb) + goto error; + tascam->capture_urbs[i] =3D c_urb; + + c_urb->transfer_buffer =3D usb_alloc_coherent( + tascam->dev, tascam->capture_urb_alloc_size, GFP_KERNEL, + &c_urb->transfer_dma); + if (!c_urb->transfer_buffer) + goto error; + + usb_fill_bulk_urb(c_urb, tascam->dev, + usb_rcvbulkpipe(tascam->dev, EP_AUDIO_IN), + c_urb->transfer_buffer, + tascam->capture_urb_alloc_size, + capture_urb_complete, tascam); + c_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; + } + + tascam->capture_ring_buffer =3D + kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL); + if (!tascam->capture_ring_buffer) + goto error; + + tascam->capture_decode_raw_block =3D + kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_KERNEL); + if (!tascam->capture_decode_raw_block) + goto error; + + tascam->capture_decode_dst_block =3D + kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE, + GFP_KERNEL); + if (!tascam->capture_decode_dst_block) + goto error; + + tascam->capture_routing_buffer =3D + kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE, + GFP_KERNEL); + if (!tascam->capture_routing_buffer) + goto error; + return 0; =20 error: @@ -136,6 +203,7 @@ void tascam_stop_work_handler(struct work_struct *work) =20 usb_kill_anchored_urbs(&tascam->playback_anchor); usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); atomic_set(&tascam->active_urbs, 0); } =20 @@ -259,10 +327,12 @@ static int tascam_probe(struct usb_interface *intf, =20 spin_lock_init(&tascam->lock); init_usb_anchor(&tascam->playback_anchor); + init_usb_anchor(&tascam->capture_anchor); init_usb_anchor(&tascam->feedback_anchor); =20 INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); + INIT_WORK(&tascam->capture_work, tascam_capture_work_handler); =20 err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); if (err < 0) @@ -326,6 +396,7 @@ static void tascam_disconnect(struct usb_interface *int= f) if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { snd_card_disconnect(tascam->card); cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); cancel_work_sync(&tascam->stop_pcm_work); tascam_free_urbs(tascam); snd_card_free(tascam->card); @@ -353,8 +424,10 @@ static int tascam_suspend(struct usb_interface *intf, = pm_message_t message) snd_pcm_suspend_all(tascam->pcm); =20 cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); cancel_work_sync(&tascam->stop_pcm_work); usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); usb_kill_anchored_urbs(&tascam->feedback_anchor); =20 return 0; diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index 257ab22dafc1..34b9b275b905 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -20,14 +20,17 @@ /* --- USB Endpoints (Alternate Setting 1) --- */ #define EP_PLAYBACK_FEEDBACK 0x81 #define EP_AUDIO_OUT 0x02 +#define EP_AUDIO_IN 0x86 =20 /* --- USB Control Message Protocol --- */ #define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) +#define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) #define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) #define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVIC= E) =20 enum uac_request { UAC_SET_CUR =3D 0x01, + UAC_GET_CUR =3D 0x81, }; =20 enum uac_control_selector { @@ -64,6 +67,9 @@ enum tascam_register { #define NUM_FEEDBACK_URBS 4 #define FEEDBACK_URB_PACKETS 1 #define FEEDBACK_PACKET_SIZE 3 +#define NUM_CAPTURE_URBS 8 +#define CAPTURE_URB_SIZE 512 +#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4) #define USB_CTRL_TIMEOUT_MS 1000 #define FEEDBACK_SYNC_LOSS_THRESHOLD 41 =20 @@ -73,6 +79,12 @@ enum tascam_register { #define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) #define FEEDBACK_ACCUMULATOR_SIZE 128 =20 +/* --- Capture Decoding Defines --- */ +#define DECODED_CHANNELS_PER_FRAME 4 +#define DECODED_SAMPLE_SIZE 4 +#define FRAMES_PER_DECODE_BLOCK 8 +#define RAW_BYTES_PER_DECODE_BLOCK 512 + struct tascam_card; =20 #include "us144mkii_pcm.h" @@ -117,10 +129,19 @@ struct us144mkii_frame_pattern_observer { * @last_period_pos: Last reported period position for playback. * * @capture_substream: Pointer to the active capture PCM substream. + * @capture_urbs: Array of URBs for capture. + * @capture_urb_alloc_size: Size of allocated buffer for each capture URB. * @capture_active: Atomic flag indicating if capture is active. * @driver_capture_pos: Current position in the ALSA capture buffer (frame= s). * @capture_frames_processed: Total frames processed for capture. * @last_capture_period_pos: Last reported period position for capture. + * @capture_ring_buffer: Ring buffer for raw capture data from USB. + * @capture_ring_buffer_read_ptr: Read pointer for the capture ring buffer. + * @capture_ring_buffer_write_ptr: Write pointer for the capture ring buff= er. + * @capture_decode_raw_block: Buffer for a raw 512-byte capture block. + * @capture_decode_dst_block: Buffer for decoded 32-bit capture samples. + * @capture_routing_buffer: Intermediate buffer for capture routing. + * @capture_work: Work struct for deferred capture processing. * * @stop_work: Work struct for deferred stream stopping. * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. @@ -141,6 +162,7 @@ struct us144mkii_frame_pattern_observer { * @fpo: Holds the state for the dynamic feedback pattern generation. * * @playback_anchor: USB anchor for playback URBs. + * @capture_anchor: USB anchor for capture URBs. * @feedback_anchor: USB anchor for feedback URBs. */ struct tascam_card { @@ -160,7 +182,10 @@ struct tascam_card { size_t playback_urb_alloc_size; struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; size_t feedback_urb_alloc_size; + struct urb *capture_urbs[NUM_CAPTURE_URBS]; + size_t capture_urb_alloc_size; struct usb_anchor playback_anchor; + struct usb_anchor capture_anchor; struct usb_anchor feedback_anchor; =20 /* --- Stream State --- */ @@ -179,6 +204,12 @@ struct tascam_card { u64 capture_frames_processed; snd_pcm_uframes_t driver_capture_pos; u64 last_capture_period_pos; + u8 *capture_ring_buffer; + size_t capture_ring_buffer_read_ptr; + size_t capture_ring_buffer_write_ptr; + u8 *capture_decode_raw_block; + s32 *capture_decode_dst_block; + s32 *capture_routing_buffer; =20 /* --- Feedback Sync State --- */ unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; @@ -192,14 +223,16 @@ struct tascam_card { /* --- Workqueues --- */ struct work_struct stop_work; struct work_struct stop_pcm_work; + struct work_struct capture_work; }; =20 /** * tascam_free_urbs() - Free all allocated URBs and associated buffers. * @tascam: the tascam_card instance * - * This function kills, unlinks, and frees all playback and feedback URBs, - * along with their transfer buffers. + * This function kills, unlinks, and frees all playback, feedback, and + * capture URBs, along with their transfer buffers and the capture + * ring/decode buffers. */ void tascam_free_urbs(struct tascam_card *tascam); =20 @@ -207,7 +240,8 @@ void tascam_free_urbs(struct tascam_card *tascam); * tascam_alloc_urbs() - Allocate all URBs and associated buffers. * @tascam: the tascam_card instance * - * This function allocates and initializes all URBs for playback and feedb= ack. + * This function allocates and initializes all URBs for playback, feedback, + * and capture, as well as the necessary buffers for data processing. * * Return: 0 on success, or a negative error code on failure. */ @@ -217,8 +251,8 @@ int tascam_alloc_urbs(struct tascam_card *tascam); * tascam_stop_work_handler() - Work handler to stop all active streams. * @work: Pointer to the work_struct. * - * This function is scheduled to stop all active URBs (playback, feedback) - * and reset the active_urbs counter. + * This function is scheduled to stop all active URBs (playback, feedback, + * capture) and reset the active_urbs counter. */ void tascam_stop_work_handler(struct work_struct *work); =20 diff --git a/sound/usb/usx2y/us144mkii_capture.c b/sound/usb/usx2y/us144mki= i_capture.c index 22b8faa9bbe8..68ff07d7e441 100644 --- a/sound/usb/usx2y/us144mkii_capture.c +++ b/sound/usb/usx2y/us144mkii_capture.c @@ -45,7 +45,7 @@ static int tascam_capture_close(struct snd_pcm_substream = *substream) * tascam_capture_prepare() - Prepares the PCM capture substream for use. * @substream: The ALSA PCM substream to prepare. * - * This function initializes capture-related counters. + * This function initializes capture-related counters and ring buffer poin= ters. * * Return: 0 on success. */ @@ -56,6 +56,8 @@ static int tascam_capture_prepare(struct snd_pcm_substrea= m *substream) tascam->driver_capture_pos =3D 0; tascam->capture_frames_processed =3D 0; tascam->last_capture_period_pos =3D 0; + tascam->capture_ring_buffer_read_ptr =3D 0; + tascam->capture_ring_buffer_write_ptr =3D 0; =20 return 0; } @@ -107,3 +109,217 @@ const struct snd_pcm_ops tascam_capture_ops =3D { .trigger =3D tascam_pcm_trigger, .pointer =3D tascam_capture_pointer, }; + +/** + * decode_tascam_capture_block() - Decodes a raw 512-byte block from the d= evice. + * @src_block: Pointer to the 512-byte raw source block. + * @dst_block: Pointer to the destination buffer for decoded audio frames. + * + * The device sends audio data in a complex, multiplexed format. This func= tion + * demultiplexes the bits from the raw block into 8 frames of 4-channel, + * 24-bit audio (stored in 32-bit containers). + */ +static void decode_tascam_capture_block(const u8 *src_block, s32 *dst_bloc= k) +{ + int frame, bit; + + memset(dst_block, 0, + FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE); + + for (frame =3D 0; frame < FRAMES_PER_DECODE_BLOCK; ++frame) { + const u8 *p_src_frame_base =3D src_block + frame * 64; + s32 *p_dst_frame =3D dst_block + frame * 4; + + s32 ch[4] =3D { 0 }; + + for (bit =3D 0; bit < 24; ++bit) { + u8 byte1 =3D p_src_frame_base[bit]; + u8 byte2 =3D p_src_frame_base[bit + 32]; + + ch[0] =3D (ch[0] << 1) | (byte1 & 1); + ch[2] =3D (ch[2] << 1) | ((byte1 >> 1) & 1); + + ch[1] =3D (ch[1] << 1) | (byte2 & 1); + ch[3] =3D (ch[3] << 1) | ((byte2 >> 1) & 1); + } + + /* + * The result is a 24-bit sample. Shift left by 8 to align it to + * the most significant bits of a 32-bit integer (S32_LE format). + */ + p_dst_frame[0] =3D ch[0] << 8; + p_dst_frame[1] =3D ch[1] << 8; + p_dst_frame[2] =3D ch[2] << 8; + p_dst_frame[3] =3D ch[3] << 8; + } +} + +void tascam_capture_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, capture_work); + struct snd_pcm_substream *substream =3D tascam->capture_substream; + struct snd_pcm_runtime *runtime; + u8 *raw_block =3D tascam->capture_decode_raw_block; + s32 *decoded_block =3D tascam->capture_decode_dst_block; + s32 *routed_block =3D tascam->capture_routing_buffer; + + if (!substream || !substream->runtime) + return; + runtime =3D substream->runtime; + + if (!raw_block || !decoded_block || !routed_block) { + dev_err(tascam->card->dev, + "Capture decode/routing buffers not allocated!\n"); + return; + } + + while (atomic_read(&tascam->capture_active)) { + size_t write_ptr, read_ptr, available_data; + bool can_process; + + { + guard(spinlock_irqsave)(&tascam->lock); + write_ptr =3D tascam->capture_ring_buffer_write_ptr; + read_ptr =3D tascam->capture_ring_buffer_read_ptr; + available_data =3D (write_ptr >=3D read_ptr) ? + (write_ptr - read_ptr) : + (CAPTURE_RING_BUFFER_SIZE - + read_ptr + write_ptr); + can_process =3D + (available_data >=3D RAW_BYTES_PER_DECODE_BLOCK); + + if (can_process) { + size_t bytes_to_end =3D + CAPTURE_RING_BUFFER_SIZE - read_ptr; + if (bytes_to_end >=3D + RAW_BYTES_PER_DECODE_BLOCK) { + memcpy(raw_block, + tascam->capture_ring_buffer + + read_ptr, + RAW_BYTES_PER_DECODE_BLOCK); + } else { + memcpy(raw_block, + tascam->capture_ring_buffer + + read_ptr, + bytes_to_end); + memcpy(raw_block + bytes_to_end, + tascam->capture_ring_buffer, + RAW_BYTES_PER_DECODE_BLOCK - + bytes_to_end); + } + tascam->capture_ring_buffer_read_ptr =3D + (read_ptr + + RAW_BYTES_PER_DECODE_BLOCK) % + CAPTURE_RING_BUFFER_SIZE; + } + } + + if (!can_process) + break; + + decode_tascam_capture_block(raw_block, decoded_block); + process_capture_routing_us144mkii(tascam, decoded_block, + routed_block); + + { + guard(spinlock_irqsave)(&tascam->lock); + if (atomic_read(&tascam->capture_active)) { + int f; + + for (f =3D 0; f < FRAMES_PER_DECODE_BLOCK; ++f) { + u8 *dst_frame_start =3D + runtime->dma_area + + frames_to_bytes( + runtime, + tascam->driver_capture_pos); + s32 *routed_frame_start =3D + routed_block + + (f * NUM_CHANNELS); + int c; + + for (c =3D 0; c < NUM_CHANNELS; c++) { + u8 *dst_channel =3D + dst_frame_start + + (c * BYTES_PER_SAMPLE); + s32 *src_channel_s32 =3D + routed_frame_start + c; + + memcpy(dst_channel, + ((char *)src_channel_s32) + + 1, + 3); + } + + tascam->driver_capture_pos =3D + (tascam->driver_capture_pos + + 1) % + runtime->buffer_size; + } + } + } + } +} + +void capture_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + int ret; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -ENODEV && + urb->status !=3D -EPROTO) + dev_err_ratelimited(tascam->card->dev, + "Capture URB failed: %d\n", + urb->status); + goto out; + } + if (!tascam || !atomic_read(&tascam->capture_active)) + goto out; + + if (urb->actual_length > 0) { + size_t write_ptr; + size_t bytes_to_end; + + { + guard(spinlock_irqsave)(&tascam->lock); + write_ptr =3D tascam->capture_ring_buffer_write_ptr; + bytes_to_end =3D CAPTURE_RING_BUFFER_SIZE - write_ptr; + + if (urb->actual_length > bytes_to_end) { + memcpy(tascam->capture_ring_buffer + write_ptr, + urb->transfer_buffer, bytes_to_end); + memcpy(tascam->capture_ring_buffer, + urb->transfer_buffer + bytes_to_end, + urb->actual_length - bytes_to_end); + } else { + memcpy(tascam->capture_ring_buffer + write_ptr, + urb->transfer_buffer, + urb->actual_length); + } + + tascam->capture_ring_buffer_write_ptr =3D + (write_ptr + urb->actual_length) % + CAPTURE_RING_BUFFER_SIZE; + } + + schedule_work(&tascam->capture_work); + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->capture_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err_ratelimited(tascam->card->dev, + "Failed to resubmit capture URB: %d\n", + ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + atomic_dec( + &tascam->active_urbs); /* Decrement on failed resubmission */ + } +out: + usb_put_urb(urb); +} diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c index 7d1bbd547504..7bee8b4210a6 100644 --- a/sound/usb/usx2y/us144mkii_pcm.c +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -61,6 +61,16 @@ void process_playback_routing_us144mkii(struct tascam_ca= rd *tascam, memcpy(dst_buffer, src_buffer, frames * BYTES_PER_FRAME); } =20 +void process_capture_routing_us144mkii(struct tascam_card *tascam, + const s32 *decoded_block, + s32 *routed_block) +{ + /* This is a stub. Routing will be added in a later commit. */ + memcpy(routed_block, decoded_block, + FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * + DECODED_SAMPLE_SIZE); +} + int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te) { struct usb_device *dev =3D tascam->dev; @@ -110,6 +120,12 @@ int us144mkii_configure_device_for_rate(struct tascam_= card *tascam, int rate) USB_CTRL_TIMEOUT_MS); if (err < 0) goto fail; + err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, + EP_AUDIO_IN, rate_payload_buf, 3, + USB_CTRL_TIMEOUT_MS); + if (err < 0) + goto fail; err =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_OUT, rate_payload_buf, 3, @@ -274,6 +290,20 @@ int tascam_pcm_trigger(struct snd_pcm_substream *subst= ream, int cmd) } atomic_inc(&tascam->active_urbs); } + for (i =3D 0; i < NUM_CAPTURE_URBS; i++) { + usb_get_urb(tascam->capture_urbs[i]); + usb_anchor_urb(tascam->capture_urbs[i], + &tascam->capture_anchor); + err =3D usb_submit_urb(tascam->capture_urbs[i], + GFP_ATOMIC); + if (err < 0) { + usb_unanchor_urb(tascam->capture_urbs[i]); + usb_put_urb(tascam->capture_urbs[i]); + atomic_dec(&tascam->active_urbs); + goto start_rollback; + } + atomic_inc(&tascam->active_urbs); + } =20 return 0; start_rollback: diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pc= m.h index 6ca00c3ce53d..74da8564431b 100644 --- a/sound/usb/usx2y/us144mkii_pcm.h +++ b/sound/usb/usx2y/us144mkii_pcm.h @@ -51,6 +51,16 @@ void playback_urb_complete(struct urb *urb); */ void feedback_urb_complete(struct urb *urb); =20 +/** + * capture_urb_complete() - Completion handler for capture bulk URBs. + * @urb: the completed URB + * + * This function runs in interrupt context. It copies the received raw data + * into an intermediate ring buffer and then schedules the workqueue to pr= ocess + * it. It then resubmits the URB to receive more data. + */ +void capture_urb_complete(struct urb *urb); + /** * tascam_stop_pcm_work_handler() - Work handler to stop PCM streams. * @work: Pointer to the work_struct. @@ -64,8 +74,8 @@ void tascam_stop_pcm_work_handler(struct work_struct *wor= k); * tascam_init_pcm() - Initializes the ALSA PCM device. * @pcm: Pointer to the ALSA PCM device to initialize. * - * This function sets up the PCM operations and preallocates pages for the - * PCM buffer. + * This function sets up the PCM operations, adds ALSA controls for routing + * and sample rate, and preallocates pages for the PCM buffer. * * Return: 0 on success, or a negative error code on failure. */ @@ -94,6 +104,16 @@ void process_playback_routing_us144mkii(struct tascam_c= ard *tascam, const u8 *src_buffer, u8 *dst_buffer, size_t frames); =20 +/** + * process_capture_routing_us144mkii() - Apply capture routing matrix + * @tascam: The driver instance. + * @decoded_block: Buffer containing 4 channels of S32LE decoded audio. + * @routed_block: Buffer to be filled for ALSA. + */ +void process_capture_routing_us144mkii(struct tascam_card *tascam, + const s32 *decoded_block, + s32 *routed_block); + /** * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams. * @substream: The ALSA PCM substream. @@ -131,4 +151,15 @@ int tascam_pcm_hw_free(struct snd_pcm_substream *subst= ream); */ int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd); =20 +/** + * tascam_capture_work_handler() - Deferred work for processing capture da= ta. + * @work: the work_struct instance + * + * This function runs in a kernel thread context, not an IRQ context. It r= eads + * raw data from the capture ring buffer, decodes it, applies routing, and + * copies the final audio data into the ALSA capture ring buffer. This off= loads + * the CPU-intensive decoding from the time-sensitive URB completion handl= ers. + */ +void tascam_capture_work_handler(struct work_struct *work); + #endif /* __US144MKII_PCM_H */ --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ed1-f46.google.com (mail-ed1-f46.google.com [209.85.208.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5CD0F275B19; Tue, 12 Aug 2025 12:56:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003410; cv=none; b=hHlGhihHVpiNDdhiRNMCh4anZalh7DmDhSokniB19wUIXcYQEinY/QI8gKIRrvV15n6X6/NTBguZPtLoWa61svdZvQWKJYhWf+GFDQcQmo5Yhw8Y4Xl8NcZ96B1g4EivbwYGnbGX1kOtWj8tqYXj1g9qmxAg0fC/71r/b8Tcwno= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003410; c=relaxed/simple; bh=C3ybyQQGIbjc8NQlG8BH+g0H2+qjw24oDoZwNCvf59o=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=bYRItpNv3ZFIRs3VNmquWn3AL47uESJ+67/ZfnaIZ8axaIQE4VO6Fmw2FWBRi9dzYClA5NMUV3QF7/iVEwmsgxKqbkn8h8njl3lu3udyOWu0v5O6/7yb2JyRCRJboYtSn1g/+huMXihuuyQGAlbci8KtG7cwlW6y4LB5cGC9T+E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=d7T2AW5q; arc=none smtp.client-ip=209.85.208.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="d7T2AW5q" Received: by mail-ed1-f46.google.com with SMTP id 4fb4d7f45d1cf-6157c81ff9eso8301551a12.3; Tue, 12 Aug 2025 05:56:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003404; x=1755608204; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qvIkb7cGEb/Ghy2S086B7ZlGAa89IXzdViRuKJif9Po=; b=d7T2AW5q2nOonpxXZI2kKJC+G1KZVfU9VUBgxvBy5GEv+gJvHtWxpubZ6F6gcwg2oB 8AdQnwSf29jPChKd72PD9XM8fMKZqXyq3cw+1Bt9MCedN4+SyoBb3sXO/HdRF/OhPPfr i44AVVZ3+Qiyg9TdBbuQCY8pMGZt8XHRPNg2hGZVzOlRwh7rgIvvmq/tAWEcaACN5f2f M6WbS2tWfycNDQ5hnafZ2YQbIVGwPGTPb0NqM52rCRbGO6QUFfKCX7HQGWJET1PgiI8+ NUjuyVIZNZfq91fIVcMEsgfI5Ay5eI+0WOyF9tIB3gDKu1Nps2wb2/DQL39R+9Ny20Tp 0aIw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003404; x=1755608204; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=qvIkb7cGEb/Ghy2S086B7ZlGAa89IXzdViRuKJif9Po=; b=v8AJl70gKu+I2xreBbNFtgM6M2EhPKSoxbQfHkW+UTZMdQaMWeWHjYbjQFdn23pJhl lU0Dt8ZlT+Pi48xYPfVeed+LqgR9CooEAk4WSQJwy9gOWg8NhfvyUddKf95dJBQO68U0 3eyTJK/K09nwlNoimlWkwWvPSEyaYLlTGnOt6acF1mjmP9gQ/fTljFvKnYBVS/ZJY2cy 0QI5DspmkAaJ5fGib2a3KRiUMly/KUBFkBIkRfNNGsbsYaBhdo6vAElp9WN1bobodSQR 2KrF9Gs4SudQFVNTZQcldjhwG435USAR4oAFbvKLU35EmjvpBVuJPhRJao/aj0x9oCYY RKtQ== X-Forwarded-Encrypted: i=1; AJvYcCUH5f3C44ghPPZ4Xsws2rAjZgnrS15JJlyTJhFxXs/6vSBA3C8MZ5XJaRMDDqxj2WRBmIjLyGv97aAqPQ==@vger.kernel.org X-Gm-Message-State: AOJu0YxyOS4435HC+X+lk0dqvlJUavg13bxmc7RK0z7rO3eyT/gToO+o Xu5QVwrl9opv1U3wSx2RCvdMXpII9t/dp54rpr4UkUh0phD4K0W6QLzt X-Gm-Gg: ASbGncvQ+3Q2QXPsrspl+vJGMZBgHyhRPVeCfjvrmZS3NxFjZWSinQcu+aeWdb5vjJT kl6LU/63T0jgCGolmXqCoz/EfJFzRudZISfOTJqfE4UHORKQM90Sb1IoxNAQdsLbBJ2cNugS7t/ hH3lIXXaKj1cZ8ebqaPgEAPfYKurC5WBKa0kLcJgH1k+7YK3yX4HBVU+XWi63nsX5tX4esXj11P tIfwrdZB3fhA0cpEdYntlC/NzyboiBVY/QPsVeuKGiqZOOhDZ/E8Rr/sbfY3sbzLIs5IOhHTpHb +s4q4+88aG0HH26jUK06gkYsnNTOPc6OEWV+ZL0g8XyUCkoKJdtN8bpoZry7r3sgMi0CVnlSEYD gH40pcmoKufP2j0wtEBvzi4HPEV7b59MMYIrDNQH42c/WY4loSryEYobtZErWroFwstWuwoUWBV rL5RWXTQ== X-Google-Smtp-Source: AGHT+IFdDxIAvPitjR+NjXl60GB508wiWw7vazW/44MbzpGGkWcy+9le05z2UliSNKyETLqjXXJ9dw== X-Received: by 2002:a17:907:9287:b0:ae0:68a8:bd6a with SMTP id a640c23a62f3a-afa1dfea07dmr319834766b.15.1755003403221; Tue, 12 Aug 2025 05:56:43 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:42 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 4/6] ALSA: usb-audio: us144mkii: Add MIDI support and mixer controls Date: Tue, 12 Aug 2025 14:56:27 +0200 Message-Id: <20250812125633.79270-8-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch adds the remaining features to the driver: MIDI I/O and ALSA mixer controls. A raw MIDI device is created, supporting one input and one output port. The implementation handles the device-specific 9-byte packet format for both sending and receiving MIDI data, using bulk URBs and workqueues for processing. ALSA kcontrols are added to the mixer interface, allowing user-space control over the audio routing matrix (e.g., selecting analog/digital sources for capture channels, and routing playback streams to different outputs). A read-only control to display the current sample rate is also included. Finally, the stub routing functions are replaced with the full logic to apply the user-selected mixer settings to the audio streams. With this commit, the driver is feature-complete. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 303 ++++++++++++------ sound/usb/usx2y/us144mkii.h | 126 +++++++- sound/usb/usx2y/us144mkii_controls.c | 444 +++++++++++++++++++++++++++ sound/usb/usx2y/us144mkii_midi.c | 401 ++++++++++++++++++++++++ sound/usb/usx2y/us144mkii_pcm.c | 57 +++- 5 files changed, 1219 insertions(+), 112 deletions(-) create mode 100644 sound/usb/usx2y/us144mkii_controls.c create mode 100644 sound/usb/usx2y/us144mkii_midi.c diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 7a114e64fb1f..f7944eb2fb93 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -75,6 +75,30 @@ void tascam_free_urbs(struct tascam_card *tascam) } } =20 + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + for (i =3D 0; i < NUM_MIDI_IN_URBS; i++) { + if (tascam->midi_in_urbs[i]) { + usb_free_coherent( + tascam->dev, MIDI_IN_BUF_SIZE, + tascam->midi_in_urbs[i]->transfer_buffer, + tascam->midi_in_urbs[i]->transfer_dma); + usb_free_urb(tascam->midi_in_urbs[i]); + tascam->midi_in_urbs[i] =3D NULL; + } + } + + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i]) { + usb_free_coherent( + tascam->dev, MIDI_OUT_BUF_SIZE, + tascam->midi_out_urbs[i]->transfer_buffer, + tascam->midi_out_urbs[i]->transfer_dma); + usb_free_urb(tascam->midi_out_urbs[i]); + tascam->midi_out_urbs[i] =3D NULL; + } + } + kfree(tascam->capture_routing_buffer); tascam->capture_routing_buffer =3D NULL; kfree(tascam->capture_decode_dst_block); @@ -164,6 +188,44 @@ int tascam_alloc_urbs(struct tascam_card *tascam) c_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; } =20 + /* MIDI URB and buffer allocation */ + for (i =3D 0; i < NUM_MIDI_IN_URBS; i++) { + struct urb *m_urb =3D usb_alloc_urb(0, GFP_KERNEL); + + if (!m_urb) + goto error; + tascam->midi_in_urbs[i] =3D m_urb; + m_urb->transfer_buffer =3D + usb_alloc_coherent(tascam->dev, MIDI_IN_BUF_SIZE, + GFP_KERNEL, &m_urb->transfer_dma); + if (!m_urb->transfer_buffer) + goto error; + usb_fill_bulk_urb(m_urb, tascam->dev, + usb_rcvbulkpipe(tascam->dev, EP_MIDI_IN), + m_urb->transfer_buffer, MIDI_IN_BUF_SIZE, + tascam_midi_in_urb_complete, tascam); + m_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; + } + + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + struct urb *m_urb =3D usb_alloc_urb(0, GFP_KERNEL); + + if (!m_urb) + goto error; + tascam->midi_out_urbs[i] =3D m_urb; + m_urb->transfer_buffer =3D + usb_alloc_coherent(tascam->dev, MIDI_OUT_BUF_SIZE, + GFP_KERNEL, &m_urb->transfer_dma); + if (!m_urb->transfer_buffer) + goto error; + usb_fill_bulk_urb(m_urb, tascam->dev, + usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT), + m_urb->transfer_buffer, + 0, /* length set later */ + tascam_midi_out_urb_complete, tascam); + m_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; + } + tascam->capture_ring_buffer =3D kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL); if (!tascam->capture_ring_buffer) @@ -213,16 +275,112 @@ void tascam_stop_work_handler(struct work_struct *wo= rk) * @card: Pointer to the ALSA sound card instance. * * This function is called when the sound card is being freed. It releases - * the reference to the USB device. + * resources allocated for the tascam_card structure, including the MIDI + * input FIFO and decrements the USB device reference count. */ static void tascam_card_private_free(struct snd_card *card) { struct tascam_card *tascam =3D card->private_data; =20 - if (tascam && tascam->dev) { - usb_put_dev(tascam->dev); - tascam->dev =3D NULL; + if (tascam) { + kfifo_free(&tascam->midi_in_fifo); + if (tascam->dev) { + usb_put_dev(tascam->dev); + tascam->dev =3D NULL; + } + } +} + +/** + * tascam_suspend() - Handles device suspension. + * @intf: The USB interface being suspended. + * @message: Power management message. + * + * This function is called when the device is suspended. It stops all acti= ve + * streams, kills all URBs, and sends a vendor-specific deep sleep command + * to the device to ensure a stable low-power state. + * + * Return: 0 on success. + */ +static int tascam_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct tascam_card *tascam =3D usb_get_intfdata(intf); + + if (!tascam) + return 0; + + snd_pcm_suspend_all(tascam->pcm); + + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_in_work); + cancel_work_sync(&tascam->midi_out_work); + cancel_work_sync(&tascam->stop_pcm_work); + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + + return 0; +} + +/** + * tascam_resume() - Handles device resumption from suspend. + * @intf: The USB interface being resumed. + * + * This function is called when the device resumes from suspend. It + * re-establishes the active USB interface settings and re-configures the = sample + * rate if it was previously active. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_resume(struct usb_interface *intf) +{ + struct tascam_card *tascam =3D usb_get_intfdata(intf); + int err; + + if (!tascam) + return 0; + + dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); + + /* + * The device requires a full re-initialization sequence upon resume. + * First, re-establish the active USB interface settings. + */ + err =3D usb_set_interface(tascam->dev, 0, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 0: %d\n", + err); + return err; } + err =3D usb_set_interface(tascam->dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 1: %d\n", + err); + return err; + } + + /* Re-configure the sample rate if one was previously active */ + if (tascam->current_rate > 0) + us144mkii_configure_device_for_rate(tascam, + tascam->current_rate); + + return 0; +} + +static void tascam_error_timer(struct timer_list *t) +{ + struct tascam_card *tascam =3D + container_of(t, struct tascam_card, error_timer); + + if (atomic_read(&tascam->midi_in_active)) + schedule_work(&tascam->midi_in_work); + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); } =20 /** @@ -235,8 +393,8 @@ static void tascam_card_private_free(struct snd_card *c= ard) * - Checking for the second interface (MIDI) and associating it. * - Performing a vendor-specific handshake with the device. * - Setting alternate settings for USB interfaces. - * - Creating and registering the ALSA sound card and PCM device. - * - Allocating and initializing URBs for audio transfers. + * - Creating and registering the ALSA sound card, PCM device, and MIDI de= vice. + * - Allocating and initializing URBs for audio and MIDI transfers. * * Return: 0 on success, or a negative error code on failure. */ @@ -326,27 +484,25 @@ static int tascam_probe(struct usb_interface *intf, tascam->iface0 =3D intf; =20 spin_lock_init(&tascam->lock); + spin_lock_init(&tascam->midi_in_lock); + spin_lock_init(&tascam->midi_out_lock); init_usb_anchor(&tascam->playback_anchor); init_usb_anchor(&tascam->capture_anchor); init_usb_anchor(&tascam->feedback_anchor); + init_usb_anchor(&tascam->midi_in_anchor); + init_usb_anchor(&tascam->midi_out_anchor); + + timer_setup(&tascam->error_timer, tascam_error_timer, 0); =20 INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); INIT_WORK(&tascam->capture_work, tascam_capture_work_handler); + init_completion(&tascam->midi_out_drain_completion); =20 - err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); - if (err < 0) - goto free_card; - tascam->pcm->private_data =3D tascam; - strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); - - err =3D tascam_init_pcm(tascam->pcm); - if (err < 0) - goto free_card; - - err =3D tascam_alloc_urbs(tascam); - if (err < 0) - goto free_card; + if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL)) { + snd_card_free(card); + return -ENOMEM; + } =20 strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { @@ -363,6 +519,28 @@ static int tascam_probe(struct usb_interface *intf, card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct, dev_name(&dev->dev)); =20 + err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); + if (err < 0) + goto free_card; + tascam->pcm->private_data =3D tascam; + strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); + + err =3D tascam_init_pcm(tascam->pcm); + if (err < 0) + goto free_card; + + err =3D tascam_create_midi(tascam); + if (err < 0) + goto free_card; + + err =3D tascam_create_controls(tascam); + if (err < 0) + goto free_card; + + err =3D tascam_alloc_urbs(tascam); + if (err < 0) + goto free_card; + err =3D snd_card_register(card); if (err < 0) goto free_card; @@ -383,8 +561,8 @@ static int tascam_probe(struct usb_interface *intf, * @intf: The USB interface being disconnected. * * This function is called when the device is disconnected from the system. - * It cleans up all allocated resources by freeing the sound card, which in - * turn triggers freeing of URBs and other resources. + * It cleans up all allocated resources, including killing URBs, freeing + * the sound card, and releasing memory. */ static void tascam_disconnect(struct usb_interface *intf) { @@ -394,89 +572,26 @@ static void tascam_disconnect(struct usb_interface *i= ntf) return; =20 if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { + /* Ensure all deferred work is complete before freeing resources */ snd_card_disconnect(tascam->card); cancel_work_sync(&tascam->stop_work); cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_in_work); + cancel_work_sync(&tascam->midi_out_work); cancel_work_sync(&tascam->stop_pcm_work); + + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + timer_delete_sync(&tascam->error_timer); tascam_free_urbs(tascam); snd_card_free(tascam->card); dev_idx--; } } =20 -/** - * tascam_suspend() - Handles device suspension. - * @intf: The USB interface being suspended. - * @message: Power management message. - * - * This function is called when the device is suspended. It stops all acti= ve - * streams and kills all URBs. - * - * Return: 0 on success. - */ -static int tascam_suspend(struct usb_interface *intf, pm_message_t message) -{ - struct tascam_card *tascam =3D usb_get_intfdata(intf); - - if (!tascam) - return 0; - - snd_pcm_suspend_all(tascam->pcm); - - cancel_work_sync(&tascam->stop_work); - cancel_work_sync(&tascam->capture_work); - cancel_work_sync(&tascam->stop_pcm_work); - usb_kill_anchored_urbs(&tascam->playback_anchor); - usb_kill_anchored_urbs(&tascam->capture_anchor); - usb_kill_anchored_urbs(&tascam->feedback_anchor); - - return 0; -} - -/** - * tascam_resume() - Handles device resumption from suspend. - * @intf: The USB interface being resumed. - * - * This function is called when the device resumes from suspend. It - * re-establishes the active USB interface settings and re-configures the = sample - * rate if it was previously active. - * - * Return: 0 on success, or a negative error code on failure. - */ -static int tascam_resume(struct usb_interface *intf) -{ - struct tascam_card *tascam =3D usb_get_intfdata(intf); - int err; - - if (!tascam) - return 0; - - dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); - - /* Re-establish the active USB interface settings. */ - err =3D usb_set_interface(tascam->dev, 0, 1); - if (err < 0) { - dev_err(&intf->dev, - "resume: failed to set alt setting on intf 0: %d\n", - err); - return err; - } - err =3D usb_set_interface(tascam->dev, 1, 1); - if (err < 0) { - dev_err(&intf->dev, - "resume: failed to set alt setting on intf 1: %d\n", - err); - return err; - } - - /* Re-configure the sample rate if one was previously active */ - if (tascam->current_rate > 0) - us144mkii_configure_device_for_rate(tascam, - tascam->current_rate); - - return 0; -} - static const struct usb_device_id tascam_usb_ids[] =3D { { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144) }, { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) }, diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index 34b9b275b905..c740a0b5a0ea 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -4,13 +4,18 @@ #ifndef __US144MKII_H #define __US144MKII_H =20 +#include +#include #include #include +#include #include #include #include +#include =20 #define DRIVER_NAME "us144mkii" +#define DRIVER_VERSION "1.7.6" =20 /* --- USB Device Identification --- */ #define USB_VID_TASCAM 0x0644 @@ -20,13 +25,15 @@ /* --- USB Endpoints (Alternate Setting 1) --- */ #define EP_PLAYBACK_FEEDBACK 0x81 #define EP_AUDIO_OUT 0x02 +#define EP_MIDI_IN 0x83 +#define EP_MIDI_OUT 0x04 #define EP_AUDIO_IN 0x86 =20 /* --- USB Control Message Protocol --- */ #define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) #define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) -#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) #define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVIC= E) +#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) =20 enum uac_request { UAC_SET_CUR =3D 0x01, @@ -48,6 +55,8 @@ enum tascam_mode_value { MODE_VAL_STREAM_START =3D 0x0030, }; =20 +#define HANDSHAKE_SUCCESS_VAL 0x12 + enum tascam_register { REG_ADDR_UNKNOWN_0D =3D 0x0d04, REG_ADDR_UNKNOWN_0E =3D 0x0e00, @@ -70,6 +79,11 @@ enum tascam_register { #define NUM_CAPTURE_URBS 8 #define CAPTURE_URB_SIZE 512 #define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4) +#define NUM_MIDI_IN_URBS 4 +#define MIDI_IN_BUF_SIZE 64 +#define MIDI_IN_FIFO_SIZE (MIDI_IN_BUF_SIZE * NUM_MIDI_IN_URBS) +#define MIDI_OUT_BUF_SIZE 64 +#define NUM_MIDI_OUT_URBS 4 #define USB_CTRL_TIMEOUT_MS 1000 #define FEEDBACK_SYNC_LOSS_THRESHOLD 41 =20 @@ -85,10 +99,6 @@ enum tascam_register { #define FRAMES_PER_DECODE_BLOCK 8 #define RAW_BYTES_PER_DECODE_BLOCK 512 =20 -struct tascam_card; - -#include "us144mkii_pcm.h" - /** * struct us144mkii_frame_pattern_observer - State for dynamic feedback * patterns. @@ -117,6 +127,7 @@ struct us144mkii_frame_pattern_observer { * @iface1: Pointer to USB interface 1 (MIDI). * @card: Pointer to the ALSA sound card instance. * @pcm: Pointer to the ALSA PCM device. + * @rmidi: Pointer to the ALSA rawmidi device. * * @playback_substream: Pointer to the active playback PCM substream. * @playback_urbs: Array of URBs for playback. @@ -142,14 +153,35 @@ struct us144mkii_frame_pattern_observer { * @capture_decode_dst_block: Buffer for decoded 32-bit capture samples. * @capture_routing_buffer: Intermediate buffer for capture routing. * @capture_work: Work struct for deferred capture processing. - * * @stop_work: Work struct for deferred stream stopping. * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. * xrun). * + * @midi_in_substream: Pointer to the active MIDI input substream. + * @midi_out_substream: Pointer to the active MIDI output substream. + * @midi_in_urbs: Array of URBs for MIDI input. + * @midi_out_urbs: Array of URBs for MIDI output. + * @midi_in_active: Atomic flag indicating if MIDI input is active. + * @midi_out_active: Atomic flag indicating if MIDI output is active. + * @midi_in_fifo: FIFO for raw MIDI input data. + * @midi_in_work: Work struct for deferred MIDI input processing. + * @midi_out_work: Work struct for deferred MIDI output processing. + * @midi_in_lock: Spinlock for MIDI input FIFO. + * @midi_out_lock: Spinlock for MIDI output. + * @midi_out_urbs_in_flight: Bitmap of MIDI output URBs currently in fligh= t. + * @midi_running_status: Stores the last MIDI status byte for running stat= us. + * @error_timer: Timer for MIDI error retry logic. + * * @lock: Main spinlock for protecting shared driver state. * @active_urbs: Atomic counter for active URBs. * @current_rate: Currently configured sample rate of the device. + * @line_out_source: Source for Line Outputs (0: Playback 1-2, 1: Playback= 3-4). + * @digital_out_source: Source for Digital Outputs (0: Playback 1-2, 1: Pl= ayback + * 3-4). + * @capture_12_source: Source for Capture channels 1-2 (0: Analog In, 1: D= igital + * In). + * @capture_34_source: Source for Capture channels 3-4 (0: Analog In, 1: D= igital + * In). * * @feedback_accumulator_pattern: Stores the calculated frames per packet = for * feedback. @@ -164,6 +196,8 @@ struct us144mkii_frame_pattern_observer { * @playback_anchor: USB anchor for playback URBs. * @capture_anchor: USB anchor for capture URBs. * @feedback_anchor: USB anchor for feedback URBs. + * @midi_in_anchor: USB anchor for MIDI input URBs. + * @midi_out_anchor: USB anchor for MIDI output URBs. */ struct tascam_card { /* --- Core device pointers --- */ @@ -172,6 +206,7 @@ struct tascam_card { struct usb_interface *iface1; struct snd_card *card; struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; =20 /* --- PCM Substreams --- */ struct snd_pcm_substream *playback_substream; @@ -184,9 +219,13 @@ struct tascam_card { size_t feedback_urb_alloc_size; struct urb *capture_urbs[NUM_CAPTURE_URBS]; size_t capture_urb_alloc_size; + struct urb *midi_in_urbs[NUM_MIDI_IN_URBS]; + struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS]; struct usb_anchor playback_anchor; struct usb_anchor capture_anchor; struct usb_anchor feedback_anchor; + struct usb_anchor midi_in_anchor; + struct usb_anchor midi_out_anchor; =20 /* --- Stream State --- */ spinlock_t lock; @@ -211,6 +250,19 @@ struct tascam_card { s32 *capture_decode_dst_block; s32 *capture_routing_buffer; =20 + /* --- MIDI State --- */ + struct snd_rawmidi_substream *midi_in_substream; + struct snd_rawmidi_substream *midi_out_substream; + atomic_t midi_in_active; + atomic_t midi_out_active; + struct kfifo midi_in_fifo; + spinlock_t midi_in_lock; + spinlock_t midi_out_lock; + unsigned long midi_out_urbs_in_flight; + u8 midi_running_status; + struct timer_list error_timer; + struct completion midi_out_drain_completion; + /* --- Feedback Sync State --- */ unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; unsigned int feedback_pattern_out_idx; @@ -224,14 +276,23 @@ struct tascam_card { struct work_struct stop_work; struct work_struct stop_pcm_work; struct work_struct capture_work; + struct work_struct midi_in_work; + struct work_struct midi_out_work; + + /* --- Mixer/Routing State --- */ + unsigned int line_out_source; + unsigned int digital_out_source; + unsigned int capture_12_source; + unsigned int capture_34_source; }; =20 +/* main.c */ /** * tascam_free_urbs() - Free all allocated URBs and associated buffers. * @tascam: the tascam_card instance * - * This function kills, unlinks, and frees all playback, feedback, and - * capture URBs, along with their transfer buffers and the capture + * This function kills, unlinks, and frees all playback, feedback, capture, + * and MIDI URBs, along with their transfer buffers and the capture * ring/decode buffers. */ void tascam_free_urbs(struct tascam_card *tascam); @@ -241,7 +302,7 @@ void tascam_free_urbs(struct tascam_card *tascam); * @tascam: the tascam_card instance * * This function allocates and initializes all URBs for playback, feedback, - * and capture, as well as the necessary buffers for data processing. + * capture, and MIDI, as well as the necessary buffers for data processing. * * Return: 0 on success, or a negative error code on failure. */ @@ -256,4 +317,51 @@ int tascam_alloc_urbs(struct tascam_card *tascam); */ void tascam_stop_work_handler(struct work_struct *work); =20 +/* us144mkii_pcm.h */ +#include "us144mkii_pcm.h" + +/* us144mkii_midi.c */ +/** + * tascam_midi_in_urb_complete() - Completion handler for MIDI IN URBs + * @urb: The completed URB. + * + * This function runs in interrupt context. It places the raw data from the + * USB endpoint into a kfifo and schedules a work item to process it later, + * ensuring the interrupt handler remains fast. + */ +void tascam_midi_in_urb_complete(struct urb *urb); + +/** + * tascam_midi_out_urb_complete() - Completion handler for MIDI OUT bulk U= RB. + * @urb: The completed URB. + * + * This function runs in interrupt context. It marks the output URB as no + * longer in-flight. It then re-schedules the work handler to check for and + * send any more data waiting in the ALSA buffer. This is a safe, non-bloc= king + * way to continue the data transmission chain. + */ +void tascam_midi_out_urb_complete(struct urb *urb); + +/** + * tascam_create_midi() - Create and initialize the ALSA rawmidi device. + * @tascam: The driver instance. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_create_midi(struct tascam_card *tascam); + +/* us144mkii_controls.c */ +/** + * tascam_create_controls() - Creates and adds ALSA mixer controls for the + * device. + * @tascam: The driver instance. + * + * This function registers custom ALSA controls for managing audio routing + * (line out source, digital out source, capture 1-2 source, capture 3-4 s= ource) + * and displaying the current sample rate. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_create_controls(struct tascam_card *tascam); + #endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_controls.c b/sound/usb/usx2y/us144mk= ii_controls.c new file mode 100644 index 000000000000..b6ad1b5a4bf5 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_controls.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * @brief Text descriptions for playback output source options. + * + * Used by ALSA kcontrol elements to provide user-friendly names for + * the playback routing options (e.g., "Playback 1-2", "Playback 3-4"). + */ +static const char *const playback_source_texts[] =3D { "Playback 1-2", + "Playback 3-4" }; + +/** + * @brief Text descriptions for capture input source options. + * + * Used by ALSA kcontrol elements to provide user-friendly names for + * the capture routing options (e.g., "Analog In", "Digital In"). + */ +static const char *const capture_source_texts[] =3D { "Analog In", "Digita= l In" }; + +/** + * tascam_playback_source_info() - ALSA control info callback for playback + * source. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the enumerated playback source + * control, including its type, count, and available items (Playback 1-2, + * Playback 3-4). + * + * Return: 0 on success. + */ +static int tascam_playback_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, playback_source_texts); +} + +/** + * tascam_line_out_get() - ALSA control get callback for Line Outputs Sour= ce. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Line Outputs sour= ce + * (Playback 1-2 or Playback 3-4) from the driver's private data and popul= ates + * the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_line_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->line_out_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_line_out_put() - ALSA control put callback for Line Outputs Sour= ce. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Line Outputs source (Playback 1-2 or Playback 3-= 4) + * based on the user's selection from the ALSA control element. It validat= es + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_line_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->line_out_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->line_out_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_line_out_control - ALSA kcontrol definition for Line Outputs Sou= rce. + * + * This defines a new ALSA mixer control named "Line OUTPUTS Source" that = allows + * the user to select between "Playback 1-2" and "Playback 3-4" for the an= alog + * line outputs of the device. It uses the `tascam_playback_source_info` f= or + * information and `tascam_line_out_get`/`tascam_line_out_put` for value + * handling. + */ +static const struct snd_kcontrol_new tascam_line_out_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Line Playback Source", + .info =3D tascam_playback_source_info, + .get =3D tascam_line_out_get, + .put =3D tascam_line_out_put, +}; + +/** + * tascam_digital_out_get() - ALSA control get callback for Digital Outputs + * Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Digital Outputs s= ource + * (Playback 1-2 or Playback 3-4) from the driver's private data and popul= ates + * the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_digital_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->digital_out_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_digital_out_put() - ALSA control put callback for Digital Outputs + * Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Digital Outputs source (Playback 1-2 or Playback= 3-4) + * based on the user's selection from the ALSA control element. It validat= es + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_digital_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->digital_out_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->digital_out_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_digital_out_control - ALSA kcontrol definition for Digital Outpu= ts + * Source. + * + * This defines a new ALSA mixer control named "Digital OUTPUTS Source" th= at + * allows the user to select between "Playback 1-2" and "Playback 3-4" for= the + * digital outputs of the device. It uses the `tascam_playback_source_info= ` for + * information and `tascam_digital_out_get`/`tascam_digital_out_put` for v= alue + * handling. + */ +static const struct snd_kcontrol_new tascam_digital_out_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Digital Playback Source", + .info =3D tascam_playback_source_info, + .get =3D tascam_digital_out_get, + .put =3D tascam_digital_out_put, +}; + +/** + * tascam_capture_source_info() - ALSA control info callback for capture s= ource. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the enumerated capture source + * control, including its type, count, and available items (Analog In, Dig= ital + * In). + * + * Return: 0 on success. + */ +static int tascam_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, capture_source_texts); +} + +/** + * tascam_capture_12_get() - ALSA control get callback for Capture channel= s 1 + * and 2 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Capture channels = 1 and + * 2 source (Analog In or Digital In) from the driver's private data and + * populates the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_capture_12_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->capture_12_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_capture_12_put() - ALSA control put callback for Capture channel= s 1 + * and 2 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Capture channels 1 and 2 source (Analog In or Di= gital + * In) based on the user's selection from the ALSA control element. It val= idates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_capture_12_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->capture_12_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->capture_12_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_capture_12_control - ALSA kcontrol definition for Capture channe= ls 1 + * and 2 Source. + * + * This defines a new ALSA mixer control named "ch1 and ch2 Source" that a= llows + * the user to select between "Analog In" and "Digital In" for the first t= wo + * capture channels of the device. It uses the `tascam_capture_source_info= ` for + * information and `tascam_capture_12_get`/`tascam_capture_12_put` for val= ue + * handling. + */ +static const struct snd_kcontrol_new tascam_capture_12_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Ch1/2 Capture Source", + .info =3D tascam_capture_source_info, + .get =3D tascam_capture_12_get, + .put =3D tascam_capture_12_put, +}; + +/** + * tascam_capture_34_get() - ALSA control get callback for Capture channel= s 3 + * and 4 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Capture channels = 3 and + * 4 source (Analog In or Digital In) from the driver's private data and + * populates the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_capture_34_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->capture_34_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_capture_34_put() - ALSA control put callback for Capture channel= s 3 + * and 4 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Capture channels 3 and 4 source (Analog In or Di= gital + * In) based on the user's selection from the ALSA control element. It val= idates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_capture_34_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->capture_34_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->capture_34_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_capture_34_control - ALSA kcontrol definition for Capture channe= ls 3 + * and 4 Source. + * + * This defines a new ALSA mixer control named "ch3 and ch4 Source" that a= llows + * the user to select between "Analog In" and "Digital In" for the third a= nd + * fourth capture channels of the device. It uses the + * `tascam_capture_source_info` for information and + * `tascam_capture_34_get`/`tascam_capture_34_put` for value handling. + */ +static const struct snd_kcontrol_new tascam_capture_34_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Ch3/4 Capture Source", + .info =3D tascam_capture_source_info, + .get =3D tascam_capture_34_get, + .put =3D tascam_capture_34_put, +}; + +/** + * tascam_samplerate_info() - ALSA control info callback for Sample Rate. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the Sample Rate control, defin= ing + * it as an integer type with a minimum value of 0 and a maximum of 96000. + * + * Return: 0 on success. + */ +static int tascam_samplerate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count =3D 1; + uinfo->value.integer.min =3D 0; + uinfo->value.integer.max =3D 96000; + return 0; +} + +/** + * tascam_samplerate_get() - ALSA control get callback for Sample Rate. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current sample rate from the device via a U= SB + * control message and populates the ALSA control element value. If the ra= te + * is already known (i.e., `current_rate` is set), it returns that value + * directly. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_samplerate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D + (struct tascam_card *)snd_kcontrol_chip(kcontrol); + u8 *buf __free(kfree) =3D NULL; + int err; + u32 rate =3D 0; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->current_rate > 0) { + ucontrol->value.integer.value[0] =3D tascam->current_rate; + return 0; + } + // Lock is released here before kmalloc and usb_control_msg + + buf =3D kmalloc(3, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + err =3D usb_control_msg(tascam->dev, usb_rcvctrlpipe(tascam->dev, 0), + UAC_GET_CUR, RT_D2H_CLASS_EP, + UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_IN, buf, 3, + USB_CTRL_TIMEOUT_MS); + + if (err >=3D 3) + rate =3D buf[0] | (buf[1] << 8) | (buf[2] << 16); + + ucontrol->value.integer.value[0] =3D rate; + return 0; +} + +/** + * tascam_samplerate_control - ALSA kcontrol definition for Sample Rate. + * + * This defines a new ALSA mixer control named "Sample Rate" that displays + * the current sample rate of the device. It is a read-only control. + */ +static const struct snd_kcontrol_new tascam_samplerate_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Sample Rate", + .info =3D tascam_samplerate_info, + .get =3D tascam_samplerate_get, + .access =3D SNDRV_CTL_ELEM_ACCESS_READ, +}; + +int tascam_create_controls(struct tascam_card *tascam) +{ + int err; + + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_line_out_control, tascam)); + if (err < 0) + return err; + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_digital_out_control, tascam)); + if (err < 0) + return err; + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_capture_12_control, tascam)); + if (err < 0) + return err; + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_capture_34_control, tascam)); + if (err < 0) + return err; + + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_samplerate_control, tascam)); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_midi.c b/sound/usb/usx2y/us144mkii_m= idi.c new file mode 100644 index 000000000000..36a05d52a8c8 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_midi.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * tascam_midi_in_work_handler() - Deferred work for processing MIDI input. + * @work: The work_struct instance. + * + * This function runs in a thread context. It safely reads raw USB data fr= om + * the kfifo, processes it by stripping protocol-specific padding bytes, a= nd + * passes the clean MIDI data to the ALSA rawmidi subsystem. + */ +static void tascam_midi_in_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, midi_in_work); + u8 buf[9]; + u8 clean_buf[8]; + unsigned int count, clean_count; + + if (!tascam->midi_in_substream) + return; + + while (kfifo_out_spinlocked(&tascam->midi_in_fifo, buf, sizeof(buf), + &tascam->midi_in_lock) =3D=3D sizeof(buf)) { + clean_count =3D 0; + for (count =3D 0; count < 8; ++count) { + if (buf[count] !=3D 0xfd) + clean_buf[clean_count++] =3D buf[count]; + } + + if (clean_count > 0) + snd_rawmidi_receive(tascam->midi_in_substream, + clean_buf, clean_count); + } +} + +void tascam_midi_in_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + int ret; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -EPROTO) { + dev_err_ratelimited(tascam->card->dev, + "MIDI IN URB failed: status %d\n", + urb->status); + } + goto out; + } + + if (tascam && atomic_read(&tascam->midi_in_active) && + urb->actual_length > 0) { + kfifo_in_spinlocked(&tascam->midi_in_fifo, urb->transfer_buffer, + urb->actual_length, &tascam->midi_in_lock); + schedule_work(&tascam->midi_in_work); + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->midi_in_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(tascam->card->dev, + "Failed to resubmit MIDI IN URB: error %d\n", ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + } +out: + usb_put_urb(urb); +} + +/** + * tascam_midi_in_open() - Opens the MIDI input substream. + * @substream: The ALSA rawmidi substream to open. + * + * This function stores a reference to the MIDI input substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_midi_in_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + + tascam->midi_in_substream =3D substream; + return 0; +} + +/** + * tascam_midi_in_close() - Closes the MIDI input substream. + * @substream: The ALSA rawmidi substream to close. + * + * Return: 0 on success. + */ +static int tascam_midi_in_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/** + * tascam_midi_in_trigger() - Triggers MIDI input stream activity. + * @substream: The ALSA rawmidi substream. + * @up: Boolean indicating whether to start (1) or stop (0) the stream. + * + * This function starts or stops the MIDI input URBs based on the 'up' + * parameter. When starting, it resets the kfifo and submits all MIDI input + * URBs. When stopping, it kills all anchored MIDI input URBs and cancels = the + * associated workqueue. + */ +static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + int i, err; + + if (up) { + if (atomic_xchg(&tascam->midi_in_active, 1) =3D=3D 0) { + { + guard(spinlock_irqsave)(&tascam->midi_in_lock); + kfifo_reset(&tascam->midi_in_fifo); + } + + for (i =3D 0; i < NUM_MIDI_IN_URBS; i++) { + usb_get_urb(tascam->midi_in_urbs[i]); + usb_anchor_urb(tascam->midi_in_urbs[i], + &tascam->midi_in_anchor); + err =3D usb_submit_urb(tascam->midi_in_urbs[i], + GFP_KERNEL); + if (err < 0) { + dev_err(tascam->card->dev, + "Failed to submit MIDI IN URB %d: %d\n", + i, err); + usb_unanchor_urb( + tascam->midi_in_urbs[i]); + usb_put_urb(tascam->midi_in_urbs[i]); + } + } + } + } else { + if (atomic_xchg(&tascam->midi_in_active, 0) =3D=3D 1) { + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + cancel_work_sync(&tascam->midi_in_work); + } + } +} + +/** + * tascam_midi_in_ops - ALSA rawmidi operations for MIDI input. + * + * This structure defines the callback functions for MIDI input stream + * operations, including open, close, and trigger. + */ +static const struct snd_rawmidi_ops tascam_midi_in_ops =3D { + .open =3D tascam_midi_in_open, + .close =3D tascam_midi_in_close, + .trigger =3D tascam_midi_in_trigger, +}; + +void tascam_midi_out_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + int i, urb_index =3D -1; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN) { + dev_err_ratelimited(tascam->card->dev, + "MIDI OUT URB failed: %d\n", + urb->status); + } + goto out; + } + + if (!tascam) + goto out; + + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i] =3D=3D urb) { + urb_index =3D i; + break; + } + } + + if (urb_index < 0) { + dev_err_ratelimited(tascam->card->dev, + "Unknown MIDI OUT URB completed!\n"); + goto out; + } + + { + guard(spinlock_irqsave)(&tascam->midi_out_lock); + clear_bit(urb_index, &tascam->midi_out_urbs_in_flight); + } + + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); + +out: + usb_put_urb(urb); +} + +/** + * tascam_midi_out_work_handler() - Deferred work for sending MIDI data + * @work: The work_struct instance. + * + * This function handles the proprietary output protocol: take the raw MIDI + * message bytes from the application, place them at the start of a 9-byte + * buffer, pad the rest with 0xFD, and add a terminator byte (0x00). + * This function pulls as many bytes as will fit into one packet from the + * ALSA buffer and sends them. + */ +static void tascam_midi_out_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, midi_out_work); + struct snd_rawmidi_substream *substream =3D tascam->midi_out_substream; + int i; + + if (!substream || !atomic_read(&tascam->midi_out_active)) + return; + + while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) =3D=3D 1) { + int urb_index; + struct urb *urb; + u8 *buf; + int bytes_to_send; + + { + guard(spinlock_irqsave)(&tascam->midi_out_lock); + + urb_index =3D -1; + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (!test_bit( + i, + &tascam->midi_out_urbs_in_flight)) { + urb_index =3D i; + break; + } + } + + if (urb_index < 0) + return; /* No free URBs, will be rescheduled by + * completion handler + */ + + urb =3D tascam->midi_out_urbs[urb_index]; + buf =3D urb->transfer_buffer; + bytes_to_send =3D snd_rawmidi_transmit(substream, buf, 8); + + if (bytes_to_send <=3D 0) + break; /* No more data */ + + if (bytes_to_send < 9) + memset(buf + bytes_to_send, 0xfd, + 9 - bytes_to_send); + buf[8] =3D 0x00; + + set_bit(urb_index, &tascam->midi_out_urbs_in_flight); + urb->transfer_buffer_length =3D 9; + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->midi_out_anchor); + if (usb_submit_urb(urb, GFP_KERNEL) < 0) { + dev_err_ratelimited( + tascam->card->dev, + "Failed to submit MIDI OUT URB %d\n", + urb_index); + { + guard(spinlock_irqsave)(&tascam->midi_out_lock); + clear_bit(urb_index, + &tascam->midi_out_urbs_in_flight); + } + usb_unanchor_urb(urb); + usb_put_urb(urb); + break; /* Stop on error */ + } + } +} + +/** + * tascam_midi_out_open() - Opens the MIDI output substream. + * @substream: The ALSA rawmidi substream to open. + * + * This function stores a reference to the MIDI output substream in the + * driver's private data and initializes the MIDI running status. + * + * Return: 0 on success. + */ +static int tascam_midi_out_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + + tascam->midi_out_substream =3D substream; + /* Initialize the running status state for the packet packer. */ + tascam->midi_running_status =3D 0; + return 0; +} + +/** + * tascam_midi_out_close() - Closes the MIDI output substream. + * @substream: The ALSA rawmidi substream to close. + * + * Return: 0 on success. + */ +static int tascam_midi_out_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/** + * tascam_midi_out_drain() - Drains the MIDI output stream. + * @substream: The ALSA rawmidi substream. + * + * This function cancels any pending MIDI output work and kills all + * anchored MIDI output URBs, ensuring all data is sent or discarded. + */ +static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + bool in_flight =3D true; + + while (in_flight) { + in_flight =3D false; + for (int i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (test_bit(i, &tascam->midi_out_urbs_in_flight)) { + in_flight =3D true; + break; + } + } + if (in_flight) + schedule_timeout_uninterruptible(1); + } + + cancel_work_sync(&tascam->midi_out_work); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); +} + +/** + * tascam_midi_out_trigger() - Triggers MIDI output stream activity. + * @substream: The ALSA rawmidi substream. + * @up: Boolean indicating whether to start (1) or stop (0) the stream. + * + * This function starts or stops the MIDI output workqueue based on the + * 'up' parameter. + */ +static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substrea= m, + int up) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + + if (up) { + atomic_set(&tascam->midi_out_active, 1); + schedule_work(&tascam->midi_out_work); + } else { + atomic_set(&tascam->midi_out_active, 0); + } +} + +/** + * tascam_midi_out_ops - ALSA rawmidi operations for MIDI output. + * + * This structure defines the callback functions for MIDI output stream + * operations, including open, close, trigger, and drain. + */ +static const struct snd_rawmidi_ops tascam_midi_out_ops =3D { + .open =3D tascam_midi_out_open, + .close =3D tascam_midi_out_close, + .trigger =3D tascam_midi_out_trigger, + .drain =3D tascam_midi_out_drain, +}; + +int tascam_create_midi(struct tascam_card *tascam) +{ + int err; + + err =3D snd_rawmidi_new(tascam->card, "US144MKII MIDI", 0, 1, 1, + &tascam->rmidi); + if (err < 0) + return err; + + strscpy(tascam->rmidi->name, "US144MKII MIDI", + sizeof(tascam->rmidi->name)); + tascam->rmidi->private_data =3D tascam; + + snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &tascam_midi_in_ops); + snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &tascam_midi_out_ops); + + tascam->rmidi->info_flags |=3D SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + INIT_WORK(&tascam->midi_in_work, tascam_midi_in_work_handler); + INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler); + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c index 7bee8b4210a6..61d776b4a04e 100644 --- a/sound/usb/usx2y/us144mkii_pcm.c +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -56,19 +56,60 @@ void process_playback_routing_us144mkii(struct tascam_c= ard *tascam, const u8 *src_buffer, u8 *dst_buffer, size_t frames) { - /* This is a stub. Routing will be added in a later commit. */ - if (src_buffer !=3D dst_buffer) - memcpy(dst_buffer, src_buffer, frames * BYTES_PER_FRAME); + size_t f; + const u8 *src_12, *src_34; + u8 *dst_line, *dst_digital; + + for (f =3D 0; f < frames; ++f) { + src_12 =3D src_buffer + f * BYTES_PER_FRAME; + src_34 =3D src_12 + (2 * BYTES_PER_SAMPLE); + dst_line =3D dst_buffer + f * BYTES_PER_FRAME; + dst_digital =3D dst_line + (2 * BYTES_PER_SAMPLE); + + /* LINE OUTPUTS (ch1/2 on device) */ + if (tascam->line_out_source =3D=3D 0) /* "ch1 and ch2" */ + memcpy(dst_line, src_12, 2 * BYTES_PER_SAMPLE); + else /* "ch3 and ch4" */ + memcpy(dst_line, src_34, 2 * BYTES_PER_SAMPLE); + + /* DIGITAL OUTPUTS (ch3/4 on device) */ + if (tascam->digital_out_source =3D=3D 0) /* "ch1 and ch2" */ + memcpy(dst_digital, src_12, 2 * BYTES_PER_SAMPLE); + else /* "ch3 and ch4" */ + memcpy(dst_digital, src_34, 2 * BYTES_PER_SAMPLE); + } } =20 void process_capture_routing_us144mkii(struct tascam_card *tascam, const s32 *decoded_block, s32 *routed_block) { - /* This is a stub. Routing will be added in a later commit. */ - memcpy(routed_block, decoded_block, - FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * - DECODED_SAMPLE_SIZE); + int f; + const s32 *src_frame; + s32 *dst_frame; + + for (f =3D 0; f < FRAMES_PER_DECODE_BLOCK; f++) { + src_frame =3D decoded_block + (f * DECODED_CHANNELS_PER_FRAME); + dst_frame =3D routed_block + (f * DECODED_CHANNELS_PER_FRAME); + + /* ch1 and ch2 Source */ + if (tascam->capture_12_source =3D=3D 0) { /* analog inputs */ + dst_frame[0] =3D src_frame[0]; /* Analog L */ + dst_frame[1] =3D src_frame[1]; /* Analog R */ + } else { /* digital inputs */ + dst_frame[0] =3D src_frame[2]; /* Digital L */ + dst_frame[1] =3D src_frame[3]; /* Digital R */ + } + + /* ch3 and ch4 Source */ + if (tascam->capture_34_source =3D=3D 0) { /* analog inputs */ + dst_frame[2] =3D src_frame[0]; /* Analog L (Duplicate) */ + dst_frame[3] =3D src_frame[1]; /* Analog R (Duplicate) */ + } else { /* digital inputs */ + dst_frame[2] =3D src_frame[2]; /* Digital L */ + dst_frame[3] =3D src_frame[3]; /* Digital R */ + } + } } =20 int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te) @@ -169,14 +210,12 @@ int us144mkii_configure_device_for_rate(struct tascam= _card *tascam, int rate) if (err < 0) goto fail; =20 - kfree(rate_payload_buf); return 0; =20 fail: dev_err(&dev->dev, "Device configuration failed at rate %d with error %d\n", rate, err); - kfree(rate_payload_buf); return err; } =20 --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ed1-f44.google.com (mail-ed1-f44.google.com [209.85.208.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D7DD260578; Tue, 12 Aug 2025 12:56:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003409; cv=none; b=ikeg4MyhIIU3jFVfpbudbzJHim0k5/zPpK7N0AI8qmzPaOISR/m+9jBXziwARmxIumnFIFfI+Osy/FEDImEDWn6irF2giuFOz8b/Q1H+71cgnRx7IULTx7rcK/pcQUKYkplf/Jqaioac/xsBJjyBtv4RL2Yp55MZXBQ8f1qyJ3U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003409; c=relaxed/simple; bh=zYImU4jxvzbeYDCcwW8grQtTwXDcIZSor5qvW0OKkxU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=kBe40FhctbtpJySzGngNCY9mZ1nkfMet3HrBPGfRGVlcWIyjj6QCpTJj88vX54catj+jk02hhXwSCMAgTh2ZCh4Mg+KM9EP1J1ErXebHh80pnhjFEcj+/C3ucdXKiDMJKjcINl1dUnd74QiX/eCMY3vjBj3/jAqs4MC3N6xsQKU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=lz2BYieu; arc=none smtp.client-ip=209.85.208.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lz2BYieu" Received: by mail-ed1-f44.google.com with SMTP id 4fb4d7f45d1cf-617b36cc489so10672547a12.0; Tue, 12 Aug 2025 05:56:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003405; x=1755608205; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Bhnbn+hKgeh6Jp1ra5XQn6HuC0UPdIm1P7jBz/93Tj0=; b=lz2BYieuGkAiWRb3aZ9zG4FOGr7B6BeiEPrDpRT6SpxOOnwUaxSE04Dr/kcQqdROBy yea+TdNYRkIvHFGIhd1PIubi0wf514uuLQ3u5jiHzcxtlsq3lmrpA9M776Z8An7pZNP5 To33zAggCueJinibAL+YnzTWyGGQgRGvBspHE5kLNTiOVX9DI5dJ5Pp2VXYq4B0r4l70 QUBZUp1UgAH3PfrORUIRUDJmXhVDKf0U0nWE3N0dzarXG8e8Pi2jGdKrDoauZvheZxbT +jJassJGKQEAaKLbPHs+9Q7vu3JVBDbfpYufQPC6xR7qtD+k5+6fGWXEtNK4JhKKd4HN rZjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003405; x=1755608205; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Bhnbn+hKgeh6Jp1ra5XQn6HuC0UPdIm1P7jBz/93Tj0=; b=hGb65BI9FnJNIte1H0OJeJFsZo4BwlEvIUo6q4aGOeGXUfszXNssOetDFaha+73FUu UW3VSZpbn8GSirzGJacvF+3Jked9/KL5ahuYwUg/8kk/qXpiQfIvBeKV5Jd9XengwQZ5 srd1/nCVwTO6foImZg53reNOli+9zhLmXoIKraLNywUYPNMkbtJ9NXl9U8DCTT6mvFQS gJK2oM6nOGJHJHbjSuFHf7Eug1UcJLrJN+et6UbrRF9MJ8EfGznOERv9EVoxA77pUEgd nl5Y90QWfFh+AeiQJBQJP4o6sx5Z+bcpfZBba+6HSZq2VM8sjlHdvIu7vXug4HhsiM+7 rpUw== X-Forwarded-Encrypted: i=1; AJvYcCUY6a2GtZJiL+7sCsXbRRve76NlxCcEZtZZ8RVPDSj7DnIJjAfS+Hv7AKEuiRdpRQ7EePRjhZKhTgW66g==@vger.kernel.org X-Gm-Message-State: AOJu0Yxpo5tZnc/038xI9D2pBlttgQMRd0TTJ6irxEmnJ1FAQUMShtfa /9Pv+JXpMePiDbv+kYNhFsmaI+yl/XmWSCW2iJstyvlxJljjLGe5Ioz8 X-Gm-Gg: ASbGncuJl2uJy+Mk5VFtUdB6mLAdGcXC0BYXPNJonQ7ZlpU8CeW0t1uvRQtbe4TSAWs XCfhHtiBqiiFTK9QWtTz7vSv31BFiO4QJHVbDcM2bioahNkr4B+RmvMe5mzzqHls326aIhOw2ao ukoIakRSC1Sm1R9OAIfxuPP/JCcwS+DZgM6v+oKxk7PixcXwbzNtoEhdLBy84qBMFuNExkrk2XH /tSBndl0WjxXJfgmh1Vf7AAM40JUl4MZoPq4mEUE3ba6qbhREXEkNnlcn/vkfYE/vjR8YrYhIuo 5YQC6gQhaUGL57xsW9DWwG+7WXpPbpv6tX6aFxTgFOxPZmYGR9vN2evHrZacBpcmo972xPEURdM RscMx2ZqDJD9tknxOY8Hni5Sc3SDi29eMFA6dANFzXZDFaDGTDTy50u09dRl3Ad0Qdy4kIa/Kjv m1Lg89Jw== X-Google-Smtp-Source: AGHT+IFEbzeg47RnZlLER74FWNsQLSf6qWS2thYftTTm+R8uXIUhrDrcY1Qb7lgGzi1WrQiGU1SO2g== X-Received: by 2002:a17:907:9287:b0:ae3:d5f2:393a with SMTP id a640c23a62f3a-afa1e1e82ebmr301526666b.44.1755003405457; Tue, 12 Aug 2025 05:56:45 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:44 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 5/6] ALSA: usb-audio: us144mkii: Add deep sleep command Date: Tue, 12 Aug 2025 14:56:29 +0200 Message-Id: <20250812125633.79270-10-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add a deep sleep vendor command to be sent during suspend, allowing the device to enter a lower power state. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 7 +++++++ sound/usb/usx2y/us144mkii.h | 1 + 2 files changed, 8 insertions(+) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index f7944eb2fb93..e452250fc5b4 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -322,6 +322,13 @@ static int tascam_suspend(struct usb_interface *intf, = pm_message_t message) usb_kill_anchored_urbs(&tascam->midi_in_anchor); usb_kill_anchored_urbs(&tascam->midi_out_anchor); =20 + dev_info(&intf->dev, "sending deep sleep command\n"); + int err =3D usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0), + VENDOR_REQ_DEEP_SLEEP, RT_H2D_VENDOR_DEV, + 0x0000, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS); + if (err < 0) + dev_err(&intf->dev, "deep sleep command failed: %d\n", err); + return 0; } =20 diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index c740a0b5a0ea..ecc4c2fed9e6 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -46,6 +46,7 @@ enum uac_control_selector { =20 enum tascam_vendor_request { VENDOR_REQ_REGISTER_WRITE =3D 0x41, + VENDOR_REQ_DEEP_SLEEP =3D 0x44, VENDOR_REQ_MODE_CONTROL =3D 0x49, }; =20 --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ed1-f51.google.com (mail-ed1-f51.google.com [209.85.208.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1BDDA27CCEE; Tue, 12 Aug 2025 12:56:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003414; cv=none; b=BjVixg+bWJ8R16ItoxfSIVAbvyKqadT0cUfA6s+RSkNKid57KpsBGuyDlmebQJGWySyIVEwZI/LY3hGBWjVXzNhsMf70VPJyQ1A59Bx2hjYEfblIcjU1qN7627WcgxDU/BNv6igJplK5pUFd3jazmqZGemJ5JIP2S6/8GriM5kg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003414; c=relaxed/simple; bh=C3ybyQQGIbjc8NQlG8BH+g0H2+qjw24oDoZwNCvf59o=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=m/fdocoBe+2DOARpwQF7g5j4lV5U4rBqB77r1hxneoLU+DFSjp0UWVSL76JrDfphg4Gf2QqJfGSRNfkjLtX2bVYmJpBHswjPqRX+Qb0wXbvkNZdvrFSFJoe/6fUXUMEbmi5ar1jBD7D4vqPOlcwz4jT93w6JTZy6ODzVORU59JE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=hR4OktPz; arc=none smtp.client-ip=209.85.208.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hR4OktPz" Received: by mail-ed1-f51.google.com with SMTP id 4fb4d7f45d1cf-605b9488c28so9580688a12.2; Tue, 12 Aug 2025 05:56:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003407; x=1755608207; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qvIkb7cGEb/Ghy2S086B7ZlGAa89IXzdViRuKJif9Po=; b=hR4OktPzvkiHtT58drd1ezYHPJgubsRHP+5E1aFgWtkGm2QF5fINZOqGpNGW9wxxZK cOfnQwGrLpEreeMDJo/aKnqq5uGL9HAAmzOtXB9N7dIkQfADwt6dzJBYxC3H+4n1t3Mu zfB7nIpvsx27vY1C6t80SGIGVBiLjqfRMcxAbsKpbebPzr95BcnFuvPHJHF6yYUJSRzS xSot6KjKPoBYM6ZbDDuDBKt6YVz7Amz4rYwjOTEL6ka6KVqxdlfmLwuunTR8mjfVqg0o Y1LOuX78DCrZIFEvzvrrgdbhpjNRGr5d9arQJuIDLt1Iax87bB0W3lKGuDYfQBOWbokT Pz/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003407; x=1755608207; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=qvIkb7cGEb/Ghy2S086B7ZlGAa89IXzdViRuKJif9Po=; b=sAaaDNetJ1NzsRMaQczuEGa+0Az6v7cJlG0cfjadfkLGhJdERb9fnU0mJnqj6RaYEG nJdHvjXKV7JM/s1OYl/0+WtTE7kj+e6AzhvyWmQh7lBoj5tz6R0iz7ciagrNXCXora2A t7vcdkjr8dqCeun7N71a0NDzOXLJ0t+p9cJ6gz90Jyz5wlYF/X6z9+41JiViksqxbvfE KpOBM5DMo1bgru9XalkMTGE4RB0KTiwDE5qT8BNvCnkE2Uz6clWivDc9HydhUhAy/q6f p2yUXCrm/xZ/ADKNHAfzzumcADdrlCZZkNY2wZPbVTNop3xLEq4pYFsXJSTRRT11yuAW F4qg== X-Forwarded-Encrypted: i=1; AJvYcCV63gnmmfHr7gXVaHmYIDDHvveVqJmlHgljvt3fdrOuHSqcYQ0aiKAwouBYRVvHaCAKsGDCEZqm+0Ck7w==@vger.kernel.org X-Gm-Message-State: AOJu0Yw4epCEEIC2T/dbwkN0l1Rq/WBS3LL5VFe9weoiUyXlFRykjA8e XWcLnIl51Oi0o1w7WRJb82Uo+7VOmw9PWRcmzrEkmV+tLpNVISPNFFjz X-Gm-Gg: ASbGncuTia6HoiM4mEVOsJXX1DgsBPbtnMkA7ItGXbGy7Rcv0trspyR5/pf3a0uqoEt iBYB1tJafBp5PNi+JCYdytnbs34HJNGQLWdfHAqXZ9DaFaNf3ffTb2w8QeEeXCWxnBwIKEJ62Iy mPbFpGaPsf4hovnl7Sujp/lfuo6cS7d20aM7r56PIv+r/Sw0AO4m56lfvwoyot9T2a9b/r/teEO imJso0PSjThefLnfpx60BtBssxlbrSsOhvMQurrcifC/pafdZ/uqu/+XrhlnwVpDJStgDMQJ6Ic 55atVZuL2a1dkuytyOdGohCPFC3UnNLrDIjU562lzxoz5ZhWHMWpm76T/ZdFUO64tZDwS6sPA/N k0OzGW4nftzFCUo6AI1MmcF7ef7EFzTkTYf1tvvhAoeK08MLO+dbi17+xrElX0pVyAI0eV0IFgF tzo9HvsQ== X-Google-Smtp-Source: AGHT+IFPevFzGFaF5BFvvM16dVe5PagAEqoXJ5U2Ul/bMYovr8f7uGf2dQZj9w6thKpZLv5adVTjNA== X-Received: by 2002:a17:906:7943:b0:afa:12cd:9daa with SMTP id a640c23a62f3a-afa1e114b88mr322385766b.48.1755003406663; Tue, 12 Aug 2025 05:56:46 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:46 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 5/7] ALSA: usb-audio: us144mkii: Add MIDI support and mixer controls Date: Tue, 12 Aug 2025 14:56:30 +0200 Message-Id: <20250812125633.79270-11-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This patch adds the remaining features to the driver: MIDI I/O and ALSA mixer controls. A raw MIDI device is created, supporting one input and one output port. The implementation handles the device-specific 9-byte packet format for both sending and receiving MIDI data, using bulk URBs and workqueues for processing. ALSA kcontrols are added to the mixer interface, allowing user-space control over the audio routing matrix (e.g., selecting analog/digital sources for capture channels, and routing playback streams to different outputs). A read-only control to display the current sample rate is also included. Finally, the stub routing functions are replaced with the full logic to apply the user-selected mixer settings to the audio streams. With this commit, the driver is feature-complete. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 303 ++++++++++++------ sound/usb/usx2y/us144mkii.h | 126 +++++++- sound/usb/usx2y/us144mkii_controls.c | 444 +++++++++++++++++++++++++++ sound/usb/usx2y/us144mkii_midi.c | 401 ++++++++++++++++++++++++ sound/usb/usx2y/us144mkii_pcm.c | 57 +++- 5 files changed, 1219 insertions(+), 112 deletions(-) create mode 100644 sound/usb/usx2y/us144mkii_controls.c create mode 100644 sound/usb/usx2y/us144mkii_midi.c diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 7a114e64fb1f..f7944eb2fb93 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -75,6 +75,30 @@ void tascam_free_urbs(struct tascam_card *tascam) } } =20 + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + for (i =3D 0; i < NUM_MIDI_IN_URBS; i++) { + if (tascam->midi_in_urbs[i]) { + usb_free_coherent( + tascam->dev, MIDI_IN_BUF_SIZE, + tascam->midi_in_urbs[i]->transfer_buffer, + tascam->midi_in_urbs[i]->transfer_dma); + usb_free_urb(tascam->midi_in_urbs[i]); + tascam->midi_in_urbs[i] =3D NULL; + } + } + + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i]) { + usb_free_coherent( + tascam->dev, MIDI_OUT_BUF_SIZE, + tascam->midi_out_urbs[i]->transfer_buffer, + tascam->midi_out_urbs[i]->transfer_dma); + usb_free_urb(tascam->midi_out_urbs[i]); + tascam->midi_out_urbs[i] =3D NULL; + } + } + kfree(tascam->capture_routing_buffer); tascam->capture_routing_buffer =3D NULL; kfree(tascam->capture_decode_dst_block); @@ -164,6 +188,44 @@ int tascam_alloc_urbs(struct tascam_card *tascam) c_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; } =20 + /* MIDI URB and buffer allocation */ + for (i =3D 0; i < NUM_MIDI_IN_URBS; i++) { + struct urb *m_urb =3D usb_alloc_urb(0, GFP_KERNEL); + + if (!m_urb) + goto error; + tascam->midi_in_urbs[i] =3D m_urb; + m_urb->transfer_buffer =3D + usb_alloc_coherent(tascam->dev, MIDI_IN_BUF_SIZE, + GFP_KERNEL, &m_urb->transfer_dma); + if (!m_urb->transfer_buffer) + goto error; + usb_fill_bulk_urb(m_urb, tascam->dev, + usb_rcvbulkpipe(tascam->dev, EP_MIDI_IN), + m_urb->transfer_buffer, MIDI_IN_BUF_SIZE, + tascam_midi_in_urb_complete, tascam); + m_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; + } + + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + struct urb *m_urb =3D usb_alloc_urb(0, GFP_KERNEL); + + if (!m_urb) + goto error; + tascam->midi_out_urbs[i] =3D m_urb; + m_urb->transfer_buffer =3D + usb_alloc_coherent(tascam->dev, MIDI_OUT_BUF_SIZE, + GFP_KERNEL, &m_urb->transfer_dma); + if (!m_urb->transfer_buffer) + goto error; + usb_fill_bulk_urb(m_urb, tascam->dev, + usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT), + m_urb->transfer_buffer, + 0, /* length set later */ + tascam_midi_out_urb_complete, tascam); + m_urb->transfer_flags |=3D URB_NO_TRANSFER_DMA_MAP; + } + tascam->capture_ring_buffer =3D kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL); if (!tascam->capture_ring_buffer) @@ -213,16 +275,112 @@ void tascam_stop_work_handler(struct work_struct *wo= rk) * @card: Pointer to the ALSA sound card instance. * * This function is called when the sound card is being freed. It releases - * the reference to the USB device. + * resources allocated for the tascam_card structure, including the MIDI + * input FIFO and decrements the USB device reference count. */ static void tascam_card_private_free(struct snd_card *card) { struct tascam_card *tascam =3D card->private_data; =20 - if (tascam && tascam->dev) { - usb_put_dev(tascam->dev); - tascam->dev =3D NULL; + if (tascam) { + kfifo_free(&tascam->midi_in_fifo); + if (tascam->dev) { + usb_put_dev(tascam->dev); + tascam->dev =3D NULL; + } + } +} + +/** + * tascam_suspend() - Handles device suspension. + * @intf: The USB interface being suspended. + * @message: Power management message. + * + * This function is called when the device is suspended. It stops all acti= ve + * streams, kills all URBs, and sends a vendor-specific deep sleep command + * to the device to ensure a stable low-power state. + * + * Return: 0 on success. + */ +static int tascam_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct tascam_card *tascam =3D usb_get_intfdata(intf); + + if (!tascam) + return 0; + + snd_pcm_suspend_all(tascam->pcm); + + cancel_work_sync(&tascam->stop_work); + cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_in_work); + cancel_work_sync(&tascam->midi_out_work); + cancel_work_sync(&tascam->stop_pcm_work); + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + + return 0; +} + +/** + * tascam_resume() - Handles device resumption from suspend. + * @intf: The USB interface being resumed. + * + * This function is called when the device resumes from suspend. It + * re-establishes the active USB interface settings and re-configures the = sample + * rate if it was previously active. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_resume(struct usb_interface *intf) +{ + struct tascam_card *tascam =3D usb_get_intfdata(intf); + int err; + + if (!tascam) + return 0; + + dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); + + /* + * The device requires a full re-initialization sequence upon resume. + * First, re-establish the active USB interface settings. + */ + err =3D usb_set_interface(tascam->dev, 0, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 0: %d\n", + err); + return err; } + err =3D usb_set_interface(tascam->dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, + "resume: failed to set alt setting on intf 1: %d\n", + err); + return err; + } + + /* Re-configure the sample rate if one was previously active */ + if (tascam->current_rate > 0) + us144mkii_configure_device_for_rate(tascam, + tascam->current_rate); + + return 0; +} + +static void tascam_error_timer(struct timer_list *t) +{ + struct tascam_card *tascam =3D + container_of(t, struct tascam_card, error_timer); + + if (atomic_read(&tascam->midi_in_active)) + schedule_work(&tascam->midi_in_work); + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); } =20 /** @@ -235,8 +393,8 @@ static void tascam_card_private_free(struct snd_card *c= ard) * - Checking for the second interface (MIDI) and associating it. * - Performing a vendor-specific handshake with the device. * - Setting alternate settings for USB interfaces. - * - Creating and registering the ALSA sound card and PCM device. - * - Allocating and initializing URBs for audio transfers. + * - Creating and registering the ALSA sound card, PCM device, and MIDI de= vice. + * - Allocating and initializing URBs for audio and MIDI transfers. * * Return: 0 on success, or a negative error code on failure. */ @@ -326,27 +484,25 @@ static int tascam_probe(struct usb_interface *intf, tascam->iface0 =3D intf; =20 spin_lock_init(&tascam->lock); + spin_lock_init(&tascam->midi_in_lock); + spin_lock_init(&tascam->midi_out_lock); init_usb_anchor(&tascam->playback_anchor); init_usb_anchor(&tascam->capture_anchor); init_usb_anchor(&tascam->feedback_anchor); + init_usb_anchor(&tascam->midi_in_anchor); + init_usb_anchor(&tascam->midi_out_anchor); + + timer_setup(&tascam->error_timer, tascam_error_timer, 0); =20 INIT_WORK(&tascam->stop_work, tascam_stop_work_handler); INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler); INIT_WORK(&tascam->capture_work, tascam_capture_work_handler); + init_completion(&tascam->midi_out_drain_completion); =20 - err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); - if (err < 0) - goto free_card; - tascam->pcm->private_data =3D tascam; - strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); - - err =3D tascam_init_pcm(tascam->pcm); - if (err < 0) - goto free_card; - - err =3D tascam_alloc_urbs(tascam); - if (err < 0) - goto free_card; + if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL)) { + snd_card_free(card); + return -ENOMEM; + } =20 strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); if (dev->descriptor.idProduct =3D=3D USB_PID_TASCAM_US144) { @@ -363,6 +519,28 @@ static int tascam_probe(struct usb_interface *intf, card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct, dev_name(&dev->dev)); =20 + err =3D snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm); + if (err < 0) + goto free_card; + tascam->pcm->private_data =3D tascam; + strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name)); + + err =3D tascam_init_pcm(tascam->pcm); + if (err < 0) + goto free_card; + + err =3D tascam_create_midi(tascam); + if (err < 0) + goto free_card; + + err =3D tascam_create_controls(tascam); + if (err < 0) + goto free_card; + + err =3D tascam_alloc_urbs(tascam); + if (err < 0) + goto free_card; + err =3D snd_card_register(card); if (err < 0) goto free_card; @@ -383,8 +561,8 @@ static int tascam_probe(struct usb_interface *intf, * @intf: The USB interface being disconnected. * * This function is called when the device is disconnected from the system. - * It cleans up all allocated resources by freeing the sound card, which in - * turn triggers freeing of URBs and other resources. + * It cleans up all allocated resources, including killing URBs, freeing + * the sound card, and releasing memory. */ static void tascam_disconnect(struct usb_interface *intf) { @@ -394,89 +572,26 @@ static void tascam_disconnect(struct usb_interface *i= ntf) return; =20 if (intf->cur_altsetting->desc.bInterfaceNumber =3D=3D 0) { + /* Ensure all deferred work is complete before freeing resources */ snd_card_disconnect(tascam->card); cancel_work_sync(&tascam->stop_work); cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_in_work); + cancel_work_sync(&tascam->midi_out_work); cancel_work_sync(&tascam->stop_pcm_work); + + usb_kill_anchored_urbs(&tascam->playback_anchor); + usb_kill_anchored_urbs(&tascam->capture_anchor); + usb_kill_anchored_urbs(&tascam->feedback_anchor); + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); + timer_delete_sync(&tascam->error_timer); tascam_free_urbs(tascam); snd_card_free(tascam->card); dev_idx--; } } =20 -/** - * tascam_suspend() - Handles device suspension. - * @intf: The USB interface being suspended. - * @message: Power management message. - * - * This function is called when the device is suspended. It stops all acti= ve - * streams and kills all URBs. - * - * Return: 0 on success. - */ -static int tascam_suspend(struct usb_interface *intf, pm_message_t message) -{ - struct tascam_card *tascam =3D usb_get_intfdata(intf); - - if (!tascam) - return 0; - - snd_pcm_suspend_all(tascam->pcm); - - cancel_work_sync(&tascam->stop_work); - cancel_work_sync(&tascam->capture_work); - cancel_work_sync(&tascam->stop_pcm_work); - usb_kill_anchored_urbs(&tascam->playback_anchor); - usb_kill_anchored_urbs(&tascam->capture_anchor); - usb_kill_anchored_urbs(&tascam->feedback_anchor); - - return 0; -} - -/** - * tascam_resume() - Handles device resumption from suspend. - * @intf: The USB interface being resumed. - * - * This function is called when the device resumes from suspend. It - * re-establishes the active USB interface settings and re-configures the = sample - * rate if it was previously active. - * - * Return: 0 on success, or a negative error code on failure. - */ -static int tascam_resume(struct usb_interface *intf) -{ - struct tascam_card *tascam =3D usb_get_intfdata(intf); - int err; - - if (!tascam) - return 0; - - dev_info(&intf->dev, "resuming TASCAM US-144MKII\n"); - - /* Re-establish the active USB interface settings. */ - err =3D usb_set_interface(tascam->dev, 0, 1); - if (err < 0) { - dev_err(&intf->dev, - "resume: failed to set alt setting on intf 0: %d\n", - err); - return err; - } - err =3D usb_set_interface(tascam->dev, 1, 1); - if (err < 0) { - dev_err(&intf->dev, - "resume: failed to set alt setting on intf 1: %d\n", - err); - return err; - } - - /* Re-configure the sample rate if one was previously active */ - if (tascam->current_rate > 0) - us144mkii_configure_device_for_rate(tascam, - tascam->current_rate); - - return 0; -} - static const struct usb_device_id tascam_usb_ids[] =3D { { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144) }, { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) }, diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index 34b9b275b905..c740a0b5a0ea 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -4,13 +4,18 @@ #ifndef __US144MKII_H #define __US144MKII_H =20 +#include +#include #include #include +#include #include #include #include +#include =20 #define DRIVER_NAME "us144mkii" +#define DRIVER_VERSION "1.7.6" =20 /* --- USB Device Identification --- */ #define USB_VID_TASCAM 0x0644 @@ -20,13 +25,15 @@ /* --- USB Endpoints (Alternate Setting 1) --- */ #define EP_PLAYBACK_FEEDBACK 0x81 #define EP_AUDIO_OUT 0x02 +#define EP_MIDI_IN 0x83 +#define EP_MIDI_OUT 0x04 #define EP_AUDIO_IN 0x86 =20 /* --- USB Control Message Protocol --- */ #define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) #define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT) -#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) #define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVIC= E) +#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) =20 enum uac_request { UAC_SET_CUR =3D 0x01, @@ -48,6 +55,8 @@ enum tascam_mode_value { MODE_VAL_STREAM_START =3D 0x0030, }; =20 +#define HANDSHAKE_SUCCESS_VAL 0x12 + enum tascam_register { REG_ADDR_UNKNOWN_0D =3D 0x0d04, REG_ADDR_UNKNOWN_0E =3D 0x0e00, @@ -70,6 +79,11 @@ enum tascam_register { #define NUM_CAPTURE_URBS 8 #define CAPTURE_URB_SIZE 512 #define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4) +#define NUM_MIDI_IN_URBS 4 +#define MIDI_IN_BUF_SIZE 64 +#define MIDI_IN_FIFO_SIZE (MIDI_IN_BUF_SIZE * NUM_MIDI_IN_URBS) +#define MIDI_OUT_BUF_SIZE 64 +#define NUM_MIDI_OUT_URBS 4 #define USB_CTRL_TIMEOUT_MS 1000 #define FEEDBACK_SYNC_LOSS_THRESHOLD 41 =20 @@ -85,10 +99,6 @@ enum tascam_register { #define FRAMES_PER_DECODE_BLOCK 8 #define RAW_BYTES_PER_DECODE_BLOCK 512 =20 -struct tascam_card; - -#include "us144mkii_pcm.h" - /** * struct us144mkii_frame_pattern_observer - State for dynamic feedback * patterns. @@ -117,6 +127,7 @@ struct us144mkii_frame_pattern_observer { * @iface1: Pointer to USB interface 1 (MIDI). * @card: Pointer to the ALSA sound card instance. * @pcm: Pointer to the ALSA PCM device. + * @rmidi: Pointer to the ALSA rawmidi device. * * @playback_substream: Pointer to the active playback PCM substream. * @playback_urbs: Array of URBs for playback. @@ -142,14 +153,35 @@ struct us144mkii_frame_pattern_observer { * @capture_decode_dst_block: Buffer for decoded 32-bit capture samples. * @capture_routing_buffer: Intermediate buffer for capture routing. * @capture_work: Work struct for deferred capture processing. - * * @stop_work: Work struct for deferred stream stopping. * @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g. * xrun). * + * @midi_in_substream: Pointer to the active MIDI input substream. + * @midi_out_substream: Pointer to the active MIDI output substream. + * @midi_in_urbs: Array of URBs for MIDI input. + * @midi_out_urbs: Array of URBs for MIDI output. + * @midi_in_active: Atomic flag indicating if MIDI input is active. + * @midi_out_active: Atomic flag indicating if MIDI output is active. + * @midi_in_fifo: FIFO for raw MIDI input data. + * @midi_in_work: Work struct for deferred MIDI input processing. + * @midi_out_work: Work struct for deferred MIDI output processing. + * @midi_in_lock: Spinlock for MIDI input FIFO. + * @midi_out_lock: Spinlock for MIDI output. + * @midi_out_urbs_in_flight: Bitmap of MIDI output URBs currently in fligh= t. + * @midi_running_status: Stores the last MIDI status byte for running stat= us. + * @error_timer: Timer for MIDI error retry logic. + * * @lock: Main spinlock for protecting shared driver state. * @active_urbs: Atomic counter for active URBs. * @current_rate: Currently configured sample rate of the device. + * @line_out_source: Source for Line Outputs (0: Playback 1-2, 1: Playback= 3-4). + * @digital_out_source: Source for Digital Outputs (0: Playback 1-2, 1: Pl= ayback + * 3-4). + * @capture_12_source: Source for Capture channels 1-2 (0: Analog In, 1: D= igital + * In). + * @capture_34_source: Source for Capture channels 3-4 (0: Analog In, 1: D= igital + * In). * * @feedback_accumulator_pattern: Stores the calculated frames per packet = for * feedback. @@ -164,6 +196,8 @@ struct us144mkii_frame_pattern_observer { * @playback_anchor: USB anchor for playback URBs. * @capture_anchor: USB anchor for capture URBs. * @feedback_anchor: USB anchor for feedback URBs. + * @midi_in_anchor: USB anchor for MIDI input URBs. + * @midi_out_anchor: USB anchor for MIDI output URBs. */ struct tascam_card { /* --- Core device pointers --- */ @@ -172,6 +206,7 @@ struct tascam_card { struct usb_interface *iface1; struct snd_card *card; struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; =20 /* --- PCM Substreams --- */ struct snd_pcm_substream *playback_substream; @@ -184,9 +219,13 @@ struct tascam_card { size_t feedback_urb_alloc_size; struct urb *capture_urbs[NUM_CAPTURE_URBS]; size_t capture_urb_alloc_size; + struct urb *midi_in_urbs[NUM_MIDI_IN_URBS]; + struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS]; struct usb_anchor playback_anchor; struct usb_anchor capture_anchor; struct usb_anchor feedback_anchor; + struct usb_anchor midi_in_anchor; + struct usb_anchor midi_out_anchor; =20 /* --- Stream State --- */ spinlock_t lock; @@ -211,6 +250,19 @@ struct tascam_card { s32 *capture_decode_dst_block; s32 *capture_routing_buffer; =20 + /* --- MIDI State --- */ + struct snd_rawmidi_substream *midi_in_substream; + struct snd_rawmidi_substream *midi_out_substream; + atomic_t midi_in_active; + atomic_t midi_out_active; + struct kfifo midi_in_fifo; + spinlock_t midi_in_lock; + spinlock_t midi_out_lock; + unsigned long midi_out_urbs_in_flight; + u8 midi_running_status; + struct timer_list error_timer; + struct completion midi_out_drain_completion; + /* --- Feedback Sync State --- */ unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; unsigned int feedback_pattern_out_idx; @@ -224,14 +276,23 @@ struct tascam_card { struct work_struct stop_work; struct work_struct stop_pcm_work; struct work_struct capture_work; + struct work_struct midi_in_work; + struct work_struct midi_out_work; + + /* --- Mixer/Routing State --- */ + unsigned int line_out_source; + unsigned int digital_out_source; + unsigned int capture_12_source; + unsigned int capture_34_source; }; =20 +/* main.c */ /** * tascam_free_urbs() - Free all allocated URBs and associated buffers. * @tascam: the tascam_card instance * - * This function kills, unlinks, and frees all playback, feedback, and - * capture URBs, along with their transfer buffers and the capture + * This function kills, unlinks, and frees all playback, feedback, capture, + * and MIDI URBs, along with their transfer buffers and the capture * ring/decode buffers. */ void tascam_free_urbs(struct tascam_card *tascam); @@ -241,7 +302,7 @@ void tascam_free_urbs(struct tascam_card *tascam); * @tascam: the tascam_card instance * * This function allocates and initializes all URBs for playback, feedback, - * and capture, as well as the necessary buffers for data processing. + * capture, and MIDI, as well as the necessary buffers for data processing. * * Return: 0 on success, or a negative error code on failure. */ @@ -256,4 +317,51 @@ int tascam_alloc_urbs(struct tascam_card *tascam); */ void tascam_stop_work_handler(struct work_struct *work); =20 +/* us144mkii_pcm.h */ +#include "us144mkii_pcm.h" + +/* us144mkii_midi.c */ +/** + * tascam_midi_in_urb_complete() - Completion handler for MIDI IN URBs + * @urb: The completed URB. + * + * This function runs in interrupt context. It places the raw data from the + * USB endpoint into a kfifo and schedules a work item to process it later, + * ensuring the interrupt handler remains fast. + */ +void tascam_midi_in_urb_complete(struct urb *urb); + +/** + * tascam_midi_out_urb_complete() - Completion handler for MIDI OUT bulk U= RB. + * @urb: The completed URB. + * + * This function runs in interrupt context. It marks the output URB as no + * longer in-flight. It then re-schedules the work handler to check for and + * send any more data waiting in the ALSA buffer. This is a safe, non-bloc= king + * way to continue the data transmission chain. + */ +void tascam_midi_out_urb_complete(struct urb *urb); + +/** + * tascam_create_midi() - Create and initialize the ALSA rawmidi device. + * @tascam: The driver instance. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_create_midi(struct tascam_card *tascam); + +/* us144mkii_controls.c */ +/** + * tascam_create_controls() - Creates and adds ALSA mixer controls for the + * device. + * @tascam: The driver instance. + * + * This function registers custom ALSA controls for managing audio routing + * (line out source, digital out source, capture 1-2 source, capture 3-4 s= ource) + * and displaying the current sample rate. + * + * Return: 0 on success, or a negative error code on failure. + */ +int tascam_create_controls(struct tascam_card *tascam); + #endif /* __US144MKII_H */ diff --git a/sound/usb/usx2y/us144mkii_controls.c b/sound/usb/usx2y/us144mk= ii_controls.c new file mode 100644 index 000000000000..b6ad1b5a4bf5 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_controls.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * @brief Text descriptions for playback output source options. + * + * Used by ALSA kcontrol elements to provide user-friendly names for + * the playback routing options (e.g., "Playback 1-2", "Playback 3-4"). + */ +static const char *const playback_source_texts[] =3D { "Playback 1-2", + "Playback 3-4" }; + +/** + * @brief Text descriptions for capture input source options. + * + * Used by ALSA kcontrol elements to provide user-friendly names for + * the capture routing options (e.g., "Analog In", "Digital In"). + */ +static const char *const capture_source_texts[] =3D { "Analog In", "Digita= l In" }; + +/** + * tascam_playback_source_info() - ALSA control info callback for playback + * source. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the enumerated playback source + * control, including its type, count, and available items (Playback 1-2, + * Playback 3-4). + * + * Return: 0 on success. + */ +static int tascam_playback_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, playback_source_texts); +} + +/** + * tascam_line_out_get() - ALSA control get callback for Line Outputs Sour= ce. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Line Outputs sour= ce + * (Playback 1-2 or Playback 3-4) from the driver's private data and popul= ates + * the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_line_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->line_out_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_line_out_put() - ALSA control put callback for Line Outputs Sour= ce. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Line Outputs source (Playback 1-2 or Playback 3-= 4) + * based on the user's selection from the ALSA control element. It validat= es + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_line_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->line_out_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->line_out_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_line_out_control - ALSA kcontrol definition for Line Outputs Sou= rce. + * + * This defines a new ALSA mixer control named "Line OUTPUTS Source" that = allows + * the user to select between "Playback 1-2" and "Playback 3-4" for the an= alog + * line outputs of the device. It uses the `tascam_playback_source_info` f= or + * information and `tascam_line_out_get`/`tascam_line_out_put` for value + * handling. + */ +static const struct snd_kcontrol_new tascam_line_out_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Line Playback Source", + .info =3D tascam_playback_source_info, + .get =3D tascam_line_out_get, + .put =3D tascam_line_out_put, +}; + +/** + * tascam_digital_out_get() - ALSA control get callback for Digital Outputs + * Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Digital Outputs s= ource + * (Playback 1-2 or Playback 3-4) from the driver's private data and popul= ates + * the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_digital_out_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->digital_out_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_digital_out_put() - ALSA control put callback for Digital Outputs + * Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Digital Outputs source (Playback 1-2 or Playback= 3-4) + * based on the user's selection from the ALSA control element. It validat= es + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_digital_out_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->digital_out_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->digital_out_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_digital_out_control - ALSA kcontrol definition for Digital Outpu= ts + * Source. + * + * This defines a new ALSA mixer control named "Digital OUTPUTS Source" th= at + * allows the user to select between "Playback 1-2" and "Playback 3-4" for= the + * digital outputs of the device. It uses the `tascam_playback_source_info= ` for + * information and `tascam_digital_out_get`/`tascam_digital_out_put` for v= alue + * handling. + */ +static const struct snd_kcontrol_new tascam_digital_out_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Digital Playback Source", + .info =3D tascam_playback_source_info, + .get =3D tascam_digital_out_get, + .put =3D tascam_digital_out_put, +}; + +/** + * tascam_capture_source_info() - ALSA control info callback for capture s= ource. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the enumerated capture source + * control, including its type, count, and available items (Analog In, Dig= ital + * In). + * + * Return: 0 on success. + */ +static int tascam_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, capture_source_texts); +} + +/** + * tascam_capture_12_get() - ALSA control get callback for Capture channel= s 1 + * and 2 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Capture channels = 1 and + * 2 source (Analog In or Digital In) from the driver's private data and + * populates the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_capture_12_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->capture_12_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_capture_12_put() - ALSA control put callback for Capture channel= s 1 + * and 2 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Capture channels 1 and 2 source (Analog In or Di= gital + * In) based on the user's selection from the ALSA control element. It val= idates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_capture_12_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->capture_12_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->capture_12_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_capture_12_control - ALSA kcontrol definition for Capture channe= ls 1 + * and 2 Source. + * + * This defines a new ALSA mixer control named "ch1 and ch2 Source" that a= llows + * the user to select between "Analog In" and "Digital In" for the first t= wo + * capture channels of the device. It uses the `tascam_capture_source_info= ` for + * information and `tascam_capture_12_get`/`tascam_capture_12_put` for val= ue + * handling. + */ +static const struct snd_kcontrol_new tascam_capture_12_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Ch1/2 Capture Source", + .info =3D tascam_capture_source_info, + .get =3D tascam_capture_12_get, + .put =3D tascam_capture_12_put, +}; + +/** + * tascam_capture_34_get() - ALSA control get callback for Capture channel= s 3 + * and 4 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current selection for the Capture channels = 3 and + * 4 source (Analog In or Digital In) from the driver's private data and + * populates the ALSA control element value. + * + * Return: 0 on success. + */ +static int tascam_capture_34_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int val; + + guard(spinlock_irqsave)(&tascam->lock); + val =3D tascam->capture_34_source; + ucontrol->value.enumerated.item[0] =3D val; + return 0; +} + +/** + * tascam_capture_34_put() - ALSA control put callback for Capture channel= s 3 + * and 4 Source. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure containing the new = value. + * + * This function sets the Capture channels 3 and 4 source (Analog In or Di= gital + * In) based on the user's selection from the ALSA control element. It val= idates + * the input and updates the driver's private data. + * + * Return: 1 if the value was changed, 0 if unchanged, or a negative error= code. + */ +static int tascam_capture_34_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D snd_kcontrol_chip(kcontrol); + int changed =3D 0; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->capture_34_source !=3D ucontrol->value.enumerated.item[0]) { + tascam->capture_34_source =3D ucontrol->value.enumerated.item[0]; + changed =3D 1; + } + return changed; +} + +/** + * tascam_capture_34_control - ALSA kcontrol definition for Capture channe= ls 3 + * and 4 Source. + * + * This defines a new ALSA mixer control named "ch3 and ch4 Source" that a= llows + * the user to select between "Analog In" and "Digital In" for the third a= nd + * fourth capture channels of the device. It uses the + * `tascam_capture_source_info` for information and + * `tascam_capture_34_get`/`tascam_capture_34_put` for value handling. + */ +static const struct snd_kcontrol_new tascam_capture_34_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Ch3/4 Capture Source", + .info =3D tascam_capture_source_info, + .get =3D tascam_capture_34_get, + .put =3D tascam_capture_34_put, +}; + +/** + * tascam_samplerate_info() - ALSA control info callback for Sample Rate. + * @kcontrol: The ALSA kcontrol instance. + * @uinfo: The ALSA control element info structure to fill. + * + * This function provides information about the Sample Rate control, defin= ing + * it as an integer type with a minimum value of 0 and a maximum of 96000. + * + * Return: 0 on success. + */ +static int tascam_samplerate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count =3D 1; + uinfo->value.integer.min =3D 0; + uinfo->value.integer.max =3D 96000; + return 0; +} + +/** + * tascam_samplerate_get() - ALSA control get callback for Sample Rate. + * @kcontrol: The ALSA kcontrol instance. + * @ucontrol: The ALSA control element value structure to fill. + * + * This function retrieves the current sample rate from the device via a U= SB + * control message and populates the ALSA control element value. If the ra= te + * is already known (i.e., `current_rate` is set), it returns that value + * directly. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int tascam_samplerate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam =3D + (struct tascam_card *)snd_kcontrol_chip(kcontrol); + u8 *buf __free(kfree) =3D NULL; + int err; + u32 rate =3D 0; + + guard(spinlock_irqsave)(&tascam->lock); + if (tascam->current_rate > 0) { + ucontrol->value.integer.value[0] =3D tascam->current_rate; + return 0; + } + // Lock is released here before kmalloc and usb_control_msg + + buf =3D kmalloc(3, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + err =3D usb_control_msg(tascam->dev, usb_rcvctrlpipe(tascam->dev, 0), + UAC_GET_CUR, RT_D2H_CLASS_EP, + UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_IN, buf, 3, + USB_CTRL_TIMEOUT_MS); + + if (err >=3D 3) + rate =3D buf[0] | (buf[1] << 8) | (buf[2] << 16); + + ucontrol->value.integer.value[0] =3D rate; + return 0; +} + +/** + * tascam_samplerate_control - ALSA kcontrol definition for Sample Rate. + * + * This defines a new ALSA mixer control named "Sample Rate" that displays + * the current sample rate of the device. It is a read-only control. + */ +static const struct snd_kcontrol_new tascam_samplerate_control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D "Sample Rate", + .info =3D tascam_samplerate_info, + .get =3D tascam_samplerate_get, + .access =3D SNDRV_CTL_ELEM_ACCESS_READ, +}; + +int tascam_create_controls(struct tascam_card *tascam) +{ + int err; + + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_line_out_control, tascam)); + if (err < 0) + return err; + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_digital_out_control, tascam)); + if (err < 0) + return err; + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_capture_12_control, tascam)); + if (err < 0) + return err; + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_capture_34_control, tascam)); + if (err < 0) + return err; + + err =3D snd_ctl_add(tascam->card, + snd_ctl_new1(&tascam_samplerate_control, tascam)); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_midi.c b/sound/usb/usx2y/us144mkii_m= idi.c new file mode 100644 index 000000000000..36a05d52a8c8 --- /dev/null +++ b/sound/usb/usx2y/us144mkii_midi.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2025 =C5=A0erif Rami + +#include "us144mkii.h" + +/** + * tascam_midi_in_work_handler() - Deferred work for processing MIDI input. + * @work: The work_struct instance. + * + * This function runs in a thread context. It safely reads raw USB data fr= om + * the kfifo, processes it by stripping protocol-specific padding bytes, a= nd + * passes the clean MIDI data to the ALSA rawmidi subsystem. + */ +static void tascam_midi_in_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, midi_in_work); + u8 buf[9]; + u8 clean_buf[8]; + unsigned int count, clean_count; + + if (!tascam->midi_in_substream) + return; + + while (kfifo_out_spinlocked(&tascam->midi_in_fifo, buf, sizeof(buf), + &tascam->midi_in_lock) =3D=3D sizeof(buf)) { + clean_count =3D 0; + for (count =3D 0; count < 8; ++count) { + if (buf[count] !=3D 0xfd) + clean_buf[clean_count++] =3D buf[count]; + } + + if (clean_count > 0) + snd_rawmidi_receive(tascam->midi_in_substream, + clean_buf, clean_count); + } +} + +void tascam_midi_in_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + int ret; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN && urb->status !=3D -EPROTO) { + dev_err_ratelimited(tascam->card->dev, + "MIDI IN URB failed: status %d\n", + urb->status); + } + goto out; + } + + if (tascam && atomic_read(&tascam->midi_in_active) && + urb->actual_length > 0) { + kfifo_in_spinlocked(&tascam->midi_in_fifo, urb->transfer_buffer, + urb->actual_length, &tascam->midi_in_lock); + schedule_work(&tascam->midi_in_work); + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->midi_in_anchor); + ret =3D usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(tascam->card->dev, + "Failed to resubmit MIDI IN URB: error %d\n", ret); + usb_unanchor_urb(urb); + usb_put_urb(urb); + } +out: + usb_put_urb(urb); +} + +/** + * tascam_midi_in_open() - Opens the MIDI input substream. + * @substream: The ALSA rawmidi substream to open. + * + * This function stores a reference to the MIDI input substream in the + * driver's private data. + * + * Return: 0 on success. + */ +static int tascam_midi_in_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + + tascam->midi_in_substream =3D substream; + return 0; +} + +/** + * tascam_midi_in_close() - Closes the MIDI input substream. + * @substream: The ALSA rawmidi substream to close. + * + * Return: 0 on success. + */ +static int tascam_midi_in_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/** + * tascam_midi_in_trigger() - Triggers MIDI input stream activity. + * @substream: The ALSA rawmidi substream. + * @up: Boolean indicating whether to start (1) or stop (0) the stream. + * + * This function starts or stops the MIDI input URBs based on the 'up' + * parameter. When starting, it resets the kfifo and submits all MIDI input + * URBs. When stopping, it kills all anchored MIDI input URBs and cancels = the + * associated workqueue. + */ +static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + int i, err; + + if (up) { + if (atomic_xchg(&tascam->midi_in_active, 1) =3D=3D 0) { + { + guard(spinlock_irqsave)(&tascam->midi_in_lock); + kfifo_reset(&tascam->midi_in_fifo); + } + + for (i =3D 0; i < NUM_MIDI_IN_URBS; i++) { + usb_get_urb(tascam->midi_in_urbs[i]); + usb_anchor_urb(tascam->midi_in_urbs[i], + &tascam->midi_in_anchor); + err =3D usb_submit_urb(tascam->midi_in_urbs[i], + GFP_KERNEL); + if (err < 0) { + dev_err(tascam->card->dev, + "Failed to submit MIDI IN URB %d: %d\n", + i, err); + usb_unanchor_urb( + tascam->midi_in_urbs[i]); + usb_put_urb(tascam->midi_in_urbs[i]); + } + } + } + } else { + if (atomic_xchg(&tascam->midi_in_active, 0) =3D=3D 1) { + usb_kill_anchored_urbs(&tascam->midi_in_anchor); + cancel_work_sync(&tascam->midi_in_work); + } + } +} + +/** + * tascam_midi_in_ops - ALSA rawmidi operations for MIDI input. + * + * This structure defines the callback functions for MIDI input stream + * operations, including open, close, and trigger. + */ +static const struct snd_rawmidi_ops tascam_midi_in_ops =3D { + .open =3D tascam_midi_in_open, + .close =3D tascam_midi_in_close, + .trigger =3D tascam_midi_in_trigger, +}; + +void tascam_midi_out_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam =3D urb->context; + int i, urb_index =3D -1; + + if (urb->status) { + if (urb->status !=3D -ENOENT && urb->status !=3D -ECONNRESET && + urb->status !=3D -ESHUTDOWN) { + dev_err_ratelimited(tascam->card->dev, + "MIDI OUT URB failed: %d\n", + urb->status); + } + goto out; + } + + if (!tascam) + goto out; + + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i] =3D=3D urb) { + urb_index =3D i; + break; + } + } + + if (urb_index < 0) { + dev_err_ratelimited(tascam->card->dev, + "Unknown MIDI OUT URB completed!\n"); + goto out; + } + + { + guard(spinlock_irqsave)(&tascam->midi_out_lock); + clear_bit(urb_index, &tascam->midi_out_urbs_in_flight); + } + + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); + +out: + usb_put_urb(urb); +} + +/** + * tascam_midi_out_work_handler() - Deferred work for sending MIDI data + * @work: The work_struct instance. + * + * This function handles the proprietary output protocol: take the raw MIDI + * message bytes from the application, place them at the start of a 9-byte + * buffer, pad the rest with 0xFD, and add a terminator byte (0x00). + * This function pulls as many bytes as will fit into one packet from the + * ALSA buffer and sends them. + */ +static void tascam_midi_out_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam =3D + container_of(work, struct tascam_card, midi_out_work); + struct snd_rawmidi_substream *substream =3D tascam->midi_out_substream; + int i; + + if (!substream || !atomic_read(&tascam->midi_out_active)) + return; + + while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) =3D=3D 1) { + int urb_index; + struct urb *urb; + u8 *buf; + int bytes_to_send; + + { + guard(spinlock_irqsave)(&tascam->midi_out_lock); + + urb_index =3D -1; + for (i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (!test_bit( + i, + &tascam->midi_out_urbs_in_flight)) { + urb_index =3D i; + break; + } + } + + if (urb_index < 0) + return; /* No free URBs, will be rescheduled by + * completion handler + */ + + urb =3D tascam->midi_out_urbs[urb_index]; + buf =3D urb->transfer_buffer; + bytes_to_send =3D snd_rawmidi_transmit(substream, buf, 8); + + if (bytes_to_send <=3D 0) + break; /* No more data */ + + if (bytes_to_send < 9) + memset(buf + bytes_to_send, 0xfd, + 9 - bytes_to_send); + buf[8] =3D 0x00; + + set_bit(urb_index, &tascam->midi_out_urbs_in_flight); + urb->transfer_buffer_length =3D 9; + } + + usb_get_urb(urb); + usb_anchor_urb(urb, &tascam->midi_out_anchor); + if (usb_submit_urb(urb, GFP_KERNEL) < 0) { + dev_err_ratelimited( + tascam->card->dev, + "Failed to submit MIDI OUT URB %d\n", + urb_index); + { + guard(spinlock_irqsave)(&tascam->midi_out_lock); + clear_bit(urb_index, + &tascam->midi_out_urbs_in_flight); + } + usb_unanchor_urb(urb); + usb_put_urb(urb); + break; /* Stop on error */ + } + } +} + +/** + * tascam_midi_out_open() - Opens the MIDI output substream. + * @substream: The ALSA rawmidi substream to open. + * + * This function stores a reference to the MIDI output substream in the + * driver's private data and initializes the MIDI running status. + * + * Return: 0 on success. + */ +static int tascam_midi_out_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + + tascam->midi_out_substream =3D substream; + /* Initialize the running status state for the packet packer. */ + tascam->midi_running_status =3D 0; + return 0; +} + +/** + * tascam_midi_out_close() - Closes the MIDI output substream. + * @substream: The ALSA rawmidi substream to close. + * + * Return: 0 on success. + */ +static int tascam_midi_out_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/** + * tascam_midi_out_drain() - Drains the MIDI output stream. + * @substream: The ALSA rawmidi substream. + * + * This function cancels any pending MIDI output work and kills all + * anchored MIDI output URBs, ensuring all data is sent or discarded. + */ +static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + bool in_flight =3D true; + + while (in_flight) { + in_flight =3D false; + for (int i =3D 0; i < NUM_MIDI_OUT_URBS; i++) { + if (test_bit(i, &tascam->midi_out_urbs_in_flight)) { + in_flight =3D true; + break; + } + } + if (in_flight) + schedule_timeout_uninterruptible(1); + } + + cancel_work_sync(&tascam->midi_out_work); + usb_kill_anchored_urbs(&tascam->midi_out_anchor); +} + +/** + * tascam_midi_out_trigger() - Triggers MIDI output stream activity. + * @substream: The ALSA rawmidi substream. + * @up: Boolean indicating whether to start (1) or stop (0) the stream. + * + * This function starts or stops the MIDI output workqueue based on the + * 'up' parameter. + */ +static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substrea= m, + int up) +{ + struct tascam_card *tascam =3D substream->rmidi->private_data; + + if (up) { + atomic_set(&tascam->midi_out_active, 1); + schedule_work(&tascam->midi_out_work); + } else { + atomic_set(&tascam->midi_out_active, 0); + } +} + +/** + * tascam_midi_out_ops - ALSA rawmidi operations for MIDI output. + * + * This structure defines the callback functions for MIDI output stream + * operations, including open, close, trigger, and drain. + */ +static const struct snd_rawmidi_ops tascam_midi_out_ops =3D { + .open =3D tascam_midi_out_open, + .close =3D tascam_midi_out_close, + .trigger =3D tascam_midi_out_trigger, + .drain =3D tascam_midi_out_drain, +}; + +int tascam_create_midi(struct tascam_card *tascam) +{ + int err; + + err =3D snd_rawmidi_new(tascam->card, "US144MKII MIDI", 0, 1, 1, + &tascam->rmidi); + if (err < 0) + return err; + + strscpy(tascam->rmidi->name, "US144MKII MIDI", + sizeof(tascam->rmidi->name)); + tascam->rmidi->private_data =3D tascam; + + snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &tascam_midi_in_ops); + snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &tascam_midi_out_ops); + + tascam->rmidi->info_flags |=3D SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + INIT_WORK(&tascam->midi_in_work, tascam_midi_in_work_handler); + INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler); + + return 0; +} diff --git a/sound/usb/usx2y/us144mkii_pcm.c b/sound/usb/usx2y/us144mkii_pc= m.c index 7bee8b4210a6..61d776b4a04e 100644 --- a/sound/usb/usx2y/us144mkii_pcm.c +++ b/sound/usb/usx2y/us144mkii_pcm.c @@ -56,19 +56,60 @@ void process_playback_routing_us144mkii(struct tascam_c= ard *tascam, const u8 *src_buffer, u8 *dst_buffer, size_t frames) { - /* This is a stub. Routing will be added in a later commit. */ - if (src_buffer !=3D dst_buffer) - memcpy(dst_buffer, src_buffer, frames * BYTES_PER_FRAME); + size_t f; + const u8 *src_12, *src_34; + u8 *dst_line, *dst_digital; + + for (f =3D 0; f < frames; ++f) { + src_12 =3D src_buffer + f * BYTES_PER_FRAME; + src_34 =3D src_12 + (2 * BYTES_PER_SAMPLE); + dst_line =3D dst_buffer + f * BYTES_PER_FRAME; + dst_digital =3D dst_line + (2 * BYTES_PER_SAMPLE); + + /* LINE OUTPUTS (ch1/2 on device) */ + if (tascam->line_out_source =3D=3D 0) /* "ch1 and ch2" */ + memcpy(dst_line, src_12, 2 * BYTES_PER_SAMPLE); + else /* "ch3 and ch4" */ + memcpy(dst_line, src_34, 2 * BYTES_PER_SAMPLE); + + /* DIGITAL OUTPUTS (ch3/4 on device) */ + if (tascam->digital_out_source =3D=3D 0) /* "ch1 and ch2" */ + memcpy(dst_digital, src_12, 2 * BYTES_PER_SAMPLE); + else /* "ch3 and ch4" */ + memcpy(dst_digital, src_34, 2 * BYTES_PER_SAMPLE); + } } =20 void process_capture_routing_us144mkii(struct tascam_card *tascam, const s32 *decoded_block, s32 *routed_block) { - /* This is a stub. Routing will be added in a later commit. */ - memcpy(routed_block, decoded_block, - FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * - DECODED_SAMPLE_SIZE); + int f; + const s32 *src_frame; + s32 *dst_frame; + + for (f =3D 0; f < FRAMES_PER_DECODE_BLOCK; f++) { + src_frame =3D decoded_block + (f * DECODED_CHANNELS_PER_FRAME); + dst_frame =3D routed_block + (f * DECODED_CHANNELS_PER_FRAME); + + /* ch1 and ch2 Source */ + if (tascam->capture_12_source =3D=3D 0) { /* analog inputs */ + dst_frame[0] =3D src_frame[0]; /* Analog L */ + dst_frame[1] =3D src_frame[1]; /* Analog R */ + } else { /* digital inputs */ + dst_frame[0] =3D src_frame[2]; /* Digital L */ + dst_frame[1] =3D src_frame[3]; /* Digital R */ + } + + /* ch3 and ch4 Source */ + if (tascam->capture_34_source =3D=3D 0) { /* analog inputs */ + dst_frame[2] =3D src_frame[0]; /* Analog L (Duplicate) */ + dst_frame[3] =3D src_frame[1]; /* Analog R (Duplicate) */ + } else { /* digital inputs */ + dst_frame[2] =3D src_frame[2]; /* Digital L */ + dst_frame[3] =3D src_frame[3]; /* Digital R */ + } + } } =20 int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int ra= te) @@ -169,14 +210,12 @@ int us144mkii_configure_device_for_rate(struct tascam= _card *tascam, int rate) if (err < 0) goto fail; =20 - kfree(rate_payload_buf); return 0; =20 fail: dev_err(&dev->dev, "Device configuration failed at rate %d with error %d\n", rate, err); - kfree(rate_payload_buf); return err; } =20 --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f53.google.com (mail-ej1-f53.google.com [209.85.218.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B349527E052; Tue, 12 Aug 2025 12:56:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003411; cv=none; b=AQ2XyLKwqbFGAd4RbY5nQabXslBJn3gsPa1wJgif0EAkToxQF7k5cmzSytBGLt6ey0NBAU8VEyHWOS1CugqjfqD/SVS8lW8qSAh3TBYFnya5saiyqGhKejkfmnRj/9fJaqlXn6rFUuuHoyxTJiTP5T12S1JzQgLpQnWvA7VETcY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003411; c=relaxed/simple; bh=XXEWb42qJiWZo4Du0CRE6Yam0gPdSTS3/b8BYir1sbk=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=ngktJ8UyDIpoPnxV5eSTOEdDPhu3SttBWehOE6t1gbjeJ5O1nCFAinuR/N6QHaLplo9bDlmUq3pIRZOBjrEzSLmr+jE4xbx+2fHAibeU4sKWqorcpTx7L3Ktv5w3v80qqHt5I345B5GNVS/KOFE9wdJCNhe52J617RnBXuARl4Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=XYzUdJ2G; arc=none smtp.client-ip=209.85.218.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XYzUdJ2G" Received: by mail-ej1-f53.google.com with SMTP id a640c23a62f3a-ae0bde4d5c9so1028774066b.3; Tue, 12 Aug 2025 05:56:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003408; x=1755608208; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=SewnEnAKmtNzuvxDsbzRVoc3jMFrgs3azT7RAG1IenM=; b=XYzUdJ2GB0V1Mx6PtxehS9OPqFkU2fzRMb5K5W7lKJeKxB8PdYQJv4kkDue8lPvpGY 4TlFwyu6fPFekJpYdO4XygFUny4FH1px8eFnA9LPXQKNAraxzTVCn0v0eebzd7I0LOUp NHwTts6hFEOgkRQobPazpS4J2tNTpbu2+83rAJ4w1KDzKP9moN4TXmKVSsivc1kPWc1E doRRxEvPaK4tOWyxbG0FJWrWwPSQkcRO4OfaQjRhT60w4YWFrXL1qKjFJVWDVNUjJR79 79dkR1KXltMjOTXIqjOj1ij3I4LFraiYJgoE39jU2MZvZmk5llai8a6P8slNjIA76Fu5 hyVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003408; x=1755608208; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=SewnEnAKmtNzuvxDsbzRVoc3jMFrgs3azT7RAG1IenM=; b=afK1G9sG839FQWTJZFzJ5bFaBKJhW7yHFW7EBH7Cl4ERNz9/5D/h2WPIfIK0iTuWDe 4+mNmYGwXpW15AOlRCDjd9awLH2vZiIxR2CeqLJ00CbvaAvO7E3NbTkjslpilYwAl+ui HEdAL6ZbUhFwS4bk+MIvVwgKexCuggPcHMtkndbGj2G5Ds+z+qLjV2JiCk7EhtluWxs7 JTi8BhokmmxkdFerKnkPSfJQglxnY1td3YfEI5u7EJaxJR52byXjzwGmGf6VOf/SXB3Z md1rWn6dOzA0kw0uSnKGbunfxr/0ta9OfTG7DldRprBypW/neESauyJqLJij5EJ6v5BV 9Feg== X-Forwarded-Encrypted: i=1; AJvYcCWDUOw9JIXOmPnfGDHvab0V6b8qYxBF4tI108YJYH/EhvOvd2nnR+gLXFScctc4B3ftykLTl2uacmCE9w==@vger.kernel.org X-Gm-Message-State: AOJu0YxQRpephtA5rO8xlYtfSFU1MH9ITukz4uZVk5lSytY8Hi0IZcpM p9EQWF74yWgd33bNJRrsyJDusP03er7JRPVCVphYXujFQv/ThsC25JrgFja8S12JFLk= X-Gm-Gg: ASbGncsTkgUbsURuq+854K5tnF6lIJH1gJd4AwDMH3ju0t916qIp82tozSu+fDLn+oR xmNYiFQzTm325oBHjL76/8tr+RElIh4A3zZDxWjn4zwuNbp0+xgxj0eOL70NWXNDI2vqC9I8WL4 1vfQgyZ/o2/CHxB6wEs+amqaDVeIlKmwL9N9HdNz81YJRr1WaaNQqaxl5021IoEjac7i3c6IUDg Dkd6mMjdnXFUg3YyctijIo6kLuE9qzsISaUaZWBnBoqutW9pGPrq/m0BWtlddFQyv69M7m4VxAZ aF5SlU+tMvDKMny5prlhT7JxuuqrAWA9KFqcrcO0KmZVlNs3hpcMwv1ofhuiVU/9nxbxdJ5h+fE +FIBnaaYSZh1rBd3LuRJDsCNl+sG8wHDuK9VHNIidnjCFqKI4l2X4b1PYX8qAKYdpd5y44MMVVx 5uqMFZ6A== X-Google-Smtp-Source: AGHT+IGFUwzn9SgWCoSQwab1IZjr+bv7V/GhxUzzQI+mWECUjzjGRevi0nAm8U1fnF7mcrUrwIMjtw== X-Received: by 2002:a17:906:794a:b0:af9:d6c1:155a with SMTP id a640c23a62f3a-afa1e128db6mr303424366b.44.1755003407660; Tue, 12 Aug 2025 05:56:47 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:47 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 6/6] ALSA: usb-audio: Add infrastructure for TASCAM US-144MKII Date: Tue, 12 Aug 2025 14:56:31 +0200 Message-Id: <20250812125633.79270-12-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This commit adds Kconfig and Makefile entries for TASCAM US-144MKII USB audio/MIDI interface support. It includes the configuration option and links new driver files. The Kconfig entry for US-144MKII is added. The Makefile is updated to compile new driver components. The US-122L driver's device ID table is adjusted to remove the US-144MKII entry, as it will now be handled by its dedicated driver. Signed-off-by: =C5=A0erif Rami --- sound/usb/Kconfig | 12 ++++++++++++ sound/usb/usx2y/Makefile | 2 ++ sound/usb/usx2y/us122l.c | 6 ------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 41c47301bc19..9b890abd96d3 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -117,6 +117,18 @@ config SND_USB_US122L To compile this driver as a module, choose M here: the module will be called snd-usb-us122l. =20 +config SND_USB_US144MKII + tristate "Tascam US-144MKII USB driver" + depends on X86 || COMPILE_TEST + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Tascam US-144MKII USB Audio/MIDI + interface. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-us144mkii. + config SND_USB_6FIRE tristate "TerraTec DMX 6Fire USB" select FW_LOADER diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile index fc033aba03a4..9db87ae39ee9 100644 --- a/sound/usb/usx2y/Makefile +++ b/sound/usb/usx2y/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 snd-usb-usx2y-y :=3D usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o snd-usb-us122l-y :=3D us122l.o +snd-usb-us144mkii-y :=3D us144mkii.o us144mkii_pcm.o us144mkii_playback.o = us144mkii_capture.o us144mkii_midi.o us144mkii_controls.o =20 obj-$(CONFIG_SND_USB_USX2Y) +=3D snd-usb-usx2y.o obj-$(CONFIG_SND_USB_US122L) +=3D snd-usb-us122l.o +obj-$(CONFIG_SND_USB_US144MKII) +=3D snd-usb-us144mkii.o \ No newline at end of file diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 2ace3ba46091..8dbbefe3e730 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -686,12 +686,6 @@ static const struct usb_device_id snd_us122l_usb_id_ta= ble[] =3D { .idVendor =3D 0x0644, .idProduct =3D USB_ID_US122MKII }, - { - .match_flags =3D USB_DEVICE_ID_MATCH_DEVICE, - .idVendor =3D 0x0644, - .idProduct =3D USB_ID_US144MKII, - .driver_info =3D US122L_FLAG_US144 - }, { /* terminator */ } }; MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table); --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f48.google.com (mail-ej1-f48.google.com [209.85.218.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A736A27F170; Tue, 12 Aug 2025 12:56:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003412; cv=none; b=d0EpC5swR+eKvtvHkW6nt32Yq2CcNp09Pw2nabgMmKBbcPysNI2p777itF7slY8Eae3eCDonxzlBOVZqkdO2jri4VWmFIFwcfEO+kWvSRRqpXNETCDtBGhvkoIG9M2IM7T2a4E/KI/xS0h3elbpDPsGOTusRZ1gQPm/IP7706NU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003412; c=relaxed/simple; bh=zYImU4jxvzbeYDCcwW8grQtTwXDcIZSor5qvW0OKkxU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=Ef9gcOV2oi5fhytGR9ShWHFiM1hkUpE8ZdwK1U3biAVAwHK0FkggTv/r/JRUJ3VPgR5t9LYFEh0DD/GFvAByoXsT1vqud5V+hJbQhBMP32qopEKYjcv1yH8+mXsUfF3BdKzkqpHRTjc8Xd5rNJUua98cdS3sRxE7BSk0DOe+D+c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=IIN/drrG; arc=none smtp.client-ip=209.85.218.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="IIN/drrG" Received: by mail-ej1-f48.google.com with SMTP id a640c23a62f3a-af90fd52147so805719066b.3; Tue, 12 Aug 2025 05:56:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003409; x=1755608209; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Bhnbn+hKgeh6Jp1ra5XQn6HuC0UPdIm1P7jBz/93Tj0=; b=IIN/drrGsHd5hKU2s3x8m/fNWDRo1hmdGb1M74cPebvx+bnnmYVmxnSbbHJSmj2VxW ghvYYRsoOxy8p29Zs0IgivCeEdx45Cl3VlYXiSgCUaE7Nm0OVj+l/o0QklOlcdTGyNqH RaVrz29rBAYMN9BDJVGG9QlEP9nqyiZbjoioEF+Il5qQpZwpn4j22Ha2BwO+2OnV0gUK xhdEoFG2nxBoz62SvZpR9CHuMu7aa+4FXrpKhyCmrxwOWpzrsHHnC1p+JuTvZHhZIoYl M+uWi2aqRJiHulawV+Vd/zUyCR0MB8YUHTMpfWNSAwn08ZkAflMNPKc3cFxDQCyzwLhH Cgbg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003409; x=1755608209; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Bhnbn+hKgeh6Jp1ra5XQn6HuC0UPdIm1P7jBz/93Tj0=; b=WndMbL2L3+Oui/LzLM/2I2kySTsZEup3nSnn5ZhZoLcQnJWxRI7MCncTQh58IffHhV 34f/aHPIi31q2Nn1cvUeCKO0D3EvUamensr4YGWcI3U0YL8jNhbvMgQ3wqNBk93G8Mjm d3QZV4vQibTi0xse35xQJWdOR8LaU6u3j4cu74HmZmUFHq2kzC0P38x2a8pd2aHpRx0h K84xWmpQeKA6p3RZKwLAUndqW6pGJJey12dSgVXOZMy8FQ18YnRrB32tYDiDla9Wze9N NR8VgS6pnBW6FrHkMV0DZi2r8J+xkKfhG0LGW2PgLZ8wEGLURyiLNT+gIucFK4h/m7t/ UGgw== X-Forwarded-Encrypted: i=1; AJvYcCXdxX1RWtvIW1Xi5wvZzNvLODYdm0fl++Uld9X+bOQffE1FljrzoV4Y3DG/60z/O729yf3IpLNlGaR11w==@vger.kernel.org X-Gm-Message-State: AOJu0YwEzDPMxS5aceiYMGfLrD8SoCMNVJzjl5YYbJ0jPENCFzy7nQBm qsYvr51Cj7kIlN/abmL595NFmVfxZ4qjBTJMlIL6E1CTM+dQodQWXtWkbDzosTxOAK4= X-Gm-Gg: ASbGncv5L4iXcdW3+9v50Of7j2efv4B0X90xnvO84HrQRvLDPk4JwPhunC26nSsrxQ7 mF8SMh8WpMGg6+chTeVf+V6KMrMqoVXUlrA+z03QclPqNcqMgZ9lrdhmlKuH2pzsbq3YxpKfB8A aDssR40lGh7ksUeJ6jy+GeACpg4Jllm8VcuwfuQe5b7jwVj93tqajme828z5PSGG6pBkyJpQKM/ ZlDLElSjnKdKZHYnD1A24M7RKArIyerEK6ogmItRs1mAMWOdHCMlVdHX+R22viwFXJxW0wa8pEt kpIJ3FQMcONhKAC2H4yq3hcLlmq61NT1iWXOpbrpjU7RhYJXsFH/yiTQ5Hcj/qvRAjfSaV1fE7U UbHNGnMLk/uY7hW0rBoANanlicD4n7u+HsTrb7CHZBVjZhINjQydUYoOFSzaNc3NFF34cNW0lTQ aWDin9pQ== X-Google-Smtp-Source: AGHT+IE4hB7CzeSMV3N0nGmhP4M7UVj+96HqC8KgrLHNgURg/MyFI0YKtInajyFewoYW3Ji34tn1Aw== X-Received: by 2002:a17:907:3ea4:b0:af9:c1fa:92af with SMTP id a640c23a62f3a-af9c6375c9fmr1563320866b.6.1755003408679; Tue, 12 Aug 2025 05:56:48 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:48 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 6/7] ALSA: usb-audio: us144mkii: Add deep sleep command Date: Tue, 12 Aug 2025 14:56:32 +0200 Message-Id: <20250812125633.79270-13-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add a deep sleep vendor command to be sent during suspend, allowing the device to enter a lower power state. Signed-off-by: =C5=A0erif Rami --- sound/usb/usx2y/us144mkii.c | 7 +++++++ sound/usb/usx2y/us144mkii.h | 1 + 2 files changed, 8 insertions(+) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index f7944eb2fb93..e452250fc5b4 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -322,6 +322,13 @@ static int tascam_suspend(struct usb_interface *intf, = pm_message_t message) usb_kill_anchored_urbs(&tascam->midi_in_anchor); usb_kill_anchored_urbs(&tascam->midi_out_anchor); =20 + dev_info(&intf->dev, "sending deep sleep command\n"); + int err =3D usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0), + VENDOR_REQ_DEEP_SLEEP, RT_H2D_VENDOR_DEV, + 0x0000, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS); + if (err < 0) + dev_err(&intf->dev, "deep sleep command failed: %d\n", err); + return 0; } =20 diff --git a/sound/usb/usx2y/us144mkii.h b/sound/usb/usx2y/us144mkii.h index c740a0b5a0ea..ecc4c2fed9e6 100644 --- a/sound/usb/usx2y/us144mkii.h +++ b/sound/usb/usx2y/us144mkii.h @@ -46,6 +46,7 @@ enum uac_control_selector { =20 enum tascam_vendor_request { VENDOR_REQ_REGISTER_WRITE =3D 0x41, + VENDOR_REQ_DEEP_SLEEP =3D 0x44, VENDOR_REQ_MODE_CONTROL =3D 0x49, }; =20 --=20 2.39.5 From nobody Sat Oct 4 21:00:51 2025 Received: from mail-ej1-f49.google.com (mail-ej1-f49.google.com [209.85.218.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 05CFB27D77A; Tue, 12 Aug 2025 12:56:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003414; cv=none; b=dMObUjEKNSfEfR0HClXEBEnIhh0qAEnKTdtYpcJQsiD+r98brU/9JHtuRxZ9h4YqiKjZ8w3vug5E1B2yCHI6YYJWTj8z+87yTSVzqYzhioO1dzhH72ZbNRUx95XSHnBao1pzxpnViTCJ69Ko6bJK4RxDPUDfbjU0slCGI+vfyTs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755003414; c=relaxed/simple; bh=XXEWb42qJiWZo4Du0CRE6Yam0gPdSTS3/b8BYir1sbk=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=U07rZCbQT3CkrekVL+VwsyjzE++MXHDatwSisFAWYT6PiofafoWYI7pj6Dyd7/NrMp5BxAGWfiP5tKLaSamSEchzHFJ5eTok++DANwmpbmCDJ3oyU6+7kIzv7o8pQO2TAfkR0AV3sfCcgjy2ZQIs2KN/Mgg+HMji2w5dtD/RIYI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=i+Ti5DK2; arc=none smtp.client-ip=209.85.218.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="i+Ti5DK2" Received: by mail-ej1-f49.google.com with SMTP id a640c23a62f3a-af937728c3eso1005497966b.0; Tue, 12 Aug 2025 05:56:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755003410; x=1755608210; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=SewnEnAKmtNzuvxDsbzRVoc3jMFrgs3azT7RAG1IenM=; b=i+Ti5DK2KEUJJbCUeYjMippZmW3SVa+ZiK99aBQDA1UfPTwJYu5Md5gyfxG9pxHFKQ kSx5d52EYnfQcRm5Bzsjwlk21EWbDB7D7koNvpsl5oUCyckgQogpzTK7OYhKwbklBtG1 a6G1JopkOfT2BAQvZ3z/hV/BLIHErJUhPJ4PCBV6yK8cl8FEbACJ0xHHD7bvCH2fxb5a AWgm6wgusWDG+lSrRMjDC/InQEYOLv9JmvHGURGK2DZyzT3vC4ISjdxLHtOPFXz3XMRZ TA3E+sL+1ROIKpTBXr5j9bCyVwN2siIZI3Vkr1WPhclUluFdlUs3Af4oxC5J+tlJDvTv Fxfg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755003410; x=1755608210; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=SewnEnAKmtNzuvxDsbzRVoc3jMFrgs3azT7RAG1IenM=; b=CJX6zu2pb4V0pS217PF01x+XwzpaYnXDFS0veeil2JlDrw+QxXY2vbAAEz5+SIXc0i 62WyjI/0Ap3c2R5dR1dPC4dxdfYOKBHCS9p6Tp+IOJp98dYR2jgwZDRVeBR2+piHlvva rTmwHdqFNZzuxjGkjyDpyzyvmyRCg4axm4gvHBV2HL7BQ90dOMvQpDOsybZo3BaRwPa/ 9MEaHCQF+V9siNweUcI5tMeaVR0PXNfXUQPfGK2WBscj1+3s6LGvUrRqmXWWsu+5OunI ltp/bNxPZ+uf9fP77P2Xk7v7Tou6sF7/8pRHdxZdR8CRt97bHwuN7fg+GSGWBBW73K2g 5Nrg== X-Forwarded-Encrypted: i=1; AJvYcCXaVqLpdQYbZSWAaGLPfXpKOfGrscg0KSZo3g0+I+5PmXUFczrM6e5VUeJUgDFsflQPcjQzcppuMp1PJg==@vger.kernel.org X-Gm-Message-State: AOJu0YxHu+b/BooTIUL2IXkrxj/yH4Q8X8mu6qqWsmjO7H/sg+PaDtFz rq1QqSLXIVMOSxTgG6P1/MQykDvly5vgND6XFV0yLVGsMBILrvY/hXRM X-Gm-Gg: ASbGncsvkgZCKEYdZ/hGPBFgVFKoNuUw3IDkuhtPQnrT90C8nVKtU/g4Lq5DbtsxKlc MbUEEtRH4U4TSeOgEyxQW2NSawuZMLvSIvo5WcxGIG5IorGeFGKYSJSjPIpE72mrCKxVWTazBEz NXwXGeaqBbDQA3MLh2mJS9fD84mRdJM68UtW5Gu0wgNYyTNi/v30N/1X38IUYkrFjeMxXcH4nv7 lKhsr8iRiUV+IzpzlwmonEAn6srYiIsHtDcRKfR1XxbX7Sqd77nA4qbMY4PXzuTCSpujUCGGWkc HZHA+PKLYPTZ6SJjlVRFPsW7VhdGP/xF0L5oa/cHQl68bIH9qa44ye9ra+4ZmTJkvoDaXkZuf82 jBT+DRvR7HWZGxpRphQo17hUgIYcTF6YSuz5gw3WHNrHEAP3rMYsDYpY3cD6Tl0wg10rVjIpp1i UoRVXCQg== X-Google-Smtp-Source: AGHT+IE+vqMk4P+MbwM4HWioLeesfqRDxLFJLOEVmi3u1V2IOOh3UjDUrPNmruLk95l264+D8ZPyow== X-Received: by 2002:a17:906:f5a9:b0:ae8:8d00:76c3 with SMTP id a640c23a62f3a-afa1d772c6dmr326631966b.29.1755003409748; Tue, 12 Aug 2025 05:56:49 -0700 (PDT) Received: from localhost.localdomain (93-87-121-223.dynamic.isp.telekom.rs. [93.87.121.223]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-af91a0a3361sm2199158266b.39.2025.08.12.05.56.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 12 Aug 2025 05:56:49 -0700 (PDT) From: =?UTF-8?q?=C5=A0erif=20Rami?= To: Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org, =?UTF-8?q?=C5=A0erif=20Rami?= Subject: [PATCH v2 7/7] ALSA: usb-audio: Add infrastructure for TASCAM US-144MKII Date: Tue, 12 Aug 2025 14:56:33 +0200 Message-Id: <20250812125633.79270-14-ramiserifpersia@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250812125633.79270-1-ramiserifpersia@gmail.com> References: <20250810124958.25309-1-ramiserifpersia@gmail.com> <20250812125633.79270-1-ramiserifpersia@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This commit adds Kconfig and Makefile entries for TASCAM US-144MKII USB audio/MIDI interface support. It includes the configuration option and links new driver files. The Kconfig entry for US-144MKII is added. The Makefile is updated to compile new driver components. The US-122L driver's device ID table is adjusted to remove the US-144MKII entry, as it will now be handled by its dedicated driver. Signed-off-by: =C5=A0erif Rami --- sound/usb/Kconfig | 12 ++++++++++++ sound/usb/usx2y/Makefile | 2 ++ sound/usb/usx2y/us122l.c | 6 ------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 41c47301bc19..9b890abd96d3 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -117,6 +117,18 @@ config SND_USB_US122L To compile this driver as a module, choose M here: the module will be called snd-usb-us122l. =20 +config SND_USB_US144MKII + tristate "Tascam US-144MKII USB driver" + depends on X86 || COMPILE_TEST + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Tascam US-144MKII USB Audio/MIDI + interface. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-us144mkii. + config SND_USB_6FIRE tristate "TerraTec DMX 6Fire USB" select FW_LOADER diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile index fc033aba03a4..9db87ae39ee9 100644 --- a/sound/usb/usx2y/Makefile +++ b/sound/usb/usx2y/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 snd-usb-usx2y-y :=3D usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o snd-usb-us122l-y :=3D us122l.o +snd-usb-us144mkii-y :=3D us144mkii.o us144mkii_pcm.o us144mkii_playback.o = us144mkii_capture.o us144mkii_midi.o us144mkii_controls.o =20 obj-$(CONFIG_SND_USB_USX2Y) +=3D snd-usb-usx2y.o obj-$(CONFIG_SND_USB_US122L) +=3D snd-usb-us122l.o +obj-$(CONFIG_SND_USB_US144MKII) +=3D snd-usb-us144mkii.o \ No newline at end of file diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 2ace3ba46091..8dbbefe3e730 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -686,12 +686,6 @@ static const struct usb_device_id snd_us122l_usb_id_ta= ble[] =3D { .idVendor =3D 0x0644, .idProduct =3D USB_ID_US122MKII }, - { - .match_flags =3D USB_DEVICE_ID_MATCH_DEVICE, - .idVendor =3D 0x0644, - .idProduct =3D USB_ID_US144MKII, - .driver_info =3D US122L_FLAG_US144 - }, { /* terminator */ } }; MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table); --=20 2.39.5