more up to spec changes for playback

This commit is contained in:
serifpersia 2025-07-06 09:47:08 +02:00
parent a948d2eec3
commit fc3a76c66e
1 changed files with 42 additions and 121 deletions

View File

@ -19,7 +19,7 @@ MODULE_AUTHOR("serifpersia <ramiserifpersia@gmail.com>");
MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define DRIVER_NAME "snd-usb-us144mkii" #define DRIVER_NAME "us144mkii"
/* /*
* TODO: * TODO:
@ -36,6 +36,7 @@ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
static int dev_idx; static int dev_idx;
static int playback_urb_packets = 8;
module_param_array(index, int, NULL, 0444); module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for the US-144MKII soundcard."); MODULE_PARM_DESC(index, "Index value for the US-144MKII soundcard.");
@ -43,6 +44,8 @@ module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for the US-144MKII soundcard."); MODULE_PARM_DESC(id, "ID string for the US-144MKII soundcard.");
module_param_array(enable, bool, NULL, 0444); module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable this US-144MKII soundcard."); MODULE_PARM_DESC(enable, "Enable this US-144MKII soundcard.");
module_param_named(playback_urb_packets, playback_urb_packets, int, 0644);
MODULE_PARM_DESC(playback_urb_packets, "Number of isochronous packets per playback URB (default: 8). Higher values may improve CPU efficiency but require a low-latency or RT kernel to avoid xruns.");
/* --- USB Device Identification --- */ /* --- USB Device Identification --- */
#define USB_VID_TASCAM 0x0644 #define USB_VID_TASCAM 0x0644
@ -79,14 +82,13 @@ MODULE_PARM_DESC(enable, "Enable this US-144MKII soundcard.");
/* --- URB Configuration --- */ /* --- URB Configuration --- */
#define NUM_PLAYBACK_URBS 8 #define NUM_PLAYBACK_URBS 8
#define NUM_FEEDBACK_URBS 4 #define NUM_FEEDBACK_URBS 4
#define MAX_FEEDBACK_PACKETS 5 #define MAX_FEEDBACK_PACKETS 5 /* Max packets needed for any profile */
#define MAX_PLAYBACK_URB_ISO_PACKETS 8
#define FEEDBACK_PACKET_SIZE 3 #define FEEDBACK_PACKET_SIZE 3
#define USB_CTRL_TIMEOUT_MS 1000 #define USB_CTRL_TIMEOUT_MS 1000
/* --- Audio Format Configuration --- */ /* --- Audio Format Configuration --- */
#define BYTES_PER_SAMPLE 3 #define BYTES_PER_SAMPLE 3
#define NUM_CHANNELS 4 /* Changed: Match hardware for efficient copy */ #define NUM_CHANNELS 4
#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) #define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE)
#define FEEDBACK_ACCUMULATOR_SIZE 128 #define FEEDBACK_ACCUMULATOR_SIZE 128
@ -102,7 +104,7 @@ static void tascam_disconnect(struct usb_interface *intf);
static int tascam_suspend(struct usb_interface *intf, pm_message_t message); static int tascam_suspend(struct usb_interface *intf, pm_message_t message);
static int tascam_resume(struct usb_interface *intf); static int tascam_resume(struct usb_interface *intf);
/* --- Rate-to-Packet Fixing Data --- */ /* --- Rate-to-Packet Fixing Data (Verified) --- */
static const unsigned int patterns_48khz[5][8] = { static const unsigned int patterns_48khz[5][8] = {
{5, 6, 6, 6, 5, 6, 6, 6}, {5, 6, 6, 6, 6, 6, 6, 6}, {5, 6, 6, 6, 5, 6, 6, 6}, {5, 6, 6, 6, 6, 6, 6, 6},
{6, 6, 6, 6, 6, 6, 6, 6}, {7, 6, 6, 6, 6, 6, 6, 6}, {6, 6, 6, 6, 6, 6, 6, 6}, {7, 6, 6, 6, 6, 6, 6, 6},
@ -143,9 +145,6 @@ struct tascam_card {
atomic_t playback_active; atomic_t playback_active;
int current_rate; int current_rate;
/* Stores the hardware profile index decided in hw_params for use in prepare */
int profile_idx;
unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE];
unsigned int feedback_pattern_out_idx; unsigned int feedback_pattern_out_idx;
unsigned int feedback_pattern_in_idx; unsigned int feedback_pattern_in_idx;
@ -169,8 +168,8 @@ static const struct snd_pcm_hardware tascam_pcm_hw = {
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000), SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100, .rate_max = 96000, .rate_min = 44100, .rate_max = 96000,
.channels_min = NUM_CHANNELS, /* Changed: Expose 4 channels */ .channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS, /* Changed: Expose 4 channels */ .channels_max = NUM_CHANNELS,
.buffer_bytes_max = 1024 * 1024, .buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 48 * BYTES_PER_FRAME, .period_bytes_min = 48 * BYTES_PER_FRAME,
.period_bytes_max = 1024 * BYTES_PER_FRAME, .period_bytes_max = 1024 * BYTES_PER_FRAME,
@ -180,9 +179,6 @@ static const struct snd_pcm_hardware tascam_pcm_hw = {
/** /**
* tascam_free_urbs - Free all allocated URBs and their buffers. * tascam_free_urbs - Free all allocated URBs and their buffers.
* @tascam: The card instance. * @tascam: The card instance.
*
* This function is the counterpart to tascam_alloc_urbs. It is called
* when the PCM device is closed to release all USB resources.
*/ */
static void tascam_free_urbs(struct tascam_card *tascam) static void tascam_free_urbs(struct tascam_card *tascam)
{ {
@ -214,9 +210,6 @@ static void tascam_free_urbs(struct tascam_card *tascam)
/** /**
* tascam_alloc_urbs - Allocate URBs and their buffers. * tascam_alloc_urbs - Allocate URBs and their buffers.
* @tascam: The card instance. * @tascam: The card instance.
*
* Allocates all necessary URBs for playback and feedback. This is called
* when the PCM device is opened.
* Returns: 0 on success, or a negative error code on failure. * Returns: 0 on success, or a negative error code on failure.
*/ */
static int tascam_alloc_urbs(struct tascam_card *tascam) static int tascam_alloc_urbs(struct tascam_card *tascam)
@ -225,10 +218,10 @@ static int tascam_alloc_urbs(struct tascam_card *tascam)
size_t max_packet_size; size_t max_packet_size;
max_packet_size = ((96000 / 8000) + 2) * BYTES_PER_FRAME; max_packet_size = ((96000 / 8000) + 2) * BYTES_PER_FRAME;
tascam->playback_urb_alloc_size = max_packet_size * MAX_PLAYBACK_URB_ISO_PACKETS; tascam->playback_urb_alloc_size = max_packet_size * playback_urb_packets;
for (i = 0; i < NUM_PLAYBACK_URBS; i++) { for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
struct urb *urb = usb_alloc_urb(MAX_PLAYBACK_URB_ISO_PACKETS, GFP_KERNEL); struct urb *urb = usb_alloc_urb(playback_urb_packets, GFP_KERNEL);
if (!urb) if (!urb)
goto error; goto error;
tascam->playback_urbs[i] = urb; tascam->playback_urbs[i] = urb;
@ -362,65 +355,11 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int err; int err;
unsigned int rate = params_rate(params); unsigned int rate = params_rate(params);
unsigned int period_frames = params_period_size(params);
/* Latency profile thresholds (in frames) based on hardware specification */
unsigned int profile_thresholds[5];
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (err < 0) if (err < 0)
return err; return err;
/* Set profile thresholds based on the selected sample rate */
switch (rate) {
case 44100:
profile_thresholds[0] = 49; /* Lowest */
profile_thresholds[1] = 64; /* Low */
profile_thresholds[2] = 128; /* Normal */
profile_thresholds[3] = 256; /* High */
profile_thresholds[4] = 512; /* Highest */
break;
case 48000:
profile_thresholds[0] = 48;
profile_thresholds[1] = 64;
profile_thresholds[2] = 128;
profile_thresholds[3] = 256;
profile_thresholds[4] = 512;
break;
case 88200:
profile_thresholds[0] = 98;
profile_thresholds[1] = 128;
profile_thresholds[2] = 256;
profile_thresholds[3] = 512;
profile_thresholds[4] = 1024;
break;
case 96000:
profile_thresholds[0] = 96;
profile_thresholds[1] = 128;
profile_thresholds[2] = 256;
profile_thresholds[3] = 512;
profile_thresholds[4] = 1024;
break;
default:
return -EINVAL;
}
/* Map the application's requested period size to a hardware profile */
if (period_frames <= profile_thresholds[0])
tascam->profile_idx = 0;
else if (period_frames <= profile_thresholds[1])
tascam->profile_idx = 1;
else if (period_frames <= profile_thresholds[2])
tascam->profile_idx = 2;
else if (period_frames <= profile_thresholds[3])
tascam->profile_idx = 3;
else /* Anything larger falls into the highest latency profile */
tascam->profile_idx = 4;
dev_info(tascam->card->dev,
"User requested period of %u frames @ %u Hz, mapping to hardware profile %d\n",
period_frames, rate, tascam->profile_idx);
/* Set rate-dependent feedback patterns and values */ /* Set rate-dependent feedback patterns and values */
switch (rate) { switch (rate) {
case 44100: case 44100:
@ -439,6 +378,8 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
tascam->feedback_patterns = patterns_96khz; tascam->feedback_patterns = patterns_96khz;
tascam->feedback_base_value = 94; tascam->feedback_max_value = 98; tascam->feedback_base_value = 94; tascam->feedback_max_value = 98;
break; break;
default:
return -EINVAL;
} }
/* Re-configure hardware only if the sample rate has changed */ /* Re-configure hardware only if the sample rate has changed */
@ -466,10 +407,7 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream)
int i, u; int i, u;
size_t nominal_frames_per_packet, nominal_bytes_per_packet; size_t nominal_frames_per_packet, nominal_bytes_per_packet;
size_t total_bytes_in_urb; size_t total_bytes_in_urb;
unsigned int playback_urb_iso_packets; unsigned int feedback_packets;
/* Feedback packet counts for each of the 5 hardware profiles */
static const unsigned int feedback_packets_for_profile[] = { 1, 1, 2, 5, 5 };
/* Reset driver state for the new stream */ /* Reset driver state for the new stream */
tascam->driver_playback_pos = 0; tascam->driver_playback_pos = 0;
@ -488,39 +426,44 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream)
tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet; tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet;
/* /*
* Program URBs safely based on the configuration chosen in hw_params. * Configure URBs based on the fixed hardware profile for the current sample rate.
* This is the correct location, as the stream is guaranteed to be stopped. * The hardware latency is fixed per rate; we use the lowest latency setting.
*/ */
switch (runtime->rate) {
case 44100: feedback_packets = 1; break;
case 48000: feedback_packets = 1; break;
case 88200: feedback_packets = 1; break;
case 96000: feedback_packets = 1; break;
default: feedback_packets = 1; break; /* Failsafe */
}
/* Configure Feedback URBs with the correct number of packets for the profile */ dev_info(tascam->card->dev, "Prepare: Using fixed hardware profile for %u Hz (%u feedback packets)\n",
runtime->rate, feedback_packets);
/* Configure Feedback URBs */
for (i = 0; i < NUM_FEEDBACK_URBS; i++) { for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->feedback_urbs[i]; struct urb *f_urb = tascam->feedback_urbs[i];
unsigned int packets = feedback_packets_for_profile[tascam->profile_idx];
int j; int j;
f_urb->number_of_packets = packets; f_urb->number_of_packets = feedback_packets;
f_urb->transfer_buffer_length = packets * FEEDBACK_PACKET_SIZE; f_urb->transfer_buffer_length = feedback_packets * FEEDBACK_PACKET_SIZE;
for (j = 0; j < packets; j++) { for (j = 0; j < feedback_packets; j++) {
f_urb->iso_frame_desc[j].offset = j * FEEDBACK_PACKET_SIZE; f_urb->iso_frame_desc[j].offset = j * FEEDBACK_PACKET_SIZE;
f_urb->iso_frame_desc[j].length = FEEDBACK_PACKET_SIZE; f_urb->iso_frame_desc[j].length = FEEDBACK_PACKET_SIZE;
} }
} }
/* /* Configure Playback URBs */
* Configure Playback URBs. The number of packets is always 40,
* as per the hardware specification.
*/
playback_urb_iso_packets = MAX_PLAYBACK_URB_ISO_PACKETS;
nominal_bytes_per_packet = nominal_frames_per_packet * BYTES_PER_FRAME; nominal_bytes_per_packet = nominal_frames_per_packet * BYTES_PER_FRAME;
total_bytes_in_urb = nominal_bytes_per_packet * playback_urb_iso_packets; total_bytes_in_urb = nominal_bytes_per_packet * playback_urb_packets;
for (u = 0; u < NUM_PLAYBACK_URBS; u++) { for (u = 0; u < NUM_PLAYBACK_URBS; u++) {
struct urb *urb = tascam->playback_urbs[u]; struct urb *urb = tascam->playback_urbs[u];
memset(urb->transfer_buffer, 0, tascam->playback_urb_alloc_size); memset(urb->transfer_buffer, 0, tascam->playback_urb_alloc_size);
urb->transfer_buffer_length = total_bytes_in_urb; urb->transfer_buffer_length = total_bytes_in_urb;
urb->number_of_packets = playback_urb_iso_packets; urb->number_of_packets = playback_urb_packets;
for (i = 0; i < playback_urb_iso_packets; i++) { for (i = 0; i < playback_urb_packets; i++) {
urb->iso_frame_desc[i].offset = i * nominal_bytes_per_packet; urb->iso_frame_desc[i].offset = i * nominal_bytes_per_packet;
urb->iso_frame_desc[i].length = nominal_bytes_per_packet; urb->iso_frame_desc[i].length = nominal_bytes_per_packet;
} }
@ -626,10 +569,7 @@ static void playback_urb_complete(struct urb *urb)
spin_lock_irqsave(&tascam->lock, flags); spin_lock_irqsave(&tascam->lock, flags);
/* /* Phase 1: Calculate the total number of frames needed for this URB. */
* Phase 1: Calculate the total number of frames needed for this URB.
* The number of frames per packet varies based on the feedback from the device.
*/
for (i = 0; i < urb->number_of_packets; i++) { for (i = 0; i < urb->number_of_packets; i++) {
unsigned int frames_for_packet; unsigned int frames_for_packet;
@ -643,11 +583,7 @@ static void playback_urb_complete(struct urb *urb)
} }
total_bytes_for_urb = total_frames_for_urb * BYTES_PER_FRAME; total_bytes_for_urb = total_frames_for_urb * BYTES_PER_FRAME;
/* /* Phase 2: Perform an efficient bulk memory copy. */
* Phase 2: Perform an efficient bulk memory copy.
* This replaces the inefficient per-frame copy loop. It handles the
* wrap-around case for the ALSA circular buffer.
*/
src_buf = runtime->dma_area; src_buf = runtime->dma_area;
dst_buf = urb->transfer_buffer; dst_buf = urb->transfer_buffer;
if (total_bytes_for_urb > 0) { if (total_bytes_for_urb > 0) {
@ -666,10 +602,7 @@ static void playback_urb_complete(struct urb *urb)
} }
tascam->driver_playback_pos = (tascam->driver_playback_pos + total_frames_for_urb) % runtime->buffer_size; tascam->driver_playback_pos = (tascam->driver_playback_pos + total_frames_for_urb) % runtime->buffer_size;
/* /* Phase 3: Populate the isochronous frame descriptors. */
* Phase 3: Populate the isochronous frame descriptors.
* The USB controller requires the offset and length for each packet within the URB.
*/
urb->transfer_buffer_length = total_bytes_for_urb; urb->transfer_buffer_length = total_bytes_for_urb;
total_bytes_for_urb = 0; /* Reuse as running offset */ total_bytes_for_urb = 0; /* Reuse as running offset */
for (i = 0; i < urb->number_of_packets; i++) { for (i = 0; i < urb->number_of_packets; i++) {
@ -805,10 +738,6 @@ static void tascam_card_private_free(struct snd_card *card)
* tascam_suspend - Called when the device is being suspended. * tascam_suspend - Called when the device is being suspended.
* @intf: The USB interface. * @intf: The USB interface.
* @message: Power management message. * @message: Power management message.
*
* Stops all active audio streams to prepare for system sleep.
*
* Returns: 0 on success.
*/ */
static int tascam_suspend(struct usb_interface *intf, pm_message_t message) static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
{ {
@ -825,12 +754,6 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
/** /**
* tascam_resume - Called when the device is being resumed. * tascam_resume - Called when the device is being resumed.
* @intf: The USB interface. * @intf: The USB interface.
*
* Re-initializes the device hardware after system resume, restoring its
* alternate settings and sample rate configuration. This is necessary because
* the device may lose its state during suspend.
*
* Returns: 0 on success, or a negative error code on failure.
*/ */
static int tascam_resume(struct usb_interface *intf) static int tascam_resume(struct usb_interface *intf)
{ {
@ -856,18 +779,12 @@ static int tascam_resume(struct usb_interface *intf)
return err; return err;
} }
/* /* Re-configure the device for the last used sample rate. */
* Re-configure the device for the last used sample rate.
* If no stream was ever started, current_rate will be 0, and we skip this.
* The ALSA core will handle resuming the PCM streams, which will
* trigger our .prepare and .trigger ops as needed.
*/
if (tascam->current_rate > 0) { if (tascam->current_rate > 0) {
dev_info(&intf->dev, "Restoring sample rate to %d Hz\n", tascam->current_rate); dev_info(&intf->dev, "Restoring sample rate to %d Hz\n", tascam->current_rate);
err = us144mkii_configure_device_for_rate(tascam, tascam->current_rate); err = us144mkii_configure_device_for_rate(tascam, tascam->current_rate);
if (err < 0) { if (err < 0) {
dev_err(&intf->dev, "Resume: Failed to restore sample rate configuration\n"); dev_err(&intf->dev, "Resume: Failed to restore sample rate configuration\n");
/* Invalidate the rate so the next hw_params will re-configure fully. */
tascam->current_rate = 0; tascam->current_rate = 0;
return err; return err;
} }
@ -994,6 +911,10 @@ static void tascam_disconnect(struct usb_interface *intf)
tascam->iface1 = NULL; tascam->iface1 = NULL;
} }
/* Decrement the device index to allow the next probe to use this slot. */
if (dev_idx > 0)
dev_idx--;
snd_card_free_when_closed(tascam->card); snd_card_free_when_closed(tascam->card);
} }