diff --git a/us144mkii.c b/us144mkii.c index d3b70cf..98618cd 100644 --- a/us144mkii.c +++ b/us144mkii.c @@ -4,31 +4,19 @@ * ALSA Driver for TASCAM US-144MKII Audio Interface */ -#include -#include #include -#include -#include -#include - +#include #include #include -#include #include #include MODULE_AUTHOR("serifpersia "); MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); #define DRIVER_NAME "us144mkii" -#define DRIVER_VERSION "1.0" - -/* - * TODO: - * - Implement audio input capture. - * - Implement MIDI IN/OUT. - */ +#define DRIVER_VERSION "1.5" /* --- Module Parameters --- */ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; @@ -60,6 +48,7 @@ static int dev_idx; #define MODE_VAL_HANDSHAKE_READ 0x0000 #define MODE_VAL_CONFIG 0x0010 #define MODE_VAL_STREAM_START 0x0030 +#define HANDSHAKE_SUCCESS_VAL 0x12 #define REG_ADDR_UNKNOWN_0D 0x0d04 #define REG_ADDR_UNKNOWN_0E 0x0e00 #define REG_ADDR_UNKNOWN_0F 0x0f00 @@ -76,6 +65,9 @@ static int dev_idx; #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 USB_CTRL_TIMEOUT_MS 1000 /* --- Audio Format Configuration --- */ @@ -84,6 +76,12 @@ static int dev_idx; #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 /* 32-bit */ +#define FRAMES_PER_DECODE_BLOCK 8 +#define RAW_BYTES_PER_DECODE_BLOCK 512 + /* --- Main Driver Data Structure --- */ struct tascam_card { struct usb_device *dev; @@ -92,18 +90,35 @@ struct tascam_card { struct snd_card *card; struct snd_pcm *pcm; + /* Playback stream */ struct snd_pcm_substream *playback_substream; struct urb *playback_urbs[NUM_PLAYBACK_URBS]; size_t playback_urb_alloc_size; - - struct snd_pcm_substream *capture_substream; - atomic_t capture_active; - struct urb *feedback_urbs[NUM_FEEDBACK_URBS]; size_t feedback_urb_alloc_size; - - spinlock_t lock; atomic_t playback_active; + u64 playback_frames_consumed; + snd_pcm_uframes_t driver_playback_pos; + u64 last_period_pos; + + /* 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; + volatile size_t capture_ring_buffer_write_ptr; + u8 *capture_decode_raw_block; + s32 *capture_decode_dst_block; + struct work_struct capture_work; // For deferred processing + + /* Shared state & Routing Matrix */ + spinlock_t lock; + atomic_t active_urbs; int current_rate; unsigned int latency_profile; unsigned int line_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */ @@ -118,13 +133,6 @@ struct tascam_card { unsigned int feedback_consecutive_errors; unsigned int feedback_urb_skip_count; - u64 playback_frames_consumed; - snd_pcm_uframes_t driver_playback_pos; - u64 last_period_pos; - - u64 capture_frames_produced; - u64 last_capture_period_pos; - const unsigned int (*feedback_patterns)[8]; unsigned int feedback_base_value; unsigned int feedback_max_value; @@ -135,6 +143,8 @@ static struct usb_driver tascam_alsa_driver; /* --- Forward Declarations --- */ static void playback_urb_complete(struct urb *urb); static void feedback_urb_complete(struct urb *urb); +static void capture_urb_complete(struct urb *urb); +static void tascam_capture_work_handler(struct work_struct *work); static int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate); static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *id); static void tascam_disconnect(struct usb_interface *intf); @@ -142,14 +152,6 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message); static int tascam_resume(struct usb_interface *intf); /* --- Sysfs Attribute for Driver Version --- */ -/** - * driver_version_show - Sysfs callback to show the driver version. - * @dev: The device structure. - * @attr: The device attribute structure. - * @buf: The buffer to write the version string into. - * - * Returns: The number of bytes written. - */ static ssize_t driver_version_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -157,7 +159,6 @@ static ssize_t driver_version_show(struct device *dev, } static DEVICE_ATTR_RO(driver_version); -/* --- ALSA Control Definitions --- */ /* --- ALSA Control Definitions --- */ static const char * const latency_profile_texts[] = {"Low", "Normal", "High"}; static const char * const playback_source_texts[] = {"Playback 1-2", "Playback 3-4"}; @@ -165,13 +166,13 @@ static const char * const capture_source_texts[] = {"Analog In", "Digital In"}; static int tascam_latency_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 = 3; -if (uinfo->value.enumerated.item >= 3) -uinfo->value.enumerated.item = 2; -strcpy(uinfo->value.enumerated.name, latency_profile_texts[uinfo->value.enumerated.item]); -return 0; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 3; + if (uinfo->value.enumerated.item >= 3) + uinfo->value.enumerated.item = 2; + strcpy(uinfo->value.enumerated.name, latency_profile_texts[uinfo->value.enumerated.item]); + return 0; } static int tascam_latency_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -185,8 +186,10 @@ static int tascam_latency_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem { struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); unsigned int new_profile = ucontrol->value.enumerated.item[0]; + if (new_profile >= 3) return -EINVAL; + if (tascam->latency_profile != new_profile) { tascam->latency_profile = new_profile; return 1; @@ -195,93 +198,124 @@ static int tascam_latency_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem } static const struct snd_kcontrol_new tascam_latency_control = { -.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Latency Profile", -.info = tascam_latency_info, .get = tascam_latency_get, .put = tascam_latency_put, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Latency Profile", + .info = tascam_latency_info, .get = tascam_latency_get, .put = tascam_latency_put, }; 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; -strcpy(uinfo->value.enumerated.name, playback_source_texts[uinfo->value.enumerated.item]); -return 0; + 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; + strcpy(uinfo->value.enumerated.name, playback_source_texts[uinfo->value.enumerated.item]); + return 0; } -static int tascam_line_out_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->line_out_source; return 0; } +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; +} -static int tascam_line_out_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->line_out_source == u->value.enumerated.item[0]) return 0; - t->line_out_source = u->value.enumerated.item[0]; return 1; } +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; +} static const struct snd_kcontrol_new tascam_line_out_control = { -.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line Out Source", -.info = tascam_playback_source_info, .get = tascam_line_out_get, .put = tascam_line_out_put, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line Out Source", + .info = tascam_playback_source_info, .get = tascam_line_out_get, .put = tascam_line_out_put, }; -static int tascam_digital_out_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->digital_out_source; return 0; } +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; +} -static int tascam_digital_out_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->digital_out_source == u->value.enumerated.item[0]) return 0; - t->digital_out_source = u->value.enumerated.item[0]; return 1; } +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; +} static const struct snd_kcontrol_new tascam_digital_out_control = { -.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Out Source", -.info = tascam_playback_source_info, .get = tascam_digital_out_get, .put = tascam_digital_out_put, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Out Source", + .info = tascam_playback_source_info, .get = tascam_digital_out_get, .put = tascam_digital_out_put, }; 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; -strcpy(uinfo->value.enumerated.name, capture_source_texts[uinfo->value.enumerated.item]); -return 0; + 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; + strcpy(uinfo->value.enumerated.name, capture_source_texts[uinfo->value.enumerated.item]); + return 0; } -static int tascam_capture_12_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_12_source; return 0; } +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; +} -static int tascam_capture_12_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->capture_12_source == u->value.enumerated.item[0]) return 0; - t->capture_12_source = u->value.enumerated.item[0]; return 1; } +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; +} static const struct snd_kcontrol_new tascam_capture_12_control = { -.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 1-2 Source", -.info = tascam_capture_source_info, .get = tascam_capture_12_get, .put = tascam_capture_12_put, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 1-2 Source", + .info = tascam_capture_source_info, .get = tascam_capture_12_get, .put = tascam_capture_12_put, }; -static int tascam_capture_34_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_34_source; return 0; } +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; +} -static int tascam_capture_34_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->capture_34_source == u->value.enumerated.item[0]) return 0; - t->capture_34_source = u->value.enumerated.item[0]; return 1; } +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; +} static const struct snd_kcontrol_new tascam_capture_34_control = { -.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 3-4 Source", -.info = tascam_capture_source_info, .get = tascam_capture_34_get, .put = tascam_capture_34_put, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 3-4 Source", + .info = tascam_capture_source_info, .get = tascam_capture_34_get, .put = tascam_capture_34_put, }; -/** - * tascam_samplerate_info - ALSA control info callback for the sample rate. - * @kcontrol: The kcontrol instance. - * @uinfo: The user control element info structure to fill. - * - * Provides information about the read-only sample rate control. - * - * Returns: 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; @@ -291,17 +325,6 @@ static int tascam_samplerate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_ return 0; } -/** - * tascam_samplerate_get - ALSA control get callback for the sample rate. - * @kcontrol: The kcontrol instance. - * @ucontrol: The user control element value structure to fill. - * - * Reports the current sample rate of the device. It first checks the driver's - * internal state. If no stream is active, it queries the device directly via - * a USB control message. - * - * Returns: 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); @@ -378,8 +401,11 @@ static const struct snd_pcm_hardware tascam_pcm_hw = { }; /** - * tascam_free_urbs - Free all allocated URBs and their buffers. - * @tascam: The card instance. + * tascam_free_urbs - Free all allocated URBs and associated buffers. + * @tascam: the tascam_card instance. + * + * This function kills, unlinks, and frees all playback, feedback, and capture + * URBs, along with their transfer buffers and the capture ring/decode buffers. */ static void tascam_free_urbs(struct tascam_card *tascam) { @@ -406,12 +432,34 @@ static void tascam_free_urbs(struct tascam_card *tascam) tascam->feedback_urbs[i] = NULL; } } + + for (i = 0; i < NUM_CAPTURE_URBS; i++) { + if (tascam->capture_urbs[i]) { + usb_kill_urb(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; + } + } + + 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 URBs and their buffers. - * @tascam: The card instance. - * Returns: 0 on success, or a negative error code on failure. + * tascam_alloc_urbs - Allocate all URBs and associated buffers. + * @tascam: the tascam_card instance. + * + * This function allocates and initializes all URBs for playback, feedback, + * and capture, as well as the necessary buffers for capture data processing. + * + * Return: 0 on success, or a negative error code on failure. */ static int tascam_alloc_urbs(struct tascam_card *tascam) { @@ -461,6 +509,39 @@ static int tascam_alloc_urbs(struct tascam_card *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; + } + + 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; + return 0; error: @@ -469,30 +550,68 @@ error: return -ENOMEM; } -static int tascam_pcm_open(struct snd_pcm_substream *substream) +static int tascam_playback_open(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - int err; + int err = 0; - runtime->hw = tascam_pcm_hw; + substream->runtime->hw = tascam_pcm_hw; tascam->playback_substream = substream; atomic_set(&tascam->playback_active, 0); - err = tascam_alloc_urbs(tascam); - if (err < 0) - return err; - + if (!tascam->capture_substream) { + err = tascam_alloc_urbs(tascam); + if (err < 0) + return err; + } return 0; } -static int tascam_pcm_close(struct snd_pcm_substream *substream) +static int tascam_capture_open(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - tascam_free_urbs(tascam); + int err = 0; + + substream->runtime->hw = tascam_pcm_hw; + tascam->capture_substream = substream; + atomic_set(&tascam->capture_active, 0); + + if (!tascam->playback_substream) { + err = tascam_alloc_urbs(tascam); + if (err < 0) + return err; + } return 0; } +static int tascam_playback_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + tascam->playback_substream = NULL; + if (!tascam->capture_substream) + tascam_free_urbs(tascam); + return 0; +} + +static int tascam_capture_close(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + tascam->capture_substream = NULL; + if (!tascam->playback_substream) + tascam_free_urbs(tascam); + return 0; +} + +/** + * us144mkii_configure_device_for_rate - Send USB control messages to set sample rate. + * @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. + */ static int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate) { struct usb_device *dev = tascam->dev; @@ -561,35 +680,37 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream, if (err < 0) return err; - /* Set rate-dependent feedback patterns and values */ - switch (rate) { - case 44100: - tascam->feedback_patterns = patterns_44khz; - tascam->feedback_base_value = 42; tascam->feedback_max_value = 46; - break; - case 48000: - tascam->feedback_patterns = patterns_48khz; - tascam->feedback_base_value = 46; tascam->feedback_max_value = 50; - break; - case 88200: - tascam->feedback_patterns = patterns_88khz; - tascam->feedback_base_value = 86; tascam->feedback_max_value = 90; - break; - case 96000: - tascam->feedback_patterns = patterns_96khz; - tascam->feedback_base_value = 94; tascam->feedback_max_value = 98; - break; - default: - return -EINVAL; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rate) { + case 44100: + tascam->feedback_patterns = patterns_44khz; + tascam->feedback_base_value = 42; tascam->feedback_max_value = 46; + break; + case 48000: + tascam->feedback_patterns = patterns_48khz; + tascam->feedback_base_value = 46; tascam->feedback_max_value = 50; + break; + case 88200: + tascam->feedback_patterns = patterns_88khz; + tascam->feedback_base_value = 86; tascam->feedback_max_value = 90; + break; + case 96000: + tascam->feedback_patterns = patterns_96khz; + tascam->feedback_base_value = 94; tascam->feedback_max_value = 98; + break; + default: + return -EINVAL; + } } - /* Always re-configure hardware to ensure it's in a clean state */ - err = us144mkii_configure_device_for_rate(tascam, rate); - if (err < 0) { - tascam->current_rate = 0; - return err; + 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; } - tascam->current_rate = rate; return 0; } @@ -599,7 +720,7 @@ static int tascam_pcm_hw_free(struct snd_pcm_substream *substream) return snd_pcm_lib_free_pages(substream); } -static int tascam_pcm_prepare(struct snd_pcm_substream *substream) +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; @@ -608,7 +729,6 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream) size_t total_bytes_in_urb; unsigned int feedback_packets; - /* Reset driver state for the new stream */ tascam->driver_playback_pos = 0; tascam->playback_frames_consumed = 0; tascam->last_period_pos = 0; @@ -618,24 +738,20 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream) tascam->feedback_consecutive_errors = 0; tascam->feedback_urb_skip_count = NUM_FEEDBACK_URBS; - /* Initialize feedback accumulator with nominal values */ nominal_frames_per_packet = runtime->rate / 8000; for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet; - /* Validate and apply latency profile */ switch (tascam->latency_profile) { - case 0: feedback_packets = 1; break; - case 1: feedback_packets = 2; break; - case 2: feedback_packets = 5; break; + case 0: feedback_packets = 1; break; /* Low */ + case 1: feedback_packets = 2; break; /* Normal */ + case 2: feedback_packets = 5; break; /* High */ default: feedback_packets = 2; } - /* Configure Feedback URBs */ 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++) { @@ -644,13 +760,11 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream) } } - /* Configure Playback URBs */ 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; @@ -663,52 +777,103 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream) return 0; } +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; +} + static int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - int err = 0, i; - bool start = false; + 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_xchg(&tascam->playback_active, 1) == 0) - start = true; + 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_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - atomic_set(&tascam->playback_active, 0); + 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; - default: return -EINVAL; } + 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; + } - if (start) { for (i = 0; i < NUM_FEEDBACK_URBS; i++) { err = usb_submit_urb(tascam->feedback_urbs[i], GFP_ATOMIC); - if (err < 0) goto rollback; + if (err < 0) + goto start_rollback; + atomic_inc(&tascam->active_urbs); } for (i = 0; i < NUM_PLAYBACK_URBS; i++) { err = usb_submit_urb(tascam->playback_urbs[i], GFP_ATOMIC); - if (err < 0) goto rollback; + if (err < 0) + goto start_rollback; + atomic_inc(&tascam->active_urbs); } - } else { - for (i = 0; i < NUM_PLAYBACK_URBS; i++) - usb_unlink_urb(tascam->playback_urbs[i]); - for (i = 0; i < NUM_FEEDBACK_URBS; i++) - usb_unlink_urb(tascam->feedback_urbs[i]); + for (i = 0; i < NUM_CAPTURE_URBS; i++) { + err = usb_submit_urb(tascam->capture_urbs[i], GFP_ATOMIC); + if (err < 0) + 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) { + for (i = 0; i < NUM_PLAYBACK_URBS; i++) { + usb_unlink_urb(tascam->playback_urbs[i]); + atomic_dec(&tascam->active_urbs); + } + for (i = 0; i < NUM_FEEDBACK_URBS; i++) { + usb_unlink_urb(tascam->feedback_urbs[i]); + atomic_dec(&tascam->active_urbs); + } + for (i = 0; i < NUM_CAPTURE_URBS; i++) { + usb_unlink_urb(tascam->capture_urbs[i]); + atomic_dec(&tascam->active_urbs); + } + cancel_work_sync(&tascam->capture_work); } - return 0; -rollback: - dev_err(tascam->card->dev, "Failed to submit URBs to start stream: %d\n", err); - atomic_set(&tascam->playback_active, 0); - for (i = 0; i < NUM_PLAYBACK_URBS; i++) - usb_unlink_urb(tascam->playback_urbs[i]); - for (i = 0; i < NUM_FEEDBACK_URBS; i++) - usb_unlink_urb(tascam->feedback_urbs[i]); return err; } -static snd_pcm_uframes_t tascam_pcm_pointer(struct snd_pcm_substream *substream) +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; @@ -725,76 +890,6 @@ static snd_pcm_uframes_t tascam_pcm_pointer(struct snd_pcm_substream *substream) return runtime ? pos % runtime->buffer_size : 0; } -static struct snd_pcm_ops tascam_playback_ops = { - .open = tascam_pcm_open, .close = tascam_pcm_close, - .ioctl = snd_pcm_lib_ioctl, .hw_params = tascam_pcm_hw_params, - .hw_free = tascam_pcm_hw_free, .prepare = tascam_pcm_prepare, - .trigger = tascam_pcm_trigger, .pointer = tascam_pcm_pointer, -}; - -/* --- Dummy Capture Implementation --- */ -static int tascam_capture_open(struct snd_pcm_substream *substream) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - - runtime->hw = tascam_pcm_hw; - tascam->capture_substream = substream; - atomic_set(&tascam->capture_active, 0); - return 0; -} - -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; -} - -static int tascam_capture_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); -} - -static int tascam_capture_hw_free(struct snd_pcm_substream *substream) -{ - return snd_pcm_lib_free_pages(substream); -} - -static int tascam_capture_prepare(struct snd_pcm_substream *substream) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - - tascam->capture_frames_produced = 0; - tascam->last_capture_period_pos = 0; - snd_pcm_format_set_silence(runtime->format, runtime->dma_area, - runtime->buffer_size * runtime->channels); - return 0; -} - -static int tascam_capture_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - atomic_set(&tascam->capture_active, 1); - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - atomic_set(&tascam->capture_active, 0); - break; - default: - return -EINVAL; - } - return 0; -} - static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); @@ -806,23 +901,43 @@ static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substr return 0; spin_lock_irqsave(&tascam->lock, flags); - pos = tascam->capture_frames_produced; + pos = tascam->capture_frames_processed; spin_unlock_irqrestore(&tascam->lock, flags); return runtime ? pos % runtime->buffer_size : 0; } +static 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, +}; + static struct snd_pcm_ops tascam_capture_ops = { .open = tascam_capture_open, .close = tascam_capture_close, .ioctl = snd_pcm_lib_ioctl, - .hw_params = tascam_capture_hw_params, - .hw_free = tascam_capture_hw_free, + .hw_params = tascam_pcm_hw_params, + .hw_free = tascam_pcm_hw_free, .prepare = tascam_capture_prepare, - .trigger = tascam_capture_trigger, + .trigger = tascam_pcm_trigger, .pointer = tascam_capture_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. + */ static void playback_urb_complete(struct urb *urb) { struct tascam_card *tascam = urb->context; @@ -835,10 +950,14 @@ static void playback_urb_complete(struct urb *urb) snd_pcm_uframes_t frames_to_copy; int ret, i; - if (urb->status) + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && urb->status != -ESHUTDOWN) + dev_err_ratelimited(tascam->card->dev, "Playback URB failed: %d\n", urb->status); return; + } if (!tascam || !atomic_read(&tascam->playback_active)) return; + substream = tascam->playback_substream; if (!substream || !substream->runtime) return; @@ -846,7 +965,6 @@ static void playback_urb_complete(struct urb *urb) spin_lock_irqsave(&tascam->lock, flags); - /* Phase 1: Populate the isochronous frame descriptors from the accumulator. */ for (i = 0; i < urb->number_of_packets; i++) { unsigned int frames_for_packet; size_t bytes_for_packet; @@ -865,15 +983,12 @@ static void playback_urb_complete(struct urb *urb) } urb->transfer_buffer_length = total_bytes_for_urb; - /* Phase 2: Atomically update the driver's position. */ 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; - /* --- End of Critical Section --- */ spin_unlock_irqrestore(&tascam->lock, flags); - /* Phase 3: Perform the data copy OUTSIDE the lock. */ if (total_bytes_for_urb > 0) { int f; src_buf = runtime->dma_area; @@ -907,11 +1022,22 @@ static void playback_urb_complete(struct urb *urb) dev_err_ratelimited(tascam->card->dev, "Failed to resubmit playback URB: %d\n", ret); } +/** + * 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. + */ static void feedback_urb_complete(struct urb *urb) { struct tascam_card *tascam = urb->context; - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; + 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; @@ -919,18 +1045,24 @@ static void feedback_urb_complete(struct urb *urb) bool playback_period_elapsed = false; bool capture_period_elapsed = false; - if (urb->status) + if (urb->status) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET && urb->status != -ESHUTDOWN) + dev_err_ratelimited(tascam->card->dev, "Feedback URB failed: %d\n", urb->status); return; + } if (!tascam || !atomic_read(&tascam->playback_active)) return; - substream = tascam->playback_substream; - if (!substream || !substream->runtime) + + playback_ss = tascam->playback_substream; + if (!playback_ss || !playback_ss->runtime) return; - runtime = substream->runtime; + playback_rt = playback_ss->runtime; + + capture_ss = tascam->capture_substream; + capture_rt = capture_ss ? capture_ss->runtime : NULL; spin_lock_irqsave(&tascam->lock, flags); - /* Hybrid Sync: Initial blind period for hardware to settle. */ if (tascam->feedback_urb_skip_count > 0) { tascam->feedback_urb_skip_count--; goto unlock_and_continue; @@ -958,7 +1090,7 @@ static void feedback_urb_complete(struct urb *urb) total_frames_in_urb += pattern[i]; } } else { - unsigned int nominal_frames = runtime->rate / 8000; + unsigned int nominal_frames = playback_rt->rate / 8000; int i; if (tascam->feedback_synced) { tascam->feedback_consecutive_errors++; @@ -979,7 +1111,6 @@ static void feedback_urb_complete(struct urb *urb) new_in_idx = tascam->feedback_pattern_in_idx; - /* Hybrid Sync: Pointer Lap check for sync acquisition. */ 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); @@ -995,25 +1126,22 @@ static void feedback_urb_complete(struct urb *urb) if (total_frames_in_urb > 0) { tascam->playback_frames_consumed += total_frames_in_urb; if (atomic_read(&tascam->capture_active)) - tascam->capture_frames_produced += total_frames_in_urb; + tascam->capture_frames_processed += total_frames_in_urb; } - if (runtime->period_size > 0) { - u64 current_period = div_u64(tascam->playback_frames_consumed, runtime->period_size); + 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) && tascam->capture_substream) { - struct snd_pcm_runtime *capture_runtime = tascam->capture_substream->runtime; - if (capture_runtime && capture_runtime->period_size > 0) { - u64 current_capture_period = div_u64(tascam->capture_frames_produced, capture_runtime->period_size); - if (current_capture_period > tascam->last_capture_period_pos) { - tascam->last_capture_period_pos = current_capture_period; - capture_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; } } @@ -1021,9 +1149,9 @@ unlock_and_continue: spin_unlock_irqrestore(&tascam->lock, flags); if (playback_period_elapsed) - snd_pcm_period_elapsed(substream); + snd_pcm_period_elapsed(playback_ss); if (capture_period_elapsed) - snd_pcm_period_elapsed(tascam->capture_substream); + snd_pcm_period_elapsed(capture_ss); urb->dev = tascam->dev; ret = usb_submit_urb(urb, GFP_ATOMIC); @@ -1031,7 +1159,183 @@ unlock_and_continue: dev_err_ratelimited(tascam->card->dev, "Failed to resubmit feedback URB: %d\n", ret); } +/** + * 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 handler 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. + */ +static 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; + + if (!substream || !substream->runtime) + return; + runtime = substream->runtime; + + u8 *raw_block = tascam->capture_decode_raw_block; + s32 *decoded_block = tascam->capture_decode_dst_block; + + if (!raw_block || !decoded_block) { + dev_err(tascam->card->dev, "Capture decode 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); + + spin_lock_irqsave(&tascam->lock, flags); + if (atomic_read(&tascam->capture_active)) { + int f; + for (f = 0; f < FRAMES_PER_DECODE_BLOCK; ++f) { + s32 *decoded_frame = decoded_block + (f * DECODED_CHANNELS_PER_FRAME); + char *dst_frame = runtime->dma_area + frames_to_bytes(runtime, tascam->driver_capture_pos); + + s32 *src_analog = decoded_frame; + s32 *src_digital = decoded_frame + 2; + + /* The decoded samples are in S32_LE format. The ALSA format is + * S24_3LE. We copy the 3 least significant bytes by starting + * the memcpy from the second byte of the 32-bit integer. + */ + if (tascam->capture_12_source == 0) { + memcpy(dst_frame, ((char *)src_analog) + 1, 3); // Ch1 from Analog 1 + memcpy(dst_frame + 3, ((char *)(src_analog + 1)) + 1, 3); // Ch2 from Analog 2 + } else { + memcpy(dst_frame, ((char *)src_digital) + 1, 3); // Ch1 from Digital 1 + memcpy(dst_frame + 3, ((char *)(src_digital + 1)) + 1, 3); // Ch2 from Digital 2 + } + + /* Since the device has only two analog inputs, channels 3-4 can be + * sourced from a copy of the analog inputs or the digital input. + */ + if (tascam->capture_34_source == 0) { + memcpy(dst_frame + 6, ((char *)src_analog) + 1, 3); // Ch3 from Analog 1 + memcpy(dst_frame + 9, ((char *)(src_analog + 1)) + 1, 3); // Ch4 from Analog 2 + } else { + memcpy(dst_frame + 6, ((char *)src_digital) + 1, 3); // Ch3 from Digital 1 + memcpy(dst_frame + 9, ((char *)(src_digital + 1)) + 1, 3); // Ch4 from Digital 2 + } + + tascam->driver_capture_pos++; + if (tascam->driver_capture_pos >= runtime->buffer_size) + tascam->driver_capture_pos = 0; + } + } + 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. + */ +static 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) + dev_err_ratelimited(tascam->card->dev, "Capture URB failed: %d\n", urb->status); + return; + } + if (!tascam || !atomic_read(&tascam->capture_active)) + return; + + 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); + } + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err_ratelimited(tascam->card->dev, "Failed to resubmit capture URB: %d\n", ret); +} static int tascam_create_pcm(struct tascam_card *tascam) { @@ -1077,11 +1381,6 @@ static void tascam_card_private_free(struct snd_card *card) } } -/** - * tascam_suspend - Called when the device is being suspended. - * @intf: The USB interface. - * @message: Power management message. - */ static int tascam_suspend(struct usb_interface *intf, pm_message_t message) { struct tascam_card *tascam = usb_get_intfdata(intf); @@ -1090,14 +1389,11 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message) return 0; snd_pcm_suspend_all(tascam->pcm); + cancel_work_sync(&tascam->capture_work); return 0; } -/** - * tascam_resume - Called when the device is being resumed. - * @intf: The USB interface. - */ static int tascam_resume(struct usb_interface *intf) { struct tascam_card *tascam = usb_get_intfdata(intf); @@ -1110,7 +1406,6 @@ static int tascam_resume(struct usb_interface *intf) dev = tascam->dev; dev_info(&intf->dev, "Resuming and re-initializing device...\n"); - /* Re-establish alternate settings for both interfaces */ err = usb_set_interface(dev, 0, 1); if (err < 0) { dev_err(&intf->dev, "Resume: Set Alt Setting on Intf 0 failed: %d\n", err); @@ -1122,7 +1417,6 @@ static int tascam_resume(struct usb_interface *intf) return err; } - /* Re-configure the device for the last used sample rate. */ if (tascam->current_rate > 0) { dev_info(&intf->dev, "Restoring sample rate to %d Hz\n", tascam->current_rate); err = us144mkii_configure_device_for_rate(tascam, tascam->current_rate); @@ -1136,6 +1430,18 @@ static int tascam_resume(struct usb_interface *intf) return 0; } +/** + * tascam_probe - Entry point for when the USB device is detected. + * @intf: the USB interface that was matched. + * @usb_id: the matching USB device ID. + * + * This function is called by the USB core when a device matching the driver's + * ID table is connected. It allocates the sound card, initializes the driver + * data structure, claims interfaces, sets up the device, creates the PCM + * and control interfaces, and registers the sound card with ALSA. + * + * 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); @@ -1166,12 +1472,14 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id * card->private_free = tascam_card_private_free; usb_set_intfdata(intf, tascam); spin_lock_init(&tascam->lock); + atomic_set(&tascam->active_urbs, 0); + INIT_WORK(&tascam->capture_work, tascam_capture_work_handler); tascam->latency_profile = 1; tascam->line_out_source = 0; tascam->digital_out_source = 1; tascam->capture_12_source = 0; tascam->capture_34_source = 1; - tascam->current_rate = 0; /* Not known until hw_params */ + tascam->current_rate = 0; strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname)); @@ -1212,7 +1520,7 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id * 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 == 1 && handshake_buf[0] == 0x12) + if (err == 1 && handshake_buf[0] == HANDSHAKE_SUCCESS_VAL) dev_info(&intf->dev, "Handshake successful.\n"); else dev_warn(&intf->dev, "Handshake failed (err %d, val 0x%02x), continuing anyway.\n", err, err > 0 ? handshake_buf[0] : 0); @@ -1244,6 +1552,14 @@ free_card_obj: return err; } +/** + * tascam_disconnect - Entry point for when the USB device is disconnected. + * @intf: the USB interface being disconnected. + * + * This function is called by the USB core when the device is removed. It + * cancels any pending work, disconnects the sound card from ALSA, releases + * claimed interfaces, and schedules the card structure to be freed. + */ static void tascam_disconnect(struct usb_interface *intf) { struct tascam_card *tascam = usb_get_intfdata(intf); @@ -1259,13 +1575,14 @@ static void tascam_disconnect(struct usb_interface *intf) dev_info(&intf->dev, "TASCAM US-144MKII disconnecting...\n"); snd_card_disconnect(tascam->card); + cancel_work_sync(&tascam->capture_work); + if (tascam->iface1) { usb_set_intfdata(tascam->iface1, NULL); usb_driver_release_interface(&tascam_alsa_driver, tascam->iface1); tascam->iface1 = NULL; } - /* Decrement the device index to allow the next probe to use this slot. */ if (dev_idx > 0) dev_idx--;