[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250803203409.17005-1-ramiserifpersia@gmail.com>
Date: Sun, 3 Aug 2025 22:34:09 +0200
From: Šerif Rami <ramiserifpersia@...il.com>
To: linux-sound@...r.kernel.org,
linux-kernel@...r.kernel.org
Cc: Jaroslav Kysela <perex@...ex.cz>,
Takashi Iwai <tiwai@...e.com>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Mark Brown <broonie@...nel.org>,
Arnd Bergmann <arnd@...db.de>,
Wesley Cheng <quic_wcheng@...cinc.com>,
Šerif Rami <ramiserifpersia@...il.com>
Subject: [RFC PATCH v3] ALSA: usb-audio: Add support for TASCAM US-144MKII
Hi,
Apologies for the invalid patches and the noise from my earlier attempts.
I have corrected my workflow and am now submitting this properly.
This patch introduces a new, dedicated driver for the TASCAM US-144MKII.
It replaces the partial support that previously existed
in the `us122l` driver, which now no longer targets this device.
The new driver implements ALSA compatible duplex and RawMIDI implementation of proprietary MIDI protocol.
This v3 patch has been tested as follows:
The code has been checked against the kernel's coding style guidelines.
It compiles cleanly without any warnings or errors.
I have performed a full kernel build against the latest master branch, installed it, and successfully verified that the module loads and functions correctly with the hardware.
Any feedback is greatly appreciated.
Signed-off-by: Šerif Rami <ramiserifpersia@...il.com>
---
sound/usb/Kconfig | 13 +
sound/usb/usx2y/Makefile | 2 +
sound/usb/usx2y/us122l.c | 6 -
sound/usb/usx2y/us144mkii.c | 655 +++++++++++++++++++++++++++
sound/usb/usx2y/us144mkii.h | 331 ++++++++++++++
sound/usb/usx2y/us144mkii_capture.c | 309 +++++++++++++
sound/usb/usx2y/us144mkii_controls.c | 424 +++++++++++++++++
sound/usb/usx2y/us144mkii_midi.c | 417 +++++++++++++++++
sound/usb/usx2y/us144mkii_pcm.c | 456 +++++++++++++++++++
sound/usb/usx2y/us144mkii_pcm.h | 162 +++++++
sound/usb/usx2y/us144mkii_playback.c | 453 ++++++++++++++++++
11 files changed, 3222 insertions(+), 6 deletions(-)
create mode 100644 sound/usb/usx2y/us144mkii.c
create mode 100644 sound/usb/usx2y/us144mkii.h
create mode 100644 sound/usb/usx2y/us144mkii_capture.c
create mode 100644 sound/usb/usx2y/us144mkii_controls.c
create mode 100644 sound/usb/usx2y/us144mkii_midi.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/Kconfig b/sound/usb/Kconfig
index 41c47301bc19..ef6be7c06d89 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -117,6 +117,19 @@ config SND_USB_US122L
To compile this driver as a module, choose M here: the module
will be called snd-usb-us122l.
+config SND_USB_US144MKII
+ tristate "Tascam US-144MKII USB driver"
+ depends on X86 || COMPILE_TEST
+ select SND_HWDEP
+ select SND_RAWMIDI
+ select SND_PCM
+ help
+ Say Y here to include support for Tascam US-144MKII USB Audio/MIDI
+ interfaces.
+
+ 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..eca3680ad231 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 := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
snd-usb-us122l-y := us122l.o
+snd-usb-us144mkii-y := us144mkii.o us144mkii_pcm.o us144mkii_playback.o us144mkii_capture.o us144mkii_midi.o us144mkii_controls.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o
+obj-$(CONFIG_SND_USB_US144MKII) += snd-usb-us144mkii.o
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_table[] = {
.idVendor = 0x0644,
.idProduct = USB_ID_US122MKII
},
- {
- .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
- .idVendor = 0x0644,
- .idProduct = USB_ID_US144MKII,
- .driver_info = US122L_FLAG_US144
- },
{ /* terminator */ }
};
MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table);
diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c
new file mode 100644
index 000000000000..cbf29f536878
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+/*
+ * ALSA Driver for TASCAM US-144MKII Audio Interface
+ */
+
+#include "us144mkii.h"
+
+MODULE_AUTHOR("Šerif Rami <ramiserifpersia@...il.com>");
+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 the number of TASCAM devices probed.
+ */
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = { 1, [1 ...(SNDRV_CARDS - 1)] = 0 };
+static int dev_idx;
+
+static struct usb_driver tascam_alsa_driver;
+
+/* --- Forward Declarations --- */
+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);
+
+/**
+ * driver_version_show() - Sysfs attribute to display the driver version.
+ * @dev: Pointer to the device structure.
+ * @attr: Pointer to the device attribute structure.
+ * @buf: Buffer to write the version string into.
+ *
+ * This function is a sysfs callback that provides the current driver version
+ * string to user-space when the 'driver_version' attribute is read.
+ *
+ * Return: The number of bytes written to the buffer.
+ */
+static ssize_t driver_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", DRIVER_VERSION);
+}
+static DEVICE_ATTR_RO(driver_version);
+
+/**
+ * tascam_free_urbs() - Free all allocated URBs and associated buffers.
+ * @tascam: the tascam_card instance
+ *
+ * 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)
+{
+ int i;
+
+ usb_kill_anchored_urbs(&tascam->playback_anchor);
+ for (i = 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] = NULL;
+ }
+ }
+
+ usb_kill_anchored_urbs(&tascam->feedback_anchor);
+ for (i = 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] = NULL;
+ }
+ }
+
+ usb_kill_anchored_urbs(&tascam->capture_anchor);
+ for (i = 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] = NULL;
+ }
+ }
+
+ usb_kill_anchored_urbs(&tascam->midi_in_anchor);
+ for (i = 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] = NULL;
+ }
+ }
+
+ usb_kill_anchored_urbs(&tascam->midi_out_anchor);
+ for (i = 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] = NULL;
+ }
+ }
+
+ kfree(tascam->playback_routing_buffer);
+ tascam->playback_routing_buffer = NULL;
+ kfree(tascam->capture_routing_buffer);
+ tascam->capture_routing_buffer = NULL;
+ kfree(tascam->capture_decode_dst_block);
+ tascam->capture_decode_dst_block = NULL;
+ kfree(tascam->capture_decode_raw_block);
+ tascam->capture_decode_raw_block = NULL;
+ kfree(tascam->capture_ring_buffer);
+ tascam->capture_ring_buffer = NULL;
+}
+
+/**
+ * tascam_alloc_urbs() - Allocate all URBs and associated buffers.
+ * @tascam: the tascam_card instance
+ *
+ * This function allocates and initializes all URBs for playback, feedback,
+ * capture, and MIDI, as well as the necessary buffers for data processing.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int tascam_alloc_urbs(struct tascam_card *tascam)
+{
+ int i;
+ size_t max_packet_size;
+
+ max_packet_size = ((96000 / 8000) + 2) * BYTES_PER_FRAME;
+ tascam->playback_urb_alloc_size =
+ max_packet_size * PLAYBACK_URB_PACKETS;
+
+ for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
+ struct urb *urb =
+ usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL);
+
+ if (!urb)
+ goto error;
+ tascam->playback_urbs[i] = urb;
+
+ urb->transfer_buffer = usb_alloc_coherent(
+ tascam->dev, tascam->playback_urb_alloc_size,
+ GFP_KERNEL, &urb->transfer_dma);
+ if (!urb->transfer_buffer)
+ goto error;
+
+ urb->dev = tascam->dev;
+ urb->pipe = usb_sndisocpipe(tascam->dev, EP_AUDIO_OUT);
+ urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+ urb->interval = 1;
+ urb->context = tascam;
+ urb->complete = playback_urb_complete;
+ }
+
+ tascam->feedback_urb_alloc_size =
+ FEEDBACK_PACKET_SIZE * MAX_FEEDBACK_PACKETS;
+
+ for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
+ struct urb *f_urb =
+ usb_alloc_urb(MAX_FEEDBACK_PACKETS, GFP_KERNEL);
+
+ if (!f_urb)
+ goto error;
+ tascam->feedback_urbs[i] = f_urb;
+
+ f_urb->transfer_buffer = 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 = tascam->dev;
+ f_urb->pipe =
+ usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK);
+ f_urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+ f_urb->interval = 4;
+ f_urb->context = tascam;
+ f_urb->complete = feedback_urb_complete;
+ }
+
+ tascam->capture_urb_alloc_size = CAPTURE_URB_SIZE;
+ for (i = 0; i < NUM_CAPTURE_URBS; i++) {
+ struct urb *c_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!c_urb)
+ goto error;
+ tascam->capture_urbs[i] = c_urb;
+
+ c_urb->transfer_buffer = 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 |= URB_NO_TRANSFER_DMA_MAP;
+ }
+
+ /* MIDI URB and buffer allocation */
+ for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
+ struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!m_urb)
+ goto error;
+ tascam->midi_in_urbs[i] = m_urb;
+ m_urb->transfer_buffer =
+ 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 |= URB_NO_TRANSFER_DMA_MAP;
+ }
+
+ for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
+ struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!m_urb)
+ goto error;
+ tascam->midi_out_urbs[i] = m_urb;
+ m_urb->transfer_buffer =
+ 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 |= URB_NO_TRANSFER_DMA_MAP;
+ }
+
+ tascam->capture_ring_buffer =
+ kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL);
+ if (!tascam->capture_ring_buffer)
+ goto error;
+
+ tascam->capture_decode_raw_block =
+ kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_KERNEL);
+ if (!tascam->capture_decode_raw_block)
+ goto error;
+
+ tascam->capture_decode_dst_block =
+ kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
+ DECODED_SAMPLE_SIZE,
+ GFP_KERNEL);
+ if (!tascam->capture_decode_dst_block)
+ goto error;
+
+ tascam->playback_routing_buffer =
+ kmalloc(tascam->playback_urb_alloc_size, GFP_KERNEL);
+ if (!tascam->playback_routing_buffer)
+ goto error;
+
+ tascam->capture_routing_buffer =
+ kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
+ DECODED_SAMPLE_SIZE,
+ GFP_KERNEL);
+ if (!tascam->capture_routing_buffer)
+ goto error;
+
+ 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 =
+ container_of(work, struct tascam_card, stop_work);
+
+ 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);
+ cancel_work_sync(&tascam->capture_work);
+}
+
+/**
+ * tascam_card_private_free() - Frees private data associated with the sound card.
+ * @card: Pointer to the ALSA sound card instance.
+ *
+ * This function is called when the sound card is being freed. It releases
+ * 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 = card->private_data;
+
+ if (tascam) {
+ kfifo_free(&tascam->midi_in_fifo);
+ if (tascam->dev) {
+ usb_put_dev(tascam->dev);
+ tascam->dev = 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 active
+ * 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 = usb_get_intfdata(intf);
+ int err;
+
+ if (!tascam)
+ return 0;
+
+ /*
+ * The device requires a specific sequence to enter a stable low-power
+ * state. First, ensure all data transmission is stopped before
+ * sending the final vendor command.
+ */
+ if (tascam->pcm)
+ snd_pcm_suspend_all(tascam->pcm);
+
+ /*
+ * Terminate all in-flight URBs to prevent access to the device
+ * after it has been put to sleep.
+ */
+ 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);
+ 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);
+
+ /*
+ * Send the vendor-specific "Deep Sleep" command. Failure to send this
+ * command before host-initiated suspend can leave the device in an
+ * unstable state, leading to system freezes on idle (autosuspend).
+ */
+ err = usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0),
+ 0x00, /* bRequest */
+ 0x40, /* bmRequestType: H2D, Vendor, Device */
+ 0x0044, /* wValue */
+ 0x0000, /* wIndex */
+ NULL, /* data */
+ 0, /* size */
+ 1000); /* timeout */
+ if (err < 0)
+ dev_err(&intf->dev, "failed to send deep sleep command: %d\n",
+ err);
+
+ 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 = 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 = 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 = 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;
+}
+
+/**
+ * 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 when a matching device
+ * 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, PCM device, and MIDI device.
+ * - Allocating and initializing URBs for audio and MIDI transfers.
+ *
+ * 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 = interface_to_usbdev(intf);
+ struct snd_card *card;
+ struct tascam_card *tascam;
+ int err;
+ char *handshake_buf;
+
+ if (dev->speed != USB_SPEED_HIGH)
+ dev_info(
+ &dev->dev,
+ "Device is connected to a USB 1.1 port, this is not supported.\n");
+
+ if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
+ tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0));
+ if (tascam) {
+ usb_set_intfdata(intf, tascam);
+ tascam->iface1 = intf;
+ }
+ return 0;
+ }
+
+ if (dev_idx >= 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 = kmalloc(1, GFP_KERNEL);
+ if (!handshake_buf)
+ return -ENOMEM;
+
+ err = 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);
+ kfree(handshake_buf);
+ return err;
+ }
+
+ if (handshake_buf[0] != 0x12 && handshake_buf[0] != 0x16 &&
+ handshake_buf[0] != 0x30) {
+ dev_err(&dev->dev, "Unexpected handshake value: 0x%x\n",
+ handshake_buf[0]);
+ kfree(handshake_buf);
+ return -ENODEV;
+ }
+ kfree(handshake_buf);
+
+ err = 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 = 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 = 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 = card->private_data;
+ tascam->dev = usb_get_dev(dev);
+ tascam->card = card;
+ tascam->iface0 = intf;
+
+ 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);
+
+ INIT_WORK(&tascam->stop_work, tascam_stop_work_handler);
+
+ if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL))
+ goto free_card;
+
+ card->private_free = tascam_card_private_free;
+
+ strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
+ strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname));
+ snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s",
+ card->shortname, USB_VID_TASCAM, USB_PID_TASCAM_US144MKII,
+ dev_name(&dev->dev));
+
+ err = device_create_file(&dev->dev, &dev_attr_driver_version);
+ if (err < 0)
+ dev_warn(&dev->dev,
+ "could not create driver_version attribute, err: %d\n",
+ err);
+
+ err = snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm);
+ if (err < 0)
+ goto free_card;
+ tascam->pcm->private_data = tascam;
+ strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name));
+
+ err = tascam_init_pcm(tascam->pcm);
+ if (err < 0)
+ goto free_card;
+
+ err = tascam_create_midi(tascam);
+ if (err < 0)
+ goto free_card;
+
+ err = tascam_create_controls(tascam);
+ if (err < 0)
+ goto free_card;
+
+ err = tascam_alloc_urbs(tascam);
+ if (err < 0)
+ goto free_card;
+
+ err = 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, including killing URBs, freeing
+ * the sound card, and releasing memory.
+ */
+static void tascam_disconnect(struct usb_interface *intf)
+{
+ struct tascam_card *tascam = usb_get_intfdata(intf);
+
+ if (!tascam)
+ return;
+
+ if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
+ device_remove_file(&tascam->dev->dev, &dev_attr_driver_version);
+ device_remove_file(&intf->dev, &dev_attr_driver_version);
+ 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);
+ snd_card_disconnect(tascam->card);
+ tascam_free_urbs(tascam);
+ snd_card_free(tascam->card);
+ dev_idx--;
+ }
+}
+
+static const struct usb_device_id tascam_usb_ids[] = {
+ { 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 = {
+ .name = DRIVER_NAME,
+ .probe = tascam_probe,
+ .disconnect = tascam_disconnect,
+ .suspend = tascam_suspend,
+ .resume = tascam_resume,
+ .id_table = 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..affe48d22ef6
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii.h
@@ -0,0 +1,331 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#ifndef __US144MKII_H
+#define __US144MKII_H
+
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <linux/kfifo.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+
+#define DRIVER_NAME "us144mkii"
+#define DRIVER_VERSION "1.7.4"
+
+/* --- USB Device Identification --- */
+#define USB_VID_TASCAM 0x0644
+#define USB_PID_TASCAM_US144MKII 0x8020
+
+/* --- 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
+
+/* --- 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_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
+#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
+
+enum uac_request {
+ UAC_SET_CUR = 0x01,
+ UAC_GET_CUR = 0x81,
+};
+
+enum uac_control_selector {
+ UAC_SAMPLING_FREQ_CONTROL = 0x0100,
+};
+
+enum tascam_vendor_request {
+ VENDOR_REQ_REGISTER_WRITE = 0x41,
+ VENDOR_REQ_MODE_CONTROL = 0x49,
+};
+
+enum tascam_mode_value {
+ MODE_VAL_HANDSHAKE_READ = 0x0000,
+ MODE_VAL_CONFIG = 0x0010,
+ MODE_VAL_STREAM_START = 0x0030,
+};
+
+#define HANDSHAKE_SUCCESS_VAL 0x12
+
+enum tascam_register {
+ REG_ADDR_UNKNOWN_0D = 0x0d04,
+ REG_ADDR_UNKNOWN_0E = 0x0e00,
+ REG_ADDR_UNKNOWN_0F = 0x0f00,
+ REG_ADDR_RATE_44100 = 0x1000,
+ REG_ADDR_RATE_48000 = 0x1002,
+ REG_ADDR_RATE_88200 = 0x1008,
+ REG_ADDR_RATE_96000 = 0x100a,
+ REG_ADDR_UNKNOWN_11 = 0x110b,
+};
+
+#define REG_VAL_ENABLE 0x0101
+
+/* --- URB Configuration --- */
+#define NUM_PLAYBACK_URBS 8
+#define PLAYBACK_URB_PACKETS 4
+#define NUM_FEEDBACK_URBS 4
+#define MAX_FEEDBACK_PACKETS 5
+#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 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
+
+/* --- 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
+
+/* --- 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 - Main driver data structure for the 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.
+ * @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.
+ * @playback_urb_alloc_size: Size of allocated buffer for each playback URB.
+ * @feedback_urbs: Array of URBs for feedback.
+ * @feedback_urb_alloc_size: Size of allocated buffer for each feedback URB.
+ * @playback_active: Atomic flag indicating if playback is active.
+ * @playback_frames_consumed: Total frames consumed by playback.
+ * @driver_playback_pos: Current position in the ALSA playback buffer (frames).
+ * @last_period_pos: Last reported period position for playback.
+ * @playback_routing_buffer: Intermediate buffer for playback routing.
+ *
+ * @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 (frames).
+ * @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 buffer.
+ * @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.
+ *
+ * @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_in_active: Atomic flag indicating if MIDI input is active.
+ * @midi_in_fifo: FIFO for raw MIDI input data.
+ * @midi_in_work: Work struct for deferred MIDI input processing.
+ * @midi_in_lock: Spinlock for MIDI input FIFO.
+ * @midi_out_urbs: Array of URBs for MIDI output.
+ * @midi_out_active: Atomic flag indicating if MIDI output is active.
+ * @midi_out_work: Work struct for deferred MIDI output processing.
+ * @midi_out_urbs_in_flight: Bitmap of MIDI output URBs currently in flight.
+ * @midi_out_lock: Spinlock for MIDI output.
+ * @midi_running_status: Stores the last MIDI status byte for running status.
+ *
+ * @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: Playback 3-4).
+ * @capture_12_source: Source for Capture channels 1-2 (0: Analog In, 1: Digital In).
+ * @capture_34_source: Source for Capture channels 3-4 (0: Analog In, 1: Digital In).
+ *
+ * @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.
+ * @feedback_patterns: Pointer to the current feedback patterns based on sample rate.
+ * @feedback_base_value: Base value for feedback pattern lookup.
+ * @feedback_max_value: Max value for feedback pattern lookup.
+ *
+ * @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 {
+ struct usb_device *dev;
+ struct usb_interface *iface0;
+ struct usb_interface *iface1;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_rawmidi *rmidi;
+
+ /* Playback stream */
+ struct snd_pcm_substream *playback_substream;
+ 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;
+ atomic_t playback_active;
+ u64 playback_frames_consumed;
+ snd_pcm_uframes_t driver_playback_pos;
+ u64 last_period_pos;
+ u8 *playback_routing_buffer;
+
+ /* Capture stream */
+ struct snd_pcm_substream *capture_substream;
+ struct urb *capture_urbs[NUM_CAPTURE_URBS];
+ size_t capture_urb_alloc_size;
+ atomic_t capture_active;
+ snd_pcm_uframes_t driver_capture_pos;
+ u64 capture_frames_processed;
+ 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;
+ struct work_struct capture_work;
+ struct work_struct stop_work;
+
+ /* MIDI streams */
+ struct snd_rawmidi_substream *midi_in_substream;
+ struct snd_rawmidi_substream *midi_out_substream;
+ struct urb *midi_in_urbs[NUM_MIDI_IN_URBS];
+ atomic_t midi_in_active;
+ struct kfifo midi_in_fifo;
+ struct work_struct midi_in_work;
+ spinlock_t midi_in_lock;
+ struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS];
+ atomic_t midi_out_active;
+ struct work_struct midi_out_work;
+ unsigned long midi_out_urbs_in_flight;
+ spinlock_t midi_out_lock;
+ u8 midi_running_status;
+
+ /* Shared state & Routing Matrix */
+ spinlock_t lock;
+ atomic_t active_urbs;
+ int current_rate;
+ unsigned int line_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */
+ unsigned int digital_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */
+ unsigned int capture_12_source; /* 0: Analog In, 1: Digital In */
+ unsigned int capture_34_source; /* 0: Analog In, 1: Digital In */
+
+ 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;
+
+ const unsigned int (*feedback_patterns)[8];
+ unsigned int feedback_base_value;
+ unsigned int feedback_max_value;
+
+ 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;
+};
+
+/* main */
+/**
+ * tascam_free_urbs() - Free all allocated URBs and associated buffers.
+ * @tascam: the tascam_card instance
+ *
+ * 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);
+
+/**
+ * tascam_alloc_urbs() - Allocate all URBs and associated buffers.
+ * @tascam: the tascam_card instance
+ *
+ * This function allocates and initializes all URBs for playback, feedback,
+ * capture, and MIDI, as well as the necessary buffers for data processing.
+ *
+ * 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, capture)
+ * and reset the active_urbs counter. It is used to gracefully stop streams
+ * from a workqueue context.
+ */
+void tascam_stop_work_handler(struct work_struct *work);
+
+/* 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 URB.
+ * @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-blocking
+ * 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 source)
+ * 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_capture.c b/sound/usb/usx2y/us144mkii_capture.c
new file mode 100644
index 000000000000..0db4f2c3de48
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii_capture.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#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 = snd_pcm_substream_chip(substream);
+
+ substream->runtime->hw = tascam_pcm_hw;
+ tascam->capture_substream = 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 = snd_pcm_substream_chip(substream);
+
+ tascam->capture_substream = 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 and ring buffer pointers.
+ *
+ * Return: 0 on success.
+ */
+static int tascam_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct tascam_card *tascam = snd_pcm_substream_chip(substream);
+
+ tascam->driver_capture_pos = 0;
+ tascam->capture_frames_processed = 0;
+ tascam->last_capture_period_pos = 0;
+ tascam->capture_ring_buffer_read_ptr = 0;
+ tascam->capture_ring_buffer_write_ptr = 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 = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u64 pos;
+ unsigned long flags;
+
+ if (!atomic_read(&tascam->capture_active))
+ return 0;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+ pos = tascam->capture_frames_processed;
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ return runtime ? pos % runtime->buffer_size : 0;
+}
+
+/**
+ * tascam_capture_ops - ALSA PCM operations for capture.
+ *
+ * This structure defines the callback functions for capture stream operations,
+ * including open, close, ioctl, hardware parameters, hardware free, prepare,
+ * trigger, and pointer.
+ */
+const struct snd_pcm_ops tascam_capture_ops = {
+ .open = tascam_capture_open,
+ .close = tascam_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = tascam_pcm_hw_params,
+ .hw_free = tascam_pcm_hw_free,
+ .prepare = tascam_capture_prepare,
+ .trigger = tascam_pcm_trigger,
+ .pointer = tascam_capture_pointer,
+};
+
+/**
+ * decode_tascam_capture_block() - Decodes a raw 512-byte block from the device.
+ * @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 function
+ * 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_block)
+{
+ int frame, bit;
+
+ memset(dst_block, 0,
+ FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
+ DECODED_SAMPLE_SIZE);
+
+ for (frame = 0; frame < FRAMES_PER_DECODE_BLOCK; ++frame) {
+ const u8 *p_src_frame_base = src_block + frame * 64;
+ s32 *p_dst_frame = dst_block + frame * 4;
+
+ s32 ch[4] = { 0 };
+
+ for (bit = 0; bit < 24; ++bit) {
+ u8 byte1 = p_src_frame_base[bit];
+ u8 byte2 = p_src_frame_base[bit + 32];
+
+ ch[0] = (ch[0] << 1) | (byte1 & 1);
+ ch[2] = (ch[2] << 1) | ((byte1 >> 1) & 1);
+
+ ch[1] = (ch[1] << 1) | (byte2 & 1);
+ ch[3] = (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] = ch[0] << 8;
+ p_dst_frame[1] = ch[1] << 8;
+ p_dst_frame[2] = ch[2] << 8;
+ p_dst_frame[3] = ch[3] << 8;
+ }
+}
+
+/**
+ * tascam_capture_work_handler() - Deferred work for processing capture data.
+ * @work: the work_struct instance
+ *
+ * This function runs in a kernel thread context, not an IRQ context. It reads
+ * raw data from the capture ring buffer, decodes it, applies routing, and
+ * copies the final audio data into the ALSA capture ring buffer. This offloads
+ * * the CPU-intensive decoding from the time-sensitive URB completion handlers.
+ */
+void tascam_capture_work_handler(struct work_struct *work)
+{
+ struct tascam_card *tascam =
+ container_of(work, struct tascam_card, capture_work);
+ struct snd_pcm_substream *substream = tascam->capture_substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned long flags;
+ u8 *raw_block = tascam->capture_decode_raw_block;
+ s32 *decoded_block = tascam->capture_decode_dst_block;
+ s32 *routed_block = tascam->capture_routing_buffer;
+
+ if (!substream || !substream->runtime)
+ return;
+ runtime = 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;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+ write_ptr = tascam->capture_ring_buffer_write_ptr;
+ read_ptr = tascam->capture_ring_buffer_read_ptr;
+ available_data = (write_ptr >= read_ptr) ?
+ (write_ptr - read_ptr) :
+ (CAPTURE_RING_BUFFER_SIZE - read_ptr +
+ write_ptr);
+ can_process = (available_data >= RAW_BYTES_PER_DECODE_BLOCK);
+
+ if (can_process) {
+ size_t i;
+
+ for (i = 0; i < RAW_BYTES_PER_DECODE_BLOCK; i++)
+ raw_block[i] =
+ tascam->capture_ring_buffer
+ [(read_ptr + i) %
+ CAPTURE_RING_BUFFER_SIZE];
+ tascam->capture_ring_buffer_read_ptr =
+ (read_ptr + RAW_BYTES_PER_DECODE_BLOCK) %
+ CAPTURE_RING_BUFFER_SIZE;
+ }
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ if (!can_process)
+ break;
+
+ decode_tascam_capture_block(raw_block, decoded_block);
+ process_capture_routing_us144mkii(tascam, decoded_block,
+ routed_block);
+
+ spin_lock_irqsave(&tascam->lock, flags);
+ if (atomic_read(&tascam->capture_active)) {
+ int f;
+
+ for (f = 0; f < FRAMES_PER_DECODE_BLOCK; ++f) {
+ u8 *dst_frame_start =
+ runtime->dma_area +
+ frames_to_bytes(
+ runtime,
+ tascam->driver_capture_pos);
+ s32 *routed_frame_start =
+ routed_block + (f * NUM_CHANNELS);
+ int c;
+
+ for (c = 0; c < NUM_CHANNELS; c++) {
+ u8 *dst_channel =
+ dst_frame_start +
+ (c * BYTES_PER_SAMPLE);
+ s32 *src_channel_s32 =
+ routed_frame_start + c;
+
+ memcpy(dst_channel,
+ ((char *)src_channel_s32) + 1,
+ 3);
+ }
+
+ tascam->driver_capture_pos =
+ (tascam->driver_capture_pos + 1) %
+ runtime->buffer_size;
+ }
+ }
+ spin_unlock_irqrestore(&tascam->lock, flags);
+ }
+}
+
+/**
+ * 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 process
+ * it. It then resubmits the URB to receive more data.
+ */
+void capture_urb_complete(struct urb *urb)
+{
+ struct tascam_card *tascam = urb->context;
+ int ret;
+ unsigned long flags;
+
+ if (urb->status) {
+ if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
+ urb->status != -ESHUTDOWN && urb->status != -ENODEV &&
+ urb->status != -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 i;
+ size_t write_ptr;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+ write_ptr = tascam->capture_ring_buffer_write_ptr;
+ for (i = 0; i < urb->actual_length; i++) {
+ tascam->capture_ring_buffer[write_ptr] =
+ ((u8 *)urb->transfer_buffer)[i];
+ write_ptr = (write_ptr + 1) % CAPTURE_RING_BUFFER_SIZE;
+ }
+ tascam->capture_ring_buffer_write_ptr = write_ptr;
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ schedule_work(&tascam->capture_work);
+ }
+
+ usb_get_urb(urb);
+ usb_anchor_urb(urb, &tascam->capture_anchor);
+ ret = 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);
+ }
+out:
+ usb_put_urb(urb);
+}
diff --git a/sound/usb/usx2y/us144mkii_controls.c b/sound/usb/usx2y/us144mkii_controls.c
new file mode 100644
index 000000000000..aa170730be07
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii_controls.c
@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#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[] = { "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[] = { "Analog In", "Digital 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)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 2;
+ if (uinfo->value.enumerated.item >= 2)
+ uinfo->value.enumerated.item = 1;
+ strscpy(uinfo->value.enumerated.name,
+ playback_source_texts[uinfo->value.enumerated.item],
+ sizeof(uinfo->value.enumerated.name));
+ return 0;
+}
+
+/**
+ * tascam_line_out_get() - ALSA control get callback for Line Outputs Source.
+ * @kcontrol: The ALSA kcontrol instance.
+ * @ucontrol: The ALSA control element value structure to fill.
+ *
+ * This function retrieves the current selection for the Line Outputs source
+ * (Playback 1-2 or Playback 3-4) from the driver's private data and populates
+ * 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 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.enumerated.item[0] = tascam->line_out_source;
+ return 0;
+}
+
+/**
+ * tascam_line_out_put() - ALSA control put callback for Line Outputs Source.
+ * @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 validates
+ * 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 = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.enumerated.item[0] > 1)
+ return -EINVAL;
+ if (tascam->line_out_source == ucontrol->value.enumerated.item[0])
+ return 0;
+ tascam->line_out_source = ucontrol->value.enumerated.item[0];
+ return 1;
+}
+
+/**
+ * tascam_line_out_control - ALSA kcontrol definition for Line Outputs Source.
+ *
+ * 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 analog
+ * line outputs of the device. It uses the `tascam_playback_source_info` for
+ * information and `tascam_line_out_get`/`tascam_line_out_put` for value handling.
+ */
+static const struct snd_kcontrol_new tascam_line_out_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line OUTPUTS Source",
+ .info = tascam_playback_source_info,
+ .get = tascam_line_out_get,
+ .put = 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 source
+ * (Playback 1-2 or Playback 3-4) from the driver's private data and populates
+ * 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 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.enumerated.item[0] = tascam->digital_out_source;
+ 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 validates
+ * 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 = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.enumerated.item[0] > 1)
+ return -EINVAL;
+ if (tascam->digital_out_source == ucontrol->value.enumerated.item[0])
+ return 0;
+ tascam->digital_out_source = ucontrol->value.enumerated.item[0];
+ return 1;
+}
+
+/**
+ * tascam_digital_out_control - ALSA kcontrol definition for Digital Outputs Source.
+ *
+ * This defines a new ALSA mixer control named "Digital OUTPUTS Source" that 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 value handling.
+ */
+static const struct snd_kcontrol_new tascam_digital_out_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Digital OUTPUTS Source",
+ .info = tascam_playback_source_info,
+ .get = tascam_digital_out_get,
+ .put = tascam_digital_out_put,
+};
+
+/**
+ * tascam_capture_source_info() - ALSA control info callback for capture source.
+ * @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, Digital In).
+ *
+ * Return: 0 on success.
+ */
+static int tascam_capture_source_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 2;
+ if (uinfo->value.enumerated.item >= 2)
+ uinfo->value.enumerated.item = 1;
+ strscpy(uinfo->value.enumerated.name,
+ capture_source_texts[uinfo->value.enumerated.item],
+ sizeof(uinfo->value.enumerated.name));
+ return 0;
+}
+
+/**
+ * tascam_capture_12_get() - ALSA control get callback for Capture channels 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 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.enumerated.item[0] = tascam->capture_12_source;
+ return 0;
+}
+
+/**
+ * tascam_capture_12_put() - ALSA control put callback for Capture channels 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 Digital In)
+ * based on the user's selection from the ALSA control element. It validates
+ * 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 = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.enumerated.item[0] > 1)
+ return -EINVAL;
+ if (tascam->capture_12_source == ucontrol->value.enumerated.item[0])
+ return 0;
+ tascam->capture_12_source = ucontrol->value.enumerated.item[0];
+ return 1;
+}
+
+/**
+ * tascam_capture_12_control - ALSA kcontrol definition for Capture channels 1 and 2 Source.
+ *
+ * This defines a new ALSA mixer control named "ch1 and ch2 Source" that allows
+ * the user to select between "Analog In" and "Digital In" for the first two
+ * capture channels of the device. It uses the `tascam_capture_source_info` for
+ * information and `tascam_capture_12_get`/`tascam_capture_12_put` for value handling.
+ */
+static const struct snd_kcontrol_new tascam_capture_12_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ch1 and ch2 Source",
+ .info = tascam_capture_source_info,
+ .get = tascam_capture_12_get,
+ .put = tascam_capture_12_put,
+};
+
+/**
+ * tascam_capture_34_get() - ALSA control get callback for Capture channels 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 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.enumerated.item[0] = tascam->capture_34_source;
+ return 0;
+}
+
+/**
+ * tascam_capture_34_put() - ALSA control put callback for Capture channels 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 Digital In)
+ * based on the user's selection from the ALSA control element. It validates
+ * 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 = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.enumerated.item[0] > 1)
+ return -EINVAL;
+ if (tascam->capture_34_source == ucontrol->value.enumerated.item[0])
+ return 0;
+ tascam->capture_34_source = ucontrol->value.enumerated.item[0];
+ return 1;
+}
+
+/**
+ * tascam_capture_34_control - ALSA kcontrol definition for Capture channels 3 and 4 Source.
+ *
+ * This defines a new ALSA mixer control named "ch3 and ch4 Source" that allows
+ * the user to select between "Analog In" and "Digital In" for the third and 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 = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ch3 and ch4 Source",
+ .info = tascam_capture_source_info,
+ .get = tascam_capture_34_get,
+ .put = 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, defining
+ * 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 = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 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 USB
+ * control message and populates the ALSA control element value. If the rate
+ * 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 =
+ (struct tascam_card *)snd_kcontrol_chip(kcontrol);
+ u8 *buf;
+ int err;
+ u32 rate = 0;
+
+ if (tascam->current_rate > 0) {
+ ucontrol->value.integer.value[0] = tascam->current_rate;
+ return 0;
+ }
+
+ buf = kmalloc(3, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ err = 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 >= 3)
+ rate = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+
+ ucontrol->value.integer.value[0] = rate;
+ kfree(buf);
+ 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 = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Sample Rate",
+ .info = tascam_samplerate_info,
+ .get = tascam_samplerate_get,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+};
+
+/**
+ * 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 source)
+ * 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)
+{
+ int err;
+
+ err = snd_ctl_add(tascam->card,
+ snd_ctl_new1(&tascam_line_out_control, tascam));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(tascam->card,
+ snd_ctl_new1(&tascam_digital_out_control, tascam));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(tascam->card,
+ snd_ctl_new1(&tascam_capture_12_control, tascam));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(tascam->card,
+ snd_ctl_new1(&tascam_capture_34_control, tascam));
+ if (err < 0)
+ return err;
+
+ err = 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_midi.c
new file mode 100644
index 000000000000..7ba28d0fcf19
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii_midi.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#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 from
+ * the kfifo, processes it by stripping protocol-specific padding bytes, and
+ * 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 =
+ container_of(work, struct tascam_card, midi_in_work);
+ u8 buf[MIDI_IN_BUF_SIZE];
+ unsigned int len;
+ int i;
+
+ if (!tascam->midi_in_substream)
+ return;
+
+ while (!kfifo_is_empty(&tascam->midi_in_fifo)) {
+ len = kfifo_out_spinlocked(&tascam->midi_in_fifo, buf,
+ sizeof(buf), &tascam->midi_in_lock);
+
+ if (len == 0)
+ continue;
+
+ if (!tascam->midi_in_substream)
+ continue;
+
+ for (i = 0; i < len; ++i) {
+ /* Skip padding bytes */
+ if (buf[i] == 0xfd)
+ continue;
+
+ /* The last byte is often a terminator (0x00, 0xFF). Ignore it. */
+ if (i == (len - 1) &&
+ (buf[i] == 0x00 || buf[i] == 0xff))
+ continue;
+
+ /* Submit valid MIDI bytes one by one */
+ snd_rawmidi_receive(tascam->midi_in_substream, &buf[i],
+ 1);
+ }
+ }
+}
+
+/**
+ * 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)
+{
+ struct tascam_card *tascam = urb->context;
+ int ret;
+
+ if (urb->status) {
+ if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
+ urb->status != -ESHUTDOWN && urb->status != -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 = 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 = substream->rmidi->private_data;
+
+ tascam->midi_in_substream = 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 = substream->rmidi->private_data;
+ int i, err;
+ unsigned long flags;
+
+ if (up) {
+ if (atomic_xchg(&tascam->midi_in_active, 1) == 0) {
+ spin_lock_irqsave(&tascam->midi_in_lock, flags);
+ kfifo_reset(&tascam->midi_in_fifo);
+ spin_unlock_irqrestore(&tascam->midi_in_lock, flags);
+
+ for (i = 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 = 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) == 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 = {
+ .open = tascam_midi_in_open,
+ .close = tascam_midi_in_close,
+ .trigger = tascam_midi_in_trigger,
+};
+
+/**
+ * tascam_midi_out_urb_complete() - Completion handler for MIDI OUT bulk URB.
+ * @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-blocking
+ * way to continue the data transmission chain.
+ */
+void tascam_midi_out_urb_complete(struct urb *urb)
+{
+ struct tascam_card *tascam = urb->context;
+ unsigned long flags;
+ int i, urb_index = -1;
+
+ if (urb->status) {
+ if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
+ urb->status != -ESHUTDOWN)
+ dev_err_ratelimited(tascam->card->dev,
+ "MIDI OUT URB failed: %d\n",
+ urb->status);
+ }
+
+ if (!tascam)
+ goto out;
+
+ for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
+ if (tascam->midi_out_urbs[i] == urb) {
+ urb_index = i;
+ break;
+ }
+ }
+
+ if (urb_index < 0) {
+ dev_err_ratelimited(tascam->card->dev,
+ "Unknown MIDI OUT URB completed!\n");
+ goto out;
+ }
+
+ spin_lock_irqsave(&tascam->midi_out_lock, flags);
+ clear_bit(urb_index, &tascam->midi_out_urbs_in_flight);
+ spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
+
+ 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 =
+ container_of(work, struct tascam_card, midi_out_work);
+ struct snd_rawmidi_substream *substream = tascam->midi_out_substream;
+ int i;
+
+ if (!substream || !atomic_read(&tascam->midi_out_active))
+ return;
+
+ while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) == 1) {
+ unsigned long flags;
+ int urb_index;
+ struct urb *urb;
+ u8 *buf;
+ int bytes_to_send;
+
+ spin_lock_irqsave(&tascam->midi_out_lock, flags);
+
+ urb_index = -1;
+ for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
+ if (!test_bit(i, &tascam->midi_out_urbs_in_flight)) {
+ urb_index = i;
+ break;
+ }
+ }
+
+ if (urb_index < 0) {
+ spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
+ return; /* No free URBs, will be rescheduled by completion handler */
+ }
+
+ urb = tascam->midi_out_urbs[urb_index];
+ buf = urb->transfer_buffer;
+ bytes_to_send = snd_rawmidi_transmit(substream, buf, 8);
+
+ if (bytes_to_send <= 0) {
+ spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
+ break; /* No more data */
+ }
+
+ if (bytes_to_send < 9)
+ memset(buf + bytes_to_send, 0xfd, 9 - bytes_to_send);
+ buf[8] = 0x00;
+
+ set_bit(urb_index, &tascam->midi_out_urbs_in_flight);
+ urb->transfer_buffer_length = 9;
+ spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
+
+ 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);
+ spin_lock_irqsave(&tascam->midi_out_lock, flags);
+ clear_bit(urb_index, &tascam->midi_out_urbs_in_flight);
+ spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
+ 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 = substream->rmidi->private_data;
+
+ tascam->midi_out_substream = substream;
+ /* Initialize the running status state for the packet packer. */
+ tascam->midi_running_status = 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 = substream->rmidi->private_data;
+
+ 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 *substream,
+ int up)
+{
+ struct tascam_card *tascam = 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 = {
+ .open = tascam_midi_out_open,
+ .close = tascam_midi_out_close,
+ .trigger = tascam_midi_out_trigger,
+ .drain = tascam_midi_out_drain,
+};
+
+/**
+ * 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)
+{
+ int err;
+
+ err = 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 = 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 |= 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_pcm.c
new file mode 100644
index 000000000000..d75f29807b1b
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii_pcm.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#include "us144mkii.h"
+
+/**
+ * @brief Rate-to-Packet Fixing Data
+ *
+ * These static arrays define the number of audio frames per USB isochronous
+ * packet for various sample rates. This data is crucial for maintaining
+ * audio synchronization and preventing xruns, as the device's feedback
+ * mechanism indicates how many samples it has consumed.
+ *
+ * The patterns are indexed by a feedback value received from the device,
+ * which helps the driver adjust the packet size dynamically to match the
+ * device's consumption rate.
+ */
+static const unsigned int patterns_48khz[5][8] = { { 5, 6, 6, 6, 6, 6, 6, 6 },
+ { 6, 6, 6, 6, 6, 6, 6, 6 },
+ { 6, 6, 6, 6, 6, 6, 6, 6 },
+ { 6, 6, 6, 7, 6, 6, 6, 6 },
+ { 7, 6, 6, 7, 6, 6, 7, 6 } };
+static const unsigned int patterns_96khz[5][8] = {
+ { 11, 12, 12, 12, 12, 12, 12, 12 },
+ { 12, 12, 12, 12, 12, 12, 12, 12 },
+ { 12, 12, 12, 12, 12, 12, 12, 12 },
+ { 12, 12, 13, 12, 12, 12, 12, 12 },
+ { 13, 12, 12, 13, 12, 12, 13, 12 }
+};
+static const unsigned int patterns_88khz[5][8] = {
+ { 10, 11, 11, 11, 11, 11, 11, 11 },
+ { 11, 11, 11, 11, 11, 11, 11, 11 },
+ { 11, 11, 11, 11, 11, 11, 11, 11 },
+ { 11, 11, 12, 11, 11, 11, 11, 11 },
+ { 12, 11, 11, 12, 11, 11, 12, 11 }
+};
+static const unsigned int patterns_44khz[5][8] = { { 5, 5, 5, 5, 5, 5, 5, 6 },
+ { 5, 5, 5, 6, 5, 5, 5, 6 },
+ { 5, 5, 6, 5, 6, 5, 5, 6 },
+ { 5, 6, 5, 6, 5, 6, 5, 6 },
+ { 6, 6, 6, 6, 6, 6, 6, 5 } };
+
+const struct snd_pcm_hardware tascam_pcm_hw = {
+ .info = (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 = SNDRV_PCM_FMTBIT_S24_3LE,
+ .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .channels_min = NUM_CHANNELS,
+ .channels_max = NUM_CHANNELS,
+ .buffer_bytes_max = 1024 * 1024,
+ .period_bytes_min = 48 * BYTES_PER_FRAME,
+ .period_bytes_max = 1024 * BYTES_PER_FRAME,
+ .periods_min = 2,
+ .periods_max = 1024,
+};
+
+/**
+ * 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)
+{
+ size_t f;
+ const u8 *src_12, *src_34;
+ u8 *dst_line, *dst_digital;
+
+ for (f = 0; f < frames; ++f) {
+ src_12 = src_buffer + f * BYTES_PER_FRAME;
+ src_34 = src_12 + (2 * BYTES_PER_SAMPLE);
+ dst_line = dst_buffer + f * BYTES_PER_FRAME;
+ dst_digital = dst_line + (2 * BYTES_PER_SAMPLE);
+
+ /* LINE OUTPUTS (ch1/2 on device) */
+ if (tascam->line_out_source == 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 == 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);
+ }
+}
+
+/**
+ * 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)
+{
+ int f;
+ const s32 *src_frame;
+ s32 *dst_frame;
+
+ for (f = 0; f < FRAMES_PER_DECODE_BLOCK; f++) {
+ src_frame = decoded_block + (f * DECODED_CHANNELS_PER_FRAME);
+ dst_frame = routed_block + (f * DECODED_CHANNELS_PER_FRAME);
+
+ /* ch1 and ch2 Source */
+ if (tascam->capture_12_source == 0) { /* analog inputs */
+ dst_frame[0] = src_frame[0]; /* Analog L */
+ dst_frame[1] = src_frame[1]; /* Analog R */
+ } else { /* digital inputs */
+ dst_frame[0] = src_frame[2]; /* Digital L */
+ dst_frame[1] = src_frame[3]; /* Digital R */
+ }
+
+ /* ch3 and ch4 Source */
+ if (tascam->capture_34_source == 0) { /* analog inputs */
+ dst_frame[2] = src_frame[0]; /* Analog L (Duplicate) */
+ dst_frame[3] = src_frame[1]; /* Analog R (Duplicate) */
+ } else { /* digital inputs */
+ dst_frame[2] = src_frame[2]; /* Digital L */
+ dst_frame[3] = src_frame[3]; /* Digital R */
+ }
+ }
+}
+
+/**
+ * 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 messages
+ * 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 rate)
+{
+ struct usb_device *dev = tascam->dev;
+ u8 *rate_payload_buf;
+ u16 rate_vendor_wValue;
+ int err = 0;
+ const u8 *current_payload_src;
+
+ static const u8 payload_44100[] = { 0x44, 0xac, 0x00 };
+ static const u8 payload_48000[] = { 0x80, 0xbb, 0x00 };
+ static const u8 payload_88200[] = { 0x88, 0x58, 0x01 };
+ static const u8 payload_96000[] = { 0x00, 0x77, 0x01 };
+
+ switch (rate) {
+ case 44100:
+ current_payload_src = payload_44100;
+ rate_vendor_wValue = REG_ADDR_RATE_44100;
+ break;
+ case 48000:
+ current_payload_src = payload_48000;
+ rate_vendor_wValue = REG_ADDR_RATE_48000;
+ break;
+ case 88200:
+ current_payload_src = payload_88200;
+ rate_vendor_wValue = REG_ADDR_RATE_88200;
+ break;
+ case 96000:
+ current_payload_src = payload_96000;
+ rate_vendor_wValue = REG_ADDR_RATE_96000;
+ break;
+ default:
+ dev_err(&dev->dev,
+ "Unsupported sample rate %d for configuration\n", rate);
+ return -EINVAL;
+ }
+
+ rate_payload_buf = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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;
+}
+
+/**
+ * tascam_pcm_hw_params() - Configures hardware parameters for PCM streams.
+ * @substream: The ALSA PCM substream.
+ * @params: The hardware parameters to apply.
+ *
+ * This function allocates pages for the PCM buffer and, for playback streams,
+ * 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, or a negative error code on failure.
+ */
+int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct tascam_card *tascam = snd_pcm_substream_chip(substream);
+ int err;
+ unsigned int rate = params_rate(params);
+
+ err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ if (err < 0)
+ return err;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ switch (rate) {
+ case 44100:
+ tascam->feedback_patterns = patterns_44khz;
+ tascam->feedback_base_value = 43;
+ tascam->feedback_max_value = 45;
+ break;
+ case 48000:
+ tascam->feedback_patterns = patterns_48khz;
+ tascam->feedback_base_value = 47;
+ tascam->feedback_max_value = 49;
+ break;
+ case 88200:
+ tascam->feedback_patterns = patterns_88khz;
+ tascam->feedback_base_value = 87;
+ tascam->feedback_max_value = 89;
+ break;
+ case 96000:
+ tascam->feedback_patterns = patterns_96khz;
+ tascam->feedback_base_value = 95;
+ tascam->feedback_max_value = 97;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (tascam->current_rate != rate) {
+ err = us144mkii_configure_device_for_rate(tascam, rate);
+ if (err < 0) {
+ tascam->current_rate = 0;
+ return err;
+ }
+ tascam->current_rate = rate;
+ }
+
+ return 0;
+}
+
+/**
+ * tascam_pcm_hw_free() - Frees hardware parameters for PCM streams.
+ * @substream: The ALSA PCM substream.
+ *
+ * This function frees the pages allocated for the PCM buffer.
+ *
+ * Return: 0 on success.
+ */
+int tascam_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(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, SNDRV_PCM_TRIGGER_STOP).
+ *
+ * This function handles starting and stopping of playback and capture streams
+ * by submitting or killing the associated URBs. It ensures that both streams
+ * are started/stopped together.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct tascam_card *tascam = snd_pcm_substream_chip(substream);
+ unsigned long flags;
+ int err = 0;
+ int i;
+ bool do_start = false;
+ bool do_stop = false;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+ 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 = 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 = true;
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ if (do_start) {
+ if (atomic_read(&tascam->active_urbs) > 0) {
+ dev_WARN(tascam->card->dev,
+ "Cannot start, URBs still active.\n");
+ return -EAGAIN;
+ }
+
+ for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
+ usb_get_urb(tascam->feedback_urbs[i]);
+ usb_anchor_urb(tascam->feedback_urbs[i],
+ &tascam->feedback_anchor);
+ err = 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]);
+ goto start_rollback;
+ }
+ atomic_inc(&tascam->active_urbs);
+ }
+ for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
+ usb_get_urb(tascam->playback_urbs[i]);
+ usb_anchor_urb(tascam->playback_urbs[i],
+ &tascam->playback_anchor);
+ err = 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]);
+ goto start_rollback;
+ }
+ atomic_inc(&tascam->active_urbs);
+ }
+ for (i = 0; i < NUM_CAPTURE_URBS; i++) {
+ usb_get_urb(tascam->capture_urbs[i]);
+ usb_anchor_urb(tascam->capture_urbs[i],
+ &tascam->capture_anchor);
+ err = 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]);
+ 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 = true;
+ }
+
+ if (do_stop)
+ schedule_work(&tascam->stop_work);
+
+ return err;
+}
+
+/**
+ * tascam_init_pcm() - Initializes the ALSA PCM device.
+ * @pcm: Pointer to the ALSA PCM device to initialize.
+ *
+ * This function sets up the PCM operations for playback and capture,
+ * preallocates pages for the PCM buffer, and initializes the workqueue
+ * for deferred capture processing.
+ *
+ * Return: 0 on success.
+ */
+int tascam_init_pcm(struct snd_pcm *pcm)
+{
+ struct tascam_card *tascam = 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ tascam->dev->dev.parent,
+ 64 * 1024,
+ tascam_pcm_hw.buffer_bytes_max);
+
+ INIT_WORK(&tascam->capture_work, tascam_capture_work_handler);
+
+ return 0;
+}
diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h
new file mode 100644
index 000000000000..d2631c6cc53c
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii_pcm.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#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 sizes
+ * 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 operations,
+ * including open, close, ioctl, hardware parameters, hardware free, prepare,
+ * trigger, and pointer.
+ */
+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 operations,
+ * including open, close, ioctl, hardware parameters, hardware free, prepare,
+ * trigger, and pointer.
+ */
+extern const struct snd_pcm_ops tascam_capture_ops;
+
+/**
+ * playback_urb_complete() - Completion handler for playback isochronous URBs.
+ * @urb: the completed URB
+ *
+ * This function runs in interrupt context. It calculates the number of bytes
+ * to send in the next set of packets based on the feedback-driven clock,
+ * copies the audio data from the ALSA ring buffer (applying routing), and
+ * resubmits the URB.
+ */
+void playback_urb_complete(struct urb *urb);
+
+/**
+ * feedback_urb_complete() - Completion handler for feedback isochronous URBs.
+ * @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);
+
+/**
+ * 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 process
+ * it. It then resubmits the URB to receive more data.
+ */
+void capture_urb_complete(struct urb *urb);
+
+/**
+ * tascam_init_pcm() - Initializes the ALSA PCM device.
+ * @pcm: Pointer to the ALSA PCM device to initialize.
+ *
+ * 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.
+ */
+int tascam_init_pcm(struct snd_pcm *pcm);
+
+/**
+ * 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 messages
+ * 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 rate);
+
+/**
+ * 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);
+
+/**
+ * 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.
+ * @params: The hardware parameters to apply.
+ *
+ * This function allocates pages for the PCM buffer and, for playback streams,
+ * 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, or a negative error code on failure.
+ */
+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 frees the pages allocated for the PCM buffer.
+ *
+ * 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, SNDRV_PCM_TRIGGER_STOP).
+ *
+ * This function handles starting and stopping of playback and capture streams
+ * by submitting or killing the associated URBs. It ensures that both streams
+ * are started/stopped together.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
+
+/**
+ * tascam_capture_work_handler() - Deferred work for processing capture data.
+ * @work: the work_struct instance
+ *
+ * This function runs in a kernel thread context, not an IRQ context. It reads
+ * raw data from the capture ring buffer, decodes it, applies routing, and
+ * copies the final audio data into the ALSA capture ring buffer. This offloads
+ * * the CPU-intensive decoding from the time-sensitive URB completion handlers.
+ */
+void tascam_capture_work_handler(struct work_struct *work);
+
+#endif /* __US144MKII_PCM_H */
diff --git a/sound/usb/usx2y/us144mkii_playback.c b/sound/usb/usx2y/us144mkii_playback.c
new file mode 100644
index 000000000000..f137a0f0e9a5
--- /dev/null
+++ b/sound/usb/usx2y/us144mkii_playback.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025 Šerif Rami <ramiserifpersia@...il.com>
+
+#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 = snd_pcm_substream_chip(substream);
+
+ substream->runtime->hw = tascam_pcm_hw;
+ tascam->playback_substream = 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 = snd_pcm_substream_chip(substream);
+
+ tascam->playback_substream = 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 and flags, and configures
+ * the playback URBs with appropriate packet sizes based on the nominal frame
+ * rate and feedback accumulator.
+ *
+ * Return: 0 on success.
+ */
+static int tascam_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct tascam_card *tascam = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int i, u;
+ size_t nominal_frames_per_packet, nominal_bytes_per_packet;
+ size_t total_bytes_in_urb;
+ unsigned int feedback_packets;
+
+ tascam->driver_playback_pos = 0;
+ tascam->playback_frames_consumed = 0;
+ tascam->last_period_pos = 0;
+ tascam->feedback_pattern_in_idx = 0;
+ tascam->feedback_pattern_out_idx = 0;
+ tascam->feedback_synced = false;
+ tascam->feedback_consecutive_errors = 0;
+ tascam->feedback_urb_skip_count = NUM_FEEDBACK_URBS;
+
+ nominal_frames_per_packet = runtime->rate / 8000;
+ for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++)
+ tascam->feedback_accumulator_pattern[i] =
+ nominal_frames_per_packet;
+
+ feedback_packets = 1;
+
+ for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
+ struct urb *f_urb = tascam->feedback_urbs[i];
+ int j;
+
+ f_urb->number_of_packets = feedback_packets;
+ f_urb->transfer_buffer_length =
+ feedback_packets * FEEDBACK_PACKET_SIZE;
+ for (j = 0; j < feedback_packets; j++) {
+ f_urb->iso_frame_desc[j].offset =
+ j * FEEDBACK_PACKET_SIZE;
+ f_urb->iso_frame_desc[j].length = FEEDBACK_PACKET_SIZE;
+ }
+ }
+
+ nominal_bytes_per_packet = nominal_frames_per_packet * BYTES_PER_FRAME;
+ total_bytes_in_urb = nominal_bytes_per_packet * PLAYBACK_URB_PACKETS;
+
+ for (u = 0; u < NUM_PLAYBACK_URBS; u++) {
+ struct urb *urb = tascam->playback_urbs[u];
+
+ memset(urb->transfer_buffer, 0,
+ tascam->playback_urb_alloc_size);
+ urb->transfer_buffer_length = total_bytes_in_urb;
+ urb->number_of_packets = PLAYBACK_URB_PACKETS;
+ for (i = 0; i < PLAYBACK_URB_PACKETS; i++) {
+ urb->iso_frame_desc[i].offset =
+ i * nominal_bytes_per_packet;
+ urb->iso_frame_desc[i].length =
+ nominal_bytes_per_packet;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * tascam_playback_pointer() - Returns the current playback pointer position.
+ * @substream: The ALSA PCM substream.
+ *
+ * This function returns the current position of the playback pointer within
+ * 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 = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u64 pos;
+ unsigned long flags;
+
+ if (!atomic_read(&tascam->playback_active))
+ return 0;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+ pos = tascam->playback_frames_consumed;
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ return runtime ? pos % runtime->buffer_size : 0;
+}
+
+/**
+ * tascam_playback_ops - ALSA PCM operations for playback.
+ *
+ * This structure defines the callback functions for playback stream operations,
+ * including open, close, ioctl, hardware parameters, hardware free, prepare,
+ * trigger, and pointer.
+ */
+const struct snd_pcm_ops tascam_playback_ops = {
+ .open = tascam_playback_open,
+ .close = tascam_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = tascam_pcm_hw_params,
+ .hw_free = tascam_pcm_hw_free,
+ .prepare = tascam_playback_prepare,
+ .trigger = tascam_pcm_trigger,
+ .pointer = tascam_playback_pointer,
+};
+
+/**
+ * playback_urb_complete() - Completion handler for playback isochronous URBs.
+ * @urb: the completed URB
+ *
+ * This function runs in interrupt context. It calculates the number of bytes
+ * to send in the next set of packets based on the feedback-driven clock,
+ * copies the audio data from the ALSA ring buffer (applying routing), and
+ * resubmits the URB.
+ */
+void playback_urb_complete(struct urb *urb)
+{
+ struct tascam_card *tascam = urb->context;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_runtime *runtime;
+ unsigned long flags;
+ u8 *src_buf, *dst_buf;
+ size_t total_bytes_for_urb = 0;
+ snd_pcm_uframes_t offset_frames;
+ snd_pcm_uframes_t frames_to_copy;
+ int ret, i;
+
+ if (urb->status) {
+ if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
+ urb->status != -ESHUTDOWN && urb->status != -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 = tascam->playback_substream;
+ if (!substream || !substream->runtime)
+ goto out;
+ runtime = substream->runtime;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+
+ for (i = 0; i < urb->number_of_packets; i++) {
+ unsigned int frames_for_packet;
+ size_t bytes_for_packet;
+
+ if (tascam->feedback_synced) {
+ frames_for_packet =
+ tascam->feedback_accumulator_pattern
+ [tascam->feedback_pattern_out_idx];
+ tascam->feedback_pattern_out_idx =
+ (tascam->feedback_pattern_out_idx + 1) %
+ FEEDBACK_ACCUMULATOR_SIZE;
+ } else {
+ frames_for_packet = runtime->rate / 8000;
+ }
+ bytes_for_packet = frames_for_packet * BYTES_PER_FRAME;
+
+ urb->iso_frame_desc[i].offset = total_bytes_for_urb;
+ urb->iso_frame_desc[i].length = bytes_for_packet;
+ total_bytes_for_urb += bytes_for_packet;
+ }
+ urb->transfer_buffer_length = total_bytes_for_urb;
+
+ offset_frames = tascam->driver_playback_pos;
+ frames_to_copy = bytes_to_frames(runtime, total_bytes_for_urb);
+ tascam->driver_playback_pos =
+ (offset_frames + frames_to_copy) % runtime->buffer_size;
+
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ if (total_bytes_for_urb > 0) {
+ src_buf = runtime->dma_area +
+ frames_to_bytes(runtime, offset_frames);
+ dst_buf = tascam->playback_routing_buffer;
+
+ /* Handle ring buffer wrap-around */
+ if (offset_frames + frames_to_copy > runtime->buffer_size) {
+ size_t first_chunk_bytes = frames_to_bytes(
+ runtime, runtime->buffer_size - offset_frames);
+ size_t second_chunk_bytes =
+ total_bytes_for_urb - first_chunk_bytes;
+
+ memcpy(dst_buf, src_buf, first_chunk_bytes);
+ memcpy(dst_buf + first_chunk_bytes, runtime->dma_area,
+ second_chunk_bytes);
+ } else {
+ memcpy(dst_buf, src_buf, total_bytes_for_urb);
+ }
+
+ /* Apply routing to the contiguous data in our routing buffer */
+ process_playback_routing_us144mkii(tascam, dst_buf, dst_buf,
+ frames_to_copy);
+ memcpy(urb->transfer_buffer, dst_buf, total_bytes_for_urb);
+ }
+
+ urb->dev = tascam->dev;
+ usb_get_urb(urb);
+ usb_anchor_urb(urb, &tascam->playback_anchor);
+ ret = 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);
+ }
+out:
+ usb_put_urb(urb);
+}
+
+/**
+ * feedback_urb_complete() - Completion handler for feedback isochronous URBs.
+ * @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)
+{
+ struct tascam_card *tascam = urb->context;
+ struct snd_pcm_substream *playback_ss, *capture_ss;
+ struct snd_pcm_runtime *playback_rt, *capture_rt;
+ unsigned long flags;
+ u64 total_frames_in_urb = 0;
+ int ret, p;
+ unsigned int old_in_idx, new_in_idx;
+ bool playback_period_elapsed = false;
+ bool capture_period_elapsed = false;
+
+ if (urb->status) {
+ if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
+ urb->status != -ESHUTDOWN && urb->status != -ENODEV)
+ dev_err_ratelimited(tascam->card->dev,
+ "Feedback URB failed: %d\n",
+ urb->status);
+ goto out;
+ }
+ if (!tascam || !atomic_read(&tascam->playback_active))
+ goto out;
+
+ playback_ss = tascam->playback_substream;
+ if (!playback_ss || !playback_ss->runtime)
+ goto out;
+ playback_rt = playback_ss->runtime;
+
+ capture_ss = tascam->capture_substream;
+ capture_rt = capture_ss ? capture_ss->runtime : NULL;
+
+ spin_lock_irqsave(&tascam->lock, flags);
+
+ if (tascam->feedback_urb_skip_count > 0) {
+ tascam->feedback_urb_skip_count--;
+ goto unlock_and_continue;
+ }
+
+ old_in_idx = tascam->feedback_pattern_in_idx;
+
+ for (p = 0; p < urb->number_of_packets; p++) {
+ u8 feedback_value = 0;
+ const unsigned int *pattern;
+ bool packet_ok = (urb->iso_frame_desc[p].status == 0 &&
+ urb->iso_frame_desc[p].actual_length >= 1);
+
+ if (packet_ok)
+ feedback_value = *((u8 *)urb->transfer_buffer +
+ urb->iso_frame_desc[p].offset);
+
+ if (packet_ok &&
+ feedback_value >= tascam->feedback_base_value &&
+ feedback_value <= tascam->feedback_max_value) {
+ pattern = tascam->feedback_patterns
+ [feedback_value -
+ tascam->feedback_base_value];
+ tascam->feedback_consecutive_errors = 0;
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ unsigned int in_idx =
+ (tascam->feedback_pattern_in_idx + i) %
+ FEEDBACK_ACCUMULATOR_SIZE;
+
+ tascam->feedback_accumulator_pattern[in_idx] =
+ pattern[i];
+ total_frames_in_urb += pattern[i];
+ }
+ } else {
+ unsigned int nominal_frames = 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");
+ if (playback_ss)
+ snd_pcm_stop(
+ playback_ss,
+ SNDRV_PCM_STATE_XRUN);
+ if (capture_ss)
+ snd_pcm_stop(
+ capture_ss,
+ SNDRV_PCM_STATE_XRUN);
+ tascam->feedback_synced = false;
+ goto unlock_and_continue;
+ }
+ }
+ for (i = 0; i < 8; i++) {
+ unsigned int in_idx =
+ (tascam->feedback_pattern_in_idx + i) %
+ FEEDBACK_ACCUMULATOR_SIZE;
+
+ tascam->feedback_accumulator_pattern[in_idx] =
+ nominal_frames;
+ total_frames_in_urb += nominal_frames;
+ }
+ }
+ tascam->feedback_pattern_in_idx =
+ (tascam->feedback_pattern_in_idx + 8) %
+ FEEDBACK_ACCUMULATOR_SIZE;
+ }
+
+ new_in_idx = tascam->feedback_pattern_in_idx;
+
+ if (!tascam->feedback_synced) {
+ unsigned int out_idx = tascam->feedback_pattern_out_idx;
+ bool is_ahead =
+ (new_in_idx - out_idx) % FEEDBACK_ACCUMULATOR_SIZE <
+ (FEEDBACK_ACCUMULATOR_SIZE / 2);
+ bool was_behind =
+ (old_in_idx - out_idx) % FEEDBACK_ACCUMULATOR_SIZE >=
+ (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 = true;
+ tascam->feedback_consecutive_errors = 0;
+ }
+ }
+
+ if (total_frames_in_urb > 0) {
+ tascam->playback_frames_consumed += total_frames_in_urb;
+ if (atomic_read(&tascam->capture_active))
+ tascam->capture_frames_processed += total_frames_in_urb;
+ }
+
+ if (playback_rt->period_size > 0) {
+ u64 current_period = div_u64(tascam->playback_frames_consumed,
+ playback_rt->period_size);
+
+ if (current_period > tascam->last_period_pos) {
+ tascam->last_period_pos = current_period;
+ playback_period_elapsed = true;
+ }
+ }
+
+ if (atomic_read(&tascam->capture_active) && capture_rt &&
+ capture_rt->period_size > 0) {
+ u64 current_capture_period =
+ div_u64(tascam->capture_frames_processed,
+ capture_rt->period_size);
+
+ if (current_capture_period > tascam->last_capture_period_pos) {
+ tascam->last_capture_period_pos =
+ current_capture_period;
+ capture_period_elapsed = true;
+ }
+ }
+
+unlock_and_continue:
+ spin_unlock_irqrestore(&tascam->lock, flags);
+
+ if (playback_period_elapsed)
+ snd_pcm_period_elapsed(playback_ss);
+ if (capture_period_elapsed)
+ snd_pcm_period_elapsed(capture_ss);
+
+ urb->dev = tascam->dev;
+ usb_get_urb(urb);
+ usb_anchor_urb(urb, &tascam->feedback_anchor);
+ ret = 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);
+}
--
2.39.5
Powered by blists - more mailing lists