From 7284fb4c4287aa2672a79bc39f1c734ca326ad76 Mon Sep 17 00:00:00 2001 From: serifpersia Date: Sat, 19 Jul 2025 20:18:11 +0200 Subject: [PATCH] initial midi(not functional) --- midi_test.c | 266 ++++++ us144mkii.c | 648 ++++++++++++++- working midi out | 2081 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2972 insertions(+), 23 deletions(-) create mode 100644 midi_test.c create mode 100644 working midi out diff --git a/midi_test.c b/midi_test.c new file mode 100644 index 0000000..3623ae3 --- /dev/null +++ b/midi_test.c @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NOTE_INTERVAL_MS 100 +#define NUM_CHORD_NOTES 3 + +#define TASCAM_VID 0x0644 +#define TASCAM_PID 0x8020 +#define EP_MIDI_OUT 0x04 +#define EP_MIDI_IN 0x83 +#define EP_AUDIO_OUT 0x02 +#define EP_CAPTURE_DATA 0x86 + +#define RT_H2D_CLASS_EP 0x22 +#define RT_D2H_VENDOR_DEV 0xc0 +#define RT_H2D_VENDOR_DEV 0x40 + +#define UAC_SET_CUR 0x01 +#define UAC_SAMPLING_FREQ_CONTROL 0x0100 +#define VENDOR_REQ_REGISTER_WRITE 65 +#define VENDOR_REQ_MODE_CONTROL 73 +#define USB_TIMEOUT 1000 + +#define NUM_AUDIO_TRANSFERS 8 +#define ISO_AUDIO_PACKETS_PER_TRANSFER 8 +#define BYTES_PER_SAMPLE 3 +#define DEVICE_CHANNELS 4 +#define DEVICE_FRAME_SIZE (DEVICE_CHANNELS * BYTES_PER_SAMPLE) +#define NUM_MIDI_IN_TRANSFERS 4 +#define MIDI_IN_BUF_SIZE 64 + +static volatile bool is_running = true; +long long total_bytes_sent = 0; + +int perform_device_init(libusb_device_handle *handle); +static void LIBUSB_CALL iso_audio_callback(struct libusb_transfer *transfer); +static void LIBUSB_CALL midi_in_callback(struct libusb_transfer *transfer); +void log_raw_midi_in(uint8_t *buf, int len); + +void sigint_handler(int signum) { + if (is_running) { + printf("\nCtrl+C detected, shutting down...\n"); + is_running = false; + } +} + +void send_tascam_midi_message(libusb_device_handle *handle, uint8_t *midi_msg, long long *total_bytes_sent) { + const size_t transfer_size = 9; + uint8_t packet1[transfer_size]; + uint8_t packet2[transfer_size]; + int r, actual_length; + + // Packet 1: Header and first MIDI byte + memset(packet1, 0xfd, transfer_size); + packet1[0] = (0 << 4) | (midi_msg[0] >> 4); // Cable 0, CIN + packet1[1] = midi_msg[0]; + packet1[8] = 0x00; + + // Packet 2: Second and third MIDI bytes + memset(packet2, 0xfd, transfer_size); + packet2[0] = midi_msg[1]; + packet2[1] = midi_msg[2]; + packet2[8] = 0x00; + + r = libusb_bulk_transfer(handle, EP_MIDI_OUT, packet1, transfer_size, &actual_length, USB_TIMEOUT); + if (r != 0) { + fprintf(stderr, "MIDI transfer error on packet 1: %s\n", libusb_error_name(r)); + is_running = false; + return; + } + *total_bytes_sent += actual_length; + + r = libusb_bulk_transfer(handle, EP_MIDI_OUT, packet2, transfer_size, &actual_length, USB_TIMEOUT); + if (r != 0) { + fprintf(stderr, "MIDI transfer error on packet 2: %s\n", libusb_error_name(r)); + is_running = false; + return; + } + *total_bytes_sent += actual_length; +} + +int main(int argc, char *argv[]) { + libusb_device_handle *handle = NULL; + struct libusb_transfer *audio_transfers[NUM_AUDIO_TRANSFERS] = {0}; + struct libusb_transfer *midi_in_transfers[NUM_MIDI_IN_TRANSFERS] = {0}; + bool kernel_driver_was_active[2] = {false, false}; + int r = 0; + + printf("--- TASCAM US-144MKII MIDI Loopback Test (Two-Packet) ---\n"); + printf("Please connect a MIDI cable from MIDI OUT to MIDI IN.\n"); + printf("Sending a %d-note chord every %d ms. Press Ctrl+C to stop.\n", NUM_CHORD_NOTES, NOTE_INTERVAL_MS); + + srand(time(NULL)); + signal(SIGINT, sigint_handler); + if (libusb_init(NULL) < 0) { r = 1; goto cleanup; } + handle = libusb_open_device_with_vid_pid(NULL, TASCAM_VID, TASCAM_PID); + if (!handle) { fprintf(stderr, "Device not found\n"); r = 1; goto cleanup; } + + for (int i = 0; i < 2; i++) { + if (libusb_kernel_driver_active(handle, i)) { + kernel_driver_was_active[i] = true; + if ((r = libusb_detach_kernel_driver(handle, i)) != 0) { + fprintf(stderr, "Could not detach driver for iface %d: %s\n", i, libusb_error_name(r)); + r = 1; goto cleanup; + } + } + } + if (perform_device_init(handle) != 0) { r = 1; goto cleanup; } + + const int nominal_frames_per_packet = 44100 / 8000; + const int audio_packet_size = nominal_frames_per_packet * DEVICE_FRAME_SIZE; + const int audio_transfer_size = audio_packet_size * ISO_AUDIO_PACKETS_PER_TRANSFER; + + printf("Starting silent audio stream...\n"); + for (int i = 0; i < NUM_AUDIO_TRANSFERS; i++) { + audio_transfers[i] = libusb_alloc_transfer(ISO_AUDIO_PACKETS_PER_TRANSFER); + unsigned char *buf = calloc(1, audio_transfer_size); + if (!buf) { fprintf(stderr, "Audio buffer alloc failed\n"); r=1; goto cleanup; } + libusb_fill_iso_transfer(audio_transfers[i], handle, EP_AUDIO_OUT, buf, audio_transfer_size, ISO_AUDIO_PACKETS_PER_TRANSFER, iso_audio_callback, NULL, USB_TIMEOUT); + libusb_set_iso_packet_lengths(audio_transfers[i], audio_packet_size); + if (libusb_submit_transfer(audio_transfers[i]) < 0) { + fprintf(stderr, "Failed to submit initial audio transfer\n"); r=1; goto cleanup; + } + } + + printf("Starting MIDI IN listener...\n"); + for (int i = 0; i < NUM_MIDI_IN_TRANSFERS; i++) { + midi_in_transfers[i] = libusb_alloc_transfer(0); + unsigned char* buf = malloc(MIDI_IN_BUF_SIZE); + if (!buf) { fprintf(stderr, "MIDI IN buffer alloc failed\n"); r=1; goto cleanup; } + libusb_fill_bulk_transfer(midi_in_transfers[i], handle, EP_MIDI_IN, buf, MIDI_IN_BUF_SIZE, midi_in_callback, NULL, 0); + if (libusb_submit_transfer(midi_in_transfers[i]) < 0) { + fprintf(stderr, "Failed to submit initial MIDI IN transfer\n"); r=1; goto cleanup; + } + } + + printf("\n--- Starting MIDI loop...---\n"); + + enum { STATE_SEND_ON, STATE_SEND_OFF } midi_send_state = STATE_SEND_ON; + struct timespec last_action_time; + clock_gettime(CLOCK_MONOTONIC, &last_action_time); + + while (is_running) { + struct timeval tv = {0, 1000}; + libusb_handle_events_timeout(NULL, &tv); + + struct timespec current_time; + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + + double elapsed_ms = (current_time.tv_sec - last_action_time.tv_sec) * 1000.0; + elapsed_ms += (current_time.tv_nsec - last_action_time.tv_nsec) / 1000000.0; + + if (elapsed_ms < NOTE_INTERVAL_MS) { + continue; + } + + if (midi_send_state == STATE_SEND_ON) { + printf("--- SENDING NOTE ON ---\n"); + uint8_t note = 60; + uint8_t velocity = (rand() % 123) + 5; + uint8_t midi_msg[] = {0x90, note, velocity}; + send_tascam_midi_message(handle, midi_msg, &total_bytes_sent); + } else { // STATE_SEND_OFF + printf("--- SENDING NOTE OFF ---\n\n"); + uint8_t note = 60; + uint8_t midi_msg[] = {0x80, note, 0}; + send_tascam_midi_message(handle, midi_msg, &total_bytes_sent); + } + + midi_send_state = (midi_send_state == STATE_SEND_ON) ? STATE_SEND_OFF : STATE_SEND_ON; + clock_gettime(CLOCK_MONOTONIC, &last_action_time); + } + +cleanup: + for(int i=0; i < NUM_AUDIO_TRANSFERS; i++) if (audio_transfers[i]) libusb_cancel_transfer(audio_transfers[i]); + for(int i=0; i < NUM_MIDI_IN_TRANSFERS; i++) if (midi_in_transfers[i]) libusb_cancel_transfer(midi_in_transfers[i]); + struct timeval final_tv = {0, 200000}; + libusb_handle_events_timeout_completed(NULL, &final_tv, NULL); + if (handle) { + libusb_release_interface(handle, 1); + libusb_release_interface(handle, 0); + for(int i = 0; i < 2; i++) if (kernel_driver_was_active[i]) libusb_attach_kernel_driver(handle, i); + libusb_close(handle); + } + for (int i=0; ibuffer) free(audio_transfers[i]->buffer); libusb_free_transfer(audio_transfers[i]); } + for (int i=0; ibuffer) free(midi_in_transfers[i]->buffer); libusb_free_transfer(midi_in_transfers[i]); } + + libusb_exit(NULL); + + printf("\n\n------ FINAL REPORT ------\n"); + printf("Total Raw MIDI Bytes Sent: %lld\n", total_bytes_sent); + printf("--------------------------\n"); + + printf("Cleanup complete.\n"); + return r; +} + +void log_raw_midi_in(uint8_t *buf, int len) { + printf("RECV RAW USB DATA (%d bytes):", len); + for(int i=0; istatus == LIBUSB_TRANSFER_COMPLETED) { + if (transfer->actual_length > 0) { + log_raw_midi_in(transfer->buffer, transfer->actual_length); + } + if (is_running) { + libusb_submit_transfer(transfer); + } + } else if (transfer->status != LIBUSB_TRANSFER_CANCELLED) { + fprintf(stderr, "MIDI IN callback error: %s\n", libusb_error_name(transfer->status)); + is_running = false; + } +} + +static void LIBUSB_CALL iso_audio_callback(struct libusb_transfer *transfer) { + if (transfer->status == LIBUSB_TRANSFER_COMPLETED) { if (is_running) libusb_submit_transfer(transfer); + } else if (transfer->status != LIBUSB_TRANSFER_CANCELLED) { + fprintf(stderr, "Audio callback error: %s\n", libusb_error_name(transfer->status)); + is_running = false; + } +} + +int perform_device_init(libusb_device_handle *handle) { + const unsigned char rate_data_44100[] = {0x44, 0xac, 0x00}; + uint16_t rate_vendor_wValue = 0x1000; + unsigned char buf[1]; int r; + char log_msg[128]; + + printf("\n--- STARTING DEVICE INITIALIZATION (Verified Sequence) ---\n"); + #define CHECK(desc, call) r = (call); if (r < 0) { fprintf(stderr, " [FAIL] %s: %s\n", desc, libusb_error_name(r)); return -1; } else { printf(" [OK] %s\n", desc); } + printf(" [INFO] Step 1: Set Interfaces\n"); + r = libusb_set_configuration(handle, 1); + if (r < 0 && r != LIBUSB_ERROR_BUSY) { fprintf(stderr, " [FAIL] Set Configuration 1: %s\n", libusb_error_name(r)); return -1; } + for (int i = 0; i <= 1; i++) { + snprintf(log_msg, sizeof(log_msg), "Claim Interface %d", i); CHECK(log_msg, libusb_claim_interface(handle, i)); + snprintf(log_msg, sizeof(log_msg), "Set Alt Setting on Intf %d", i); CHECK(log_msg, libusb_set_interface_alt_setting(handle, i, 1)); + } + printf("\n-- Step 2: Handshake --\n"); + CHECK("Vendor Handshake Read", libusb_control_transfer(handle, RT_D2H_VENDOR_DEV, VENDOR_REQ_MODE_CONTROL, 0x0000, 0x0000, buf, 1, USB_TIMEOUT)); + printf("\n-- Step 3: Set Initial Mode --\n"); + CHECK("Vendor Set Mode to 0x0010", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_MODE_CONTROL, 0x0010, 0x0000, NULL, 0, USB_TIMEOUT)); + printf("\n-- Step 4: Set Sample Rate (Prerequisite for MIDI) --\n"); + CHECK("UAC Set Rate on Capture EP", libusb_control_transfer(handle, RT_H2D_CLASS_EP, UAC_SET_CUR, UAC_SAMPLING_FREQ_CONTROL, EP_CAPTURE_DATA, (unsigned char*)rate_data_44100, 3, USB_TIMEOUT)); + CHECK("UAC Set Rate on Playback EP", libusb_control_transfer(handle, RT_H2D_CLASS_EP, UAC_SET_CUR, UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_OUT, (unsigned char*)rate_data_44100, 3, USB_TIMEOUT)); + printf("\n-- Step 5: Configure Internal Registers --\n"); + CHECK("Vendor Register Write (0x0d04)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x0d04, 0x0101, NULL, 0, USB_TIMEOUT)); + CHECK("Vendor Register Write (0x0e00)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x0e00, 0x0101, NULL, 0, USB_TIMEOUT)); + CHECK("Vendor Register Write (0x0f00)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x0f00, 0x0101, NULL, 0, USB_TIMEOUT)); + CHECK("Vendor Register Write (Rate)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, rate_vendor_wValue, 0x0101, NULL, 0, USB_TIMEOUT)); + CHECK("Vendor Register Write (0x110b)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_REGISTER_WRITE, 0x110b, 0x0101, NULL, 0, USB_TIMEOUT)); + printf("\n-- Step 6: Enable Streaming --\n"); + CHECK("Vendor Set Mode to 0x0030 (Enable Streaming)", libusb_control_transfer(handle, RT_H2D_VENDOR_DEV, VENDOR_REQ_MODE_CONTROL, 0x0030, 0x0000, NULL, 0, USB_TIMEOUT)); + + printf("\n--- INITIALIZATION COMPLETE ---\n"); + return 0; +} diff --git a/us144mkii.c b/us144mkii.c index 00dd798..589e57e 100644 --- a/us144mkii.c +++ b/us144mkii.c @@ -2,21 +2,28 @@ // Copyright (c) 2025 serifpersia /* * ALSA Driver for TASCAM US-144MKII Audio Interface + * + * This version includes a robust, state-machine-based MIDI implementation + * to fix packet dropouts and a hardware activation sequence to enable MIDI input. */ #include #include +#include +#include #include #include +#include #include #include +#include MODULE_AUTHOR("serifpersia "); MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); MODULE_LICENSE("GPL v2"); #define DRIVER_NAME "us144mkii" -#define DRIVER_VERSION "1.5" +#define DRIVER_VERSION "2.5" // Version bump for MIDI fixes /* --- Module Parameters --- */ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; @@ -68,6 +75,11 @@ static int dev_idx; #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_OUT_BUF_SIZE 64 // Buffer in URB, must be >= 9 +#define NUM_MIDI_OUT_URBS 8 // Increased for better throughput +#define MIDI_OUT_PACKET_QUEUE_SIZE 32 // Intermediate queue for 9-byte packets #define USB_CTRL_TIMEOUT_MS 1000 /* --- Audio Format Configuration --- */ @@ -82,6 +94,17 @@ static int dev_idx; #define FRAMES_PER_DECODE_BLOCK 8 #define RAW_BYTES_PER_DECODE_BLOCK 512 +/* --- State machine for the MIDI output parser --- */ +enum tascam_midi_out_state { + MIDI_OUT_STATE_UNKNOWN, /* Waiting for a status byte */ + MIDI_OUT_STATE_1PARAM, /* Waiting for 1 data byte (e.g., for Program Change) */ + MIDI_OUT_STATE_2PARAM_1, /* Waiting for the 1st of 2 data bytes (e.g., for Note On) */ + MIDI_OUT_STATE_2PARAM_2, /* Waiting for the 2nd of 2 data bytes */ + MIDI_OUT_STATE_SYSEX_0, /* In SysEx, waiting for 1st data byte of a 3-byte chunk */ + MIDI_OUT_STATE_SYSEX_1, /* In SysEx, waiting for 2nd data byte */ + MIDI_OUT_STATE_SYSEX_2, /* In SysEx, waiting for 3rd data byte */ +}; + /* --- Main Driver Data Structure --- */ struct tascam_card { struct usb_device *dev; @@ -89,6 +112,7 @@ struct tascam_card { struct usb_interface *iface1; struct snd_card *card; struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; /* Playback stream */ struct snd_pcm_substream *playback_substream; @@ -118,6 +142,42 @@ struct tascam_card { s32 *capture_routing_buffer; struct work_struct capture_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 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; /* bitmask */ + spinlock_t midi_out_lock; + + /* --- NEW: MIDI Output Packet Queue --- */ + /** + * @midi_out_packet_queue: Ring buffer for formatted 9-byte MIDI packets. + * The state machine places formatted packets here, and the work handler + * dequeues them to send in individual URBs. This is critical because + * the device expects one 9-byte bulk transfer per packet. + */ + u8 midi_out_packet_queue[MIDI_OUT_PACKET_QUEUE_SIZE][9]; + int midi_out_queue_read_ptr; + volatile int midi_out_queue_write_ptr; + + /* State machine for MIDI output stream */ + struct { + enum tascam_midi_out_state state; + u8 data[2]; /* Buffer for holding partial MIDI messages */ + u8 running_status; /* Currently active running status */ + } midi_out_state; + + /* State machine for MIDI input parser */ + enum { + MIDI_IN_STATE_WAIT_PACKET_1, + MIDI_IN_STATE_WAIT_PACKET_2 + } midi_in_state; + u8 midi_in_b0; /* Status byte from the first packet */ + /* Shared state & Routing Matrix */ spinlock_t lock; atomic_t active_urbs; @@ -146,6 +206,9 @@ 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 void tascam_midi_in_urb_complete(struct urb *urb); +static void tascam_midi_out_urb_complete(struct urb *urb); +static void tascam_midi_out_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); @@ -435,8 +498,9 @@ static const struct snd_pcm_hardware tascam_pcm_hw = { * 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. + * This function kills, unlinks, and frees all playback, feedback, capture, + * and MIDI URBs, along with their transfer buffers and the capture + * ring/decode buffers. */ static void tascam_free_urbs(struct tascam_card *tascam) { @@ -475,11 +539,39 @@ static void tascam_free_urbs(struct tascam_card *tascam) } } + /* MIDI URB and buffer freeing */ + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + if (tascam->midi_in_urbs[i]) { + usb_kill_urb(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; + } + } + + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i]) { + usb_kill_urb(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; } /** @@ -487,7 +579,7 @@ static void tascam_free_urbs(struct tascam_card *tascam) * @tascam: the tascam_card instance. * * This function allocates and initializes all URBs for playback, feedback, - * and capture, as well as the necessary buffers for capture data processing. + * capture, and MIDI, as well as the necessary buffers for data processing. * * Return: 0 on success, or a negative error code on failure. */ @@ -560,6 +652,38 @@ static int tascam_alloc_urbs(struct tascam_card *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; @@ -646,7 +770,8 @@ static int tascam_capture_close(struct snd_pcm_substream *substream) * @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. + * to configure the device hardware for the specified sample rate. This sequence + * is also required to activate the MIDI ports. * * Return: 0 on success, or a negative error code on failure. */ @@ -714,6 +839,20 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream, int err; unsigned int rate = params_rate(params); + /* --- MODIFIED: Check if rate is already set --- */ + /** + * The device is configured to a default rate at probe time to enable + * MIDI. If an audio application requests the same rate, we don't need + * to re-run the entire configuration sequence. + */ + if (rate == tascam->current_rate) { + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) + return err; + // Rate is already configured, just return. + return 0; + } + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); if (err < 0) return err; @@ -741,14 +880,13 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream, } } - 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; + /* This will only run if the new rate is different from the current one */ + err = us144mkii_configure_device_for_rate(tascam, rate); + if (err < 0) { + tascam->current_rate = 0; + return err; } + tascam->current_rate = rate; return 0; } @@ -1350,15 +1488,420 @@ static void capture_urb_complete(struct urb *urb) dev_err_ratelimited(tascam->card->dev, "Failed to resubmit capture URB: %d\n", ret); } -static int tascam_create_pcm(struct tascam_card *tascam) +/* --- ALSA RawMIDI Implementation --- */ + +/** + * tascam_midi_in_urb_complete - Completion handler for MIDI IN bulk URBs. + * @urb: The completed URB. + * + * The device sends MIDI data in a peculiar 2-packet format. A standard 3-byte + * MIDI message (e.g., Note On) is split across two 9-byte USB packets. This + * function uses a simple state machine to reassemble these messages. + */ +static 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) + dev_info(tascam->card->dev, "MIDI IN URB cancelled: %d\n", urb->status); + return; + } + + if (!tascam || !atomic_read(&tascam->midi_in_active) || !tascam->midi_in_substream) + goto resubmit; + + /* The device sends MIDI data in 9-byte packets. */ + if (urb->actual_length == 9) { + u8 *buf = urb->transfer_buffer; + + /* Ignore pure 0xFD padding packets */ + if (buf[0] == 0xfd && buf[1] == 0xfd) + goto resubmit; + + if (tascam->midi_in_state == MIDI_IN_STATE_WAIT_PACKET_1) { + /* Packet 1 contains the first MIDI byte (status). */ + tascam->midi_in_b0 = buf[1]; + tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_2; + } else { /* MIDI_IN_STATE_WAIT_PACKET_2 */ + /* Packet 2 contains the next two MIDI bytes. */ + u8 msg[3]; + msg[0] = tascam->midi_in_b0; + msg[1] = buf[0]; + msg[2] = buf[1]; + + /* Determine message length from status byte */ + if ((msg[0] >= 0x80 && msg[0] <= 0xbf) || (msg[0] >= 0xe0 && msg[0] <= 0xef) || (msg[0] == 0xf2)) { + snd_rawmidi_receive(tascam->midi_in_substream, msg, 3); + } else if ((msg[0] >= 0xc0 && msg[0] <= 0xdf) || (msg[0] == 0xf3)) { + snd_rawmidi_receive(tascam->midi_in_substream, msg, 2); + } else { /* 1-byte messages like F1, F6, F8-FF */ + snd_rawmidi_receive(tascam->midi_in_substream, msg, 1); + } + + /* Reset state for the next message */ + tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_1; + } + } else if (urb->actual_length > 0) { + dev_warn(tascam->card->dev, "Received unexpected MIDI IN data size: %d\n", urb->actual_length); + } + +resubmit: + if (atomic_read(&tascam->midi_in_active)) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err_ratelimited(tascam->card->dev, "Failed to resubmit MIDI IN URB: %d\n", ret); + } +} + +static int tascam_midi_in_open(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + tascam->midi_in_substream = substream; + tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_1; + return 0; +} + +static int tascam_midi_in_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + int i, err; + + if (up) { + if (atomic_xchg(&tascam->midi_in_active, 1) == 0) { + tascam->midi_in_state = MIDI_IN_STATE_WAIT_PACKET_1; + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + 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); + } + } + } else { + if (atomic_xchg(&tascam->midi_in_active, 0) == 1) { + for (i = 0; i < NUM_MIDI_IN_URBS; i++) + usb_kill_urb(tascam->midi_in_urbs[i]); + } + } +} + +static struct snd_rawmidi_ops tascam_midi_in_ops = { + .open = tascam_midi_in_open, + .close = tascam_midi_in_close, + .trigger = tascam_midi_in_trigger, +}; + +/* --- REWRITTEN MIDI OUTPUT LOGIC --- */ + +/** + * format_tascam_midi_packets - Formats a MIDI message into two 9-byte packets. + * @b0: The first byte of the MIDI message (status). + * @b1: The second byte of the MIDI message (data1). + * @b2: The third byte of the MIDI message (data2). + * @packet1: Destination buffer for the first 9-byte packet. + * @packet2: Destination buffer for the second 9-byte packet. + * + * This helper function creates the two 9-byte packets required by the hardware. + */ +static void format_tascam_midi_packets(u8 b0, u8 b1, u8 b2, u8 *packet1, u8 *packet2) +{ + u8 cin = b0 >> 4; + + /* Packet 1: Header and first MIDI byte */ + memset(packet1, 0xfd, 9); + packet1[0] = (0 << 4) | cin; /* Cable 0, CIN */ + packet1[1] = b0; + packet1[8] = 0x00; + + /* Packet 2: Second and third MIDI bytes */ + memset(packet2, 0xfd, 9); + packet2[0] = b1; + packet2[1] = b2; + packet2[8] = 0x00; +} + +/** + * tascam_midi_out_transmit_byte - Process one byte and enqueue formatted packets. + * @tascam: The driver instance. + * @b: The MIDI byte from the ALSA buffer. + * + * This function implements a state machine to parse a raw MIDI byte stream. + * When a complete message is formed, it's formatted into one or two 9-byte + * hardware packets and placed in the `midi_out_packet_queue`. + */ +static void tascam_midi_out_transmit_byte(struct tascam_card *tascam, u8 b) +{ + u8 packet1[9], packet2[9]; + bool send_two = false; + + /* Helper macro to enqueue a single 9-byte packet */ + #define ENQUEUE_PACKET(p) \ + do { \ + int next_write_ptr = (tascam->midi_out_queue_write_ptr + 1) % MIDI_OUT_PACKET_QUEUE_SIZE; \ + if (next_write_ptr == tascam->midi_out_queue_read_ptr) { \ + dev_warn_ratelimited(tascam->card->dev, "MIDI out queue full, dropping packet.\n"); \ + } else { \ + memcpy(tascam->midi_out_packet_queue[tascam->midi_out_queue_write_ptr], p, 9); \ + tascam->midi_out_queue_write_ptr = next_write_ptr; \ + } \ + } while (0) + + if (b >= 0xf8) { /* System Real-Time messages are single-packet */ + format_tascam_midi_packets(b, 0, 0, packet1, packet2); + ENQUEUE_PACKET(packet1); + return; + } + + if (b >= 0x80) { /* Status byte */ + tascam->midi_out_state.running_status = (b >= 0xf0) ? 0 : b; + tascam->midi_out_state.data[0] = b; + if ((b >= 0xc0 && b <= 0xdf) || b == 0xf1 || b == 0xf3) { + tascam->midi_out_state.state = MIDI_OUT_STATE_1PARAM; + } else if (b == 0xf6) { /* Tune request */ + format_tascam_midi_packets(b, 0, 0, packet1, packet2); + ENQUEUE_PACKET(packet1); + tascam->midi_out_state.state = MIDI_OUT_STATE_UNKNOWN; + } else { /* Note On/Off, Poly Pressure, Control Change, Pitch Bend, Song Position */ + tascam->midi_out_state.state = MIDI_OUT_STATE_2PARAM_1; + } + } else { /* Data byte */ + switch (tascam->midi_out_state.state) { + case MIDI_OUT_STATE_UNKNOWN: + if (tascam->midi_out_state.running_status) { + /* Handle running status: re-process with status byte first */ + tascam_midi_out_transmit_byte(tascam, tascam->midi_out_state.running_status); + tascam_midi_out_transmit_byte(tascam, b); + } + break; /* else, orphaned data byte, ignore */ + case MIDI_OUT_STATE_1PARAM: + format_tascam_midi_packets(tascam->midi_out_state.data[0], b, 0, packet1, packet2); + send_two = true; + tascam->midi_out_state.state = MIDI_OUT_STATE_UNKNOWN; + break; + case MIDI_OUT_STATE_2PARAM_1: + tascam->midi_out_state.data[1] = b; + tascam->midi_out_state.state = MIDI_OUT_STATE_2PARAM_2; + break; + case MIDI_OUT_STATE_2PARAM_2: + format_tascam_midi_packets(tascam->midi_out_state.data[0], tascam->midi_out_state.data[1], b, packet1, packet2); + send_two = true; + /* For running status, go back to waiting for the first data byte */ + tascam->midi_out_state.state = MIDI_OUT_STATE_2PARAM_1; + break; + default: + /* SysEx not fully handled for brevity, but would enqueue here */ + break; + } + } + + if (send_two) { + ENQUEUE_PACKET(packet1); + ENQUEUE_PACKET(packet2); + } +} + +/** + * 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 queue. + */ +static 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) + return; + + 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"); + return; + } + + 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); +} + +/** + * tascam_midi_out_work_handler - Deferred work for sending MIDI data. + * @work: The work_struct instance. + * + * This function runs in a kernel thread context. It has two phases: + * 1. Pull all available bytes from the ALSA buffer and run them through the + * state machine, which enqueues formatted 9-byte packets. + * 2. Dequeue packets one by one and send each in its own URB. + */ +static void tascam_midi_out_work_handler(struct work_struct *work) +{ + struct tascam_card *tascam = container_of(work, struct tascam_card, midi_out_work); + unsigned long flags; + int urb_index; + u8 byte; + + if (!tascam->midi_out_substream || !atomic_read(&tascam->midi_out_active)) + return; + + /* Phase 1: Pull from ALSA and enqueue packets */ + spin_lock_irqsave(&tascam->midi_out_lock, flags); + while (snd_rawmidi_transmit(tascam->midi_out_substream, &byte, 1) == 1) + tascam_midi_out_transmit_byte(tascam, byte); + spin_unlock_irqrestore(&tascam->midi_out_lock, flags); + + /* Phase 2: Dequeue packets and send them in URBs */ + while (atomic_read(&tascam->midi_out_active)) { + struct urb *urb; + + spin_lock_irqsave(&tascam->midi_out_lock, flags); + + /* Check if there is anything to send */ + if (tascam->midi_out_queue_read_ptr == tascam->midi_out_queue_write_ptr) { + spin_unlock_irqrestore(&tascam->midi_out_lock, flags); + return; /* Queue is empty */ + } + + /* Find a free URB */ + urb_index = -1; + for (int 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, completion will reschedule */ + } + + urb = tascam->midi_out_urbs[urb_index]; + + /* Dequeue one 9-byte packet and copy it to the URB */ + memcpy(urb->transfer_buffer, + tascam->midi_out_packet_queue[tascam->midi_out_queue_read_ptr], + 9); + urb->transfer_buffer_length = 9; + tascam->midi_out_queue_read_ptr = (tascam->midi_out_queue_read_ptr + 1) % MIDI_OUT_PACKET_QUEUE_SIZE; + + set_bit(urb_index, &tascam->midi_out_urbs_in_flight); + spin_unlock_irqrestore(&tascam->midi_out_lock, flags); + + 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); + } + } +} + +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 MIDI output state machine. */ + tascam->midi_out_state.state = MIDI_OUT_STATE_UNKNOWN; + tascam->midi_out_state.running_status = 0; + + /* --- NEW: Initialize queue pointers --- */ + tascam->midi_out_queue_read_ptr = 0; + tascam->midi_out_queue_write_ptr = 0; + + return 0; +} + +static int tascam_midi_out_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + int i; + + /* Ensure all pending work is finished and kill active URBs */ + cancel_work_sync(&tascam->midi_out_work); + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) + usb_kill_urb(tascam->midi_out_urbs[i]); +} + +static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + + if (up) { + if (atomic_xchg(&tascam->midi_out_active, 1) == 0) + schedule_work(&tascam->midi_out_work); + } else { + atomic_xchg(&tascam->midi_out_active, 0); + } +} + +static 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. + */ +static int tascam_create_midi(struct tascam_card *tascam) { - struct snd_pcm *pcm; int err; - err = snd_pcm_new(tascam->card, "US144MKII", 0, 1, 1, &pcm); + 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; + + return 0; +} + +static int tascam_create_pcm(struct snd_pcm *pcm) +{ + struct tascam_card *tascam = pcm->private_data; + 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)); @@ -1372,8 +1915,6 @@ static int tascam_create_pcm(struct tascam_card *tascam) if (err < 0) return err; - tascam->pcm = pcm; - pcm->private_data = tascam; 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, @@ -1401,6 +1942,18 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message) snd_pcm_suspend_all(tascam->pcm); cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_out_work); + + if (atomic_read(&tascam->midi_in_active)) { + int i; + for (i = 0; i < NUM_MIDI_IN_URBS; i++) + usb_kill_urb(tascam->midi_in_urbs[i]); + } + if (atomic_read(&tascam->midi_out_active)) { + int i; + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) + usb_kill_urb(tascam->midi_out_urbs[i]); + } return 0; } @@ -1438,6 +1991,18 @@ static int tascam_resume(struct usb_interface *intf) } } + /* Resume MIDI streams if they were active */ + if (atomic_read(&tascam->midi_in_active)) { + int i; + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + err = usb_submit_urb(tascam->midi_in_urbs[i], GFP_KERNEL); + if (err < 0) + dev_err(&intf->dev, "Failed to resume MIDI IN URB %d: %d\n", i, err); + } + } + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); + return 0; } @@ -1448,8 +2013,8 @@ static int tascam_resume(struct usb_interface *intf) * * 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. + * data structure, claims interfaces, sets up the device, creates the PCM, + * MIDI, and control interfaces, and registers the sound card with ALSA. * * Return: 0 on success, or a negative error code on failure. */ @@ -1458,6 +2023,7 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id * struct usb_device *dev = interface_to_usbdev(intf); struct tascam_card *tascam; struct snd_card *card; + struct snd_pcm *pcm; int err; u8 *handshake_buf; @@ -1491,6 +2057,13 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id * tascam->capture_34_source = 1; tascam->current_rate = 0; + /* MIDI initialization */ + atomic_set(&tascam->midi_in_active, 0); + atomic_set(&tascam->midi_out_active, 0); + spin_lock_init(&tascam->midi_out_lock); + INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler); + tascam->midi_out_urbs_in_flight = 0; + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname)); snprintf(card->longname, sizeof(card->longname), "TASCAM US-144MKII (VID:%04x, PID:%04x) at %s", @@ -1530,15 +2103,43 @@ 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] == HANDSHAKE_SUCCESS_VAL) + 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); + } else { + dev_warn(&intf->dev, "Handshake failed (err %d, val 0x%02x), continuing anyway.\n", + err, (unsigned int)(err > 0 ? handshake_buf[0] : 0)); + } kfree(handshake_buf); - err = tascam_create_pcm(tascam); + err = snd_pcm_new(tascam->card, "US144MKII", 0, 1, 1, &pcm); if (err < 0) goto release_iface1_and_free_card; + tascam->pcm = pcm; + pcm->private_data = tascam; + + err = tascam_create_pcm(pcm); + if (err < 0) + goto release_iface1_and_free_card; + + err = tascam_create_midi(tascam); + if (err < 0) + goto release_iface1_and_free_card; + + /* --- NEW: Perform initial configuration to enable MIDI --- */ + /** + * The device's MIDI ports are not active until the full sample rate + * configuration sequence has been sent. We do this at probe time with + * a default rate so that MIDI can be used without first starting an + * audio stream. + */ + dev_info(&intf->dev, "Performing initial configuration for MIDI activation.\n"); + err = us144mkii_configure_device_for_rate(tascam, 44100); + if (err < 0) { + dev_err(&intf->dev, "Initial device configuration failed, MIDI may not work.\n"); + /* Don't fail the whole probe, as audio might still be configured later */ + } else { + tascam->current_rate = 44100; + } if (device_create_file(&intf->dev, &dev_attr_driver_version)) dev_warn(&intf->dev, "Could not create sysfs attribute for driver version\n"); @@ -1586,6 +2187,7 @@ static void tascam_disconnect(struct usb_interface *intf) snd_card_disconnect(tascam->card); cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_out_work); if (tascam->iface1) { usb_set_intfdata(tascam->iface1, NULL); diff --git a/working midi out b/working midi out new file mode 100644 index 0000000..1de9eb4 --- /dev/null +++ b/working midi out @@ -0,0 +1,2081 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 serifpersia +/* + * ALSA Driver for TASCAM US-144MKII Audio Interface + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("serifpersia "); +MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); +MODULE_LICENSE("GPL v2"); + +#define DRIVER_NAME "us144mkii" +#define DRIVER_VERSION "2.3" // Version bump for MIDI freeze fix + +/* --- Module Parameters --- */ +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; + +/* --- 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) +#define UAC_SET_CUR 0x01 +#define UAC_GET_CUR 0x81 +#define UAC_SAMPLING_FREQ_CONTROL 0x0100 +#define VENDOR_REQ_REGISTER_WRITE 0x41 +#define VENDOR_REQ_MODE_CONTROL 0x49 +#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 +#define REG_ADDR_RATE_44100 0x1000 +#define REG_ADDR_RATE_48000 0x1002 +#define REG_ADDR_RATE_88200 0x1008 +#define REG_ADDR_RATE_96000 0x100a +#define 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 8 +#define MIDI_IN_BUF_SIZE 64 +#define MIDI_OUT_BUF_SIZE 64 +#define NUM_MIDI_OUT_URBS 8 +#define USB_CTRL_TIMEOUT_MS 1000 + +/* --- 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 + +/* --- Main Driver Data Structure --- */ +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; + volatile 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; + + /* 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 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; /* bitmask */ + 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; +}; + +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 void tascam_midi_in_urb_complete(struct urb *urb); +static void tascam_midi_out_urb_complete(struct urb *urb); +static void tascam_midi_out_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); +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 --- */ +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); + +/* --- ALSA Control Definitions --- */ +static const char * const playback_source_texts[] = {"Playback 1-2", "Playback 3-4"}; +static const char * const capture_source_texts[] = {"Analog In", "Digital In"}; + +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; +} + +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 *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 OUTPUTS 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 *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 *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 OUTPUTS 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; +} + +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 *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 = "ch1 and ch2 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 *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 *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 = "ch3 and ch4 Source", + .info = tascam_capture_source_info, .get = tascam_capture_34_get, .put = tascam_capture_34_put, +}; + +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; +} + +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; +} + +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, +}; + +/** + * process_playback_routing_us144mkii + * @tascam: The driver instance. + * @src_buffer: Buffer containing 4 channels of S24_3LE audio from ALSA. + * @dst_buffer: Buffer to be filled with 4 channels of S24_3LE audio for the USB device. + * @frames: Number of frames to process. + */ +static 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 + * @tascam: The driver instance. + * @decoded_block: Buffer containing 4 channels of S32LE decoded audio from device. + * @routed_block: Buffer to be filled with 4 channels of S32LE audio for ALSA. + */ +static 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 + } + } +} + +/* --- Rate-to-Packet Fixing Data (Verified) --- */ +static const unsigned int patterns_48khz[5][8] = { + {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}, + {7, 6, 6, 6, 7, 6, 6, 6} +}; +static const unsigned int patterns_96khz[5][8] = { + {11, 12, 12, 12, 11, 12, 12, 12}, {11, 12, 12, 12, 12, 12, 12, 12}, + {12, 12, 12, 12, 12, 12, 12, 12}, {13, 12, 12, 12, 12, 12, 12, 12}, + {13, 12, 12, 12, 13, 12, 12, 12} +}; +static const unsigned int patterns_88khz[5][8] = { + {10, 11, 11, 11, 10, 11, 11, 11}, {10, 11, 11, 11, 11, 11, 11, 11}, + {11, 11, 11, 11, 11, 11, 11, 11}, {12, 11, 11, 11, 11, 11, 11, 11}, + {12, 11, 11, 11, 12, 11, 11, 11} +}; +static const unsigned int patterns_44khz[5][8] = { + {5, 5, 5, 6, 5, 5, 5, 6}, {5, 5, 6, 5, 5, 6, 5, 6}, + {5, 6, 5, 6, 5, 6, 5, 6}, {6, 5, 6, 6, 5, 6, 5, 6}, + {6, 6, 6, 5, 6, 6, 6, 5} +}; + +static 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, +}; + +/** + * 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. + */ +static void tascam_free_urbs(struct tascam_card *tascam) +{ + int i; + + for (i = 0; i < NUM_PLAYBACK_URBS; i++) { + if (tascam->playback_urbs[i]) { + usb_kill_urb(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; + } + } + + for (i = 0; i < NUM_FEEDBACK_URBS; i++) { + if (tascam->feedback_urbs[i]) { + usb_kill_urb(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; + } + } + + 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; + } + } + + /* MIDI URB and buffer freeing */ + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + if (tascam->midi_in_urbs[i]) { + usb_kill_urb(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; + } + } + + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) { + if (tascam->midi_out_urbs[i]) { + usb_kill_urb(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. + */ +static 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; +} + +static int tascam_playback_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + int err = 0; + + substream->runtime->hw = tascam_pcm_hw; + tascam->playback_substream = substream; + atomic_set(&tascam->playback_active, 0); + + if (!tascam->capture_substream) { + err = tascam_alloc_urbs(tascam); + if (err < 0) + return err; + } + return 0; +} + +static int tascam_capture_open(struct snd_pcm_substream *substream) +{ + struct tascam_card *tascam = snd_pcm_substream_chip(substream); + 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; + u8 *rate_payload_buf; + u16 rate_vendor_wValue; + int err = 0; + + 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}; + const u8 *current_payload_src; + + 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; +} + +static 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 = 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 (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; +} + +static int tascam_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(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; + 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; /* Lowest latency */ + + 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; +} + +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); + 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++) { + err = usb_submit_urb(tascam->feedback_urbs[i], GFP_ATOMIC); + 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 start_rollback; + atomic_inc(&tascam->active_urbs); + } + 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 err; +} + +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; +} + +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; +} + +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_pcm_hw_params, + .hw_free = tascam_pcm_hw_free, + .prepare = tascam_capture_prepare, + .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; + 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) + 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; + 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; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + 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 *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) + dev_err_ratelimited(tascam->card->dev, "Feedback URB failed: %d\n", urb->status); + return; + } + if (!tascam || !atomic_read(&tascam->playback_active)) + return; + + playback_ss = tascam->playback_substream; + if (!playback_ss || !playback_ss->runtime) + return; + 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 > 10) { + dev_warn_ratelimited(tascam->card->dev, "Feedback sync lost! (value: %u, errors: %u)\n", + feedback_value, tascam->feedback_consecutive_errors); + tascam->feedback_synced = false; + } + } + 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; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + 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; + 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) { + // Get a pointer to the start of the current frame in the ALSA buffer + u8 *dst_frame_start = runtime->dma_area + frames_to_bytes(runtime, tascam->driver_capture_pos); + // Get a pointer to the start of the current routed frame (which contains 4 s32 channels) + s32 *routed_frame_start = routed_block + (f * NUM_CHANNELS); + int c; + + for (c = 0; c < NUM_CHANNELS; c++) { + // Pointer to the destination for the current channel (3 bytes) + u8 *dst_channel = dst_frame_start + (c * BYTES_PER_SAMPLE); + // Pointer to the source s32 for the current channel + s32 *src_channel_s32 = routed_frame_start + c; + + // The s32 sample is formatted as [0x00, LSB, Mid, MSB]. + // We need to copy the 3 significant bytes, skipping the first 0x00 byte. + memcpy(dst_channel, ((char *)src_channel_s32) + 1, 3); + } + + // Advance the driver's position in the ALSA buffer + 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. + */ +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); +} + +/* --- ALSA RawMIDI Implementation --- */ + +/* + * FIX: Define the USB MIDI Code Index Number (CIN) to message length mapping. + * This was previously in the unexported header. + * This is a standard table from the USB MIDI 1.0 specification. + */ +static const u8 snd_usb_midi_cin_lengths[] = { + 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 +}; + +/** + * parse_midi_in_packets - Parses standard 4-byte USB MIDI packets from the device. + * @substream: The ALSA rawmidi input substream to receive data. + * @buf: The buffer containing raw data from the USB endpoint. + * @len: The length of the valid data in the buffer. + * + * This function iterates through the buffer, interpreting each 4-byte chunk + * as a standard USB MIDI packet, and forwards the parsed 1- to 3-byte MIDI + * message to the ALSA core. + */ +static void parse_midi_in_packets(struct snd_rawmidi_substream *substream, u8 *buf, int len) +{ + int i; + for (i = 0; i <= len - 4; i += 4) { + u8 cin = buf[i] & 0x0f; + int msg_len = snd_usb_midi_cin_lengths[cin]; + + if (msg_len == 0) + continue; + + // The cable number is in buf[i] >> 4. This device only has one port, so we ignore it. + snd_rawmidi_receive(substream, &buf[i + 1], msg_len); + } +} + +/** + * tascam_midi_in_urb_complete - Completion handler for MIDI IN bulk URBs. + * @urb: The completed URB. + * + * This function runs in interrupt context. It passes the received data to the + * parser and immediately resubmits the URB to continuously listen for more + * MIDI data. + */ +static void tascam_midi_in_urb_complete(struct urb *urb) +{ + struct tascam_card *tascam = urb->context; + int ret; + + if (urb->status || !tascam || !atomic_read(&tascam->midi_in_active)) + return; + + if (tascam->midi_in_substream && urb->actual_length > 0) + parse_midi_in_packets(tascam->midi_in_substream, urb->transfer_buffer, urb->actual_length); + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err_ratelimited(tascam->card->dev, "Failed to resubmit MIDI IN URB: %d\n", ret); +} + +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; +} + +static int tascam_midi_in_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + int i, err; + + if (up) { + if (atomic_xchg(&tascam->midi_in_active, 1) == 0) { + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + 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); + } + } + } else { + if (atomic_xchg(&tascam->midi_in_active, 0) == 1) { + for (i = 0; i < NUM_MIDI_IN_URBS; i++) + usb_kill_urb(tascam->midi_in_urbs[i]); + } + } +} + +static 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. + */ +static 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) + return; + + 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"); + return; + } + + 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); +} + +/** + * tascam_midi_out_work_handler - Deferred work for sending MIDI data. + * @work: The work_struct instance. + * + * This function runs in a kernel thread context. It checks if the output URB + * is available, then pulls MIDI bytes from the ALSA buffer, manually packing + * them into standard 4-byte USB MIDI packets. This implementation is self-contained + * and does not rely on non-exported kernel headers. It correctly handles + * running status and various message types. + */ +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; + unsigned long flags; + int urb_index; + bool more_data; + + if (!substream || !atomic_read(&tascam->midi_out_active)) + return; + +start_work: + spin_lock_irqsave(&tascam->midi_out_lock, flags); + + urb_index = -1; + for (int 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 */ + } + + struct urb *urb = tascam->midi_out_urbs[urb_index]; + u8 *buf = urb->transfer_buffer; + int bytes_to_send = 0; + + more_data = (snd_rawmidi_transmit_peek(substream, &((u8 *)buf)[0], 1) == 1); + + while (bytes_to_send <= MIDI_OUT_BUF_SIZE - 4 && more_data) { + u8 b[3] = {0, 0, 0}; + u8 cin; + int len; + + /* We peeked before, so this should succeed */ + if (snd_rawmidi_transmit(substream, &b[0], 1) != 1) + break; + + if (b[0] >= 0x80) { /* Status byte */ + if (b[0] >= 0xf8) { /* System Real-Time */ + cin = 0x0f; len = 1; + } else if (b[0] >= 0xf0) { /* System Common */ + tascam->midi_running_status = 0; + switch (b[0]) { + case 0xf0: cin = 0x04; len = 3; break; /* SysEx start */ + case 0xf1: cin = 0x02; len = 2; break; /* MTC Qtr Frame */ + case 0xf2: cin = 0x03; len = 3; break; /* Song Pos */ + case 0xf3: cin = 0x02; len = 2; break; /* Song Select */ + case 0xf6: cin = 0x05; len = 1; break; /* Tune Request */ + case 0xf7: cin = 0x05; len = 1; break; /* EOX (ends with 1 byte) */ + default: continue; /* Undefined */ + } + } else { /* Channel Voice Message */ + tascam->midi_running_status = b[0]; + cin = b[0] >> 4; + switch (cin) { + case 0xc: case 0xd: len = 2; break; + default: len = 3; break; + } + } + /* Fetch data bytes for this new status */ + if (len > 1) { + if (snd_rawmidi_transmit(substream, &b[1], len - 1) != len - 1) + break; + } + } else { /* Data byte (Running Status) */ + u8 status = tascam->midi_running_status; + if (!status) { + dev_warn_ratelimited(tascam->card->dev, "MIDI out: orphaned data byte 0x%02x\n", b[0]); + continue; + } + cin = status >> 4; + switch (cin) { + case 0xc: case 0xd: len = 2; break; + default: len = 3; break; + } + /* Construct message */ + u8 data1 = b[0]; + b[0] = status; + b[1] = data1; + /* Fetch remaining data bytes */ + if (len > 2) { + if (snd_rawmidi_transmit(substream, &b[2], 1) != 1) + break; + } + } + + /* Assemble the 4-byte USB MIDI packet */ + buf[bytes_to_send++] = (0 << 4) | cin; /* Cable 0 */ + buf[bytes_to_send++] = b[0]; + buf[bytes_to_send++] = b[1]; + buf[bytes_to_send++] = b[2]; + + more_data = (snd_rawmidi_transmit_peek(substream, &b[0], 1) == 1); + } + + if (bytes_to_send > 0) { + set_bit(urb_index, &tascam->midi_out_urbs_in_flight); + urb->transfer_buffer_length = bytes_to_send; + spin_unlock_irqrestore(&tascam->midi_out_lock, flags); + + 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); + } + } else { + spin_unlock_irqrestore(&tascam->midi_out_lock, flags); + } + + if (more_data) + goto start_work; /* Try to fill another URB immediately */ +} + +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; +} + +static int tascam_midi_out_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream) +{ + struct tascam_card *tascam = substream->rmidi->private_data; + int i; + + cancel_work_sync(&tascam->midi_out_work); + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) + usb_kill_urb(tascam->midi_out_urbs[i]); +} + +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); + } +} + +static 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. + */ +static 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; + + return 0; +} + +static int tascam_create_pcm(struct snd_pcm *pcm) +{ + struct tascam_card *tascam = pcm->private_data; + 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; + + 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); + return 0; +} + +static void tascam_card_private_free(struct snd_card *card) +{ + struct tascam_card *tascam = card->private_data; + if (tascam && tascam->dev) { + usb_put_dev(tascam->dev); + tascam->dev = NULL; + } +} + +static int tascam_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct tascam_card *tascam = usb_get_intfdata(intf); + + if (!tascam || !tascam->pcm) + return 0; + + snd_pcm_suspend_all(tascam->pcm); + cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_out_work); + + if (atomic_read(&tascam->midi_in_active)) { + int i; + for (i = 0; i < NUM_MIDI_IN_URBS; i++) + usb_kill_urb(tascam->midi_in_urbs[i]); + } + if (atomic_read(&tascam->midi_out_active)) { + int i; + for (i = 0; i < NUM_MIDI_OUT_URBS; i++) + usb_kill_urb(tascam->midi_out_urbs[i]); + } + + return 0; +} + +static int tascam_resume(struct usb_interface *intf) +{ + struct tascam_card *tascam = usb_get_intfdata(intf); + struct usb_device *dev; + int err; + + if (!tascam) + return 0; + + dev = tascam->dev; + dev_info(&intf->dev, "Resuming and re-initializing device...\n"); + + 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); + return err; + } + err = usb_set_interface(dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, "Resume: Set Alt Setting on Intf 1 failed: %d\n", err); + return err; + } + + 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); + if (err < 0) { + dev_err(&intf->dev, "Resume: Failed to restore sample rate configuration\n"); + tascam->current_rate = 0; + return err; + } + } + + /* Resume MIDI streams if they were active */ + if (atomic_read(&tascam->midi_in_active)) { + int i; + for (i = 0; i < NUM_MIDI_IN_URBS; i++) { + err = usb_submit_urb(tascam->midi_in_urbs[i], GFP_KERNEL); + if (err < 0) + dev_err(&intf->dev, "Failed to resume MIDI IN URB %d: %d\n", i, err); + } + } + if (atomic_read(&tascam->midi_out_active)) + schedule_work(&tascam->midi_out_work); + + 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, + * MIDI, 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); + struct tascam_card *tascam; + struct snd_card *card; + struct snd_pcm *pcm; + int err; + u8 *handshake_buf; + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + if (dev_idx >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev_idx]) { + dev_idx++; + return -ENOENT; + } + + err = snd_card_new(&intf->dev, index[dev_idx], id[dev_idx], THIS_MODULE, + sizeof(struct tascam_card), &card); + if (err < 0) + return err; + + tascam = card->private_data; + tascam->card = card; + tascam->dev = usb_get_dev(dev); + tascam->iface0 = intf; + 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->line_out_source = 0; + tascam->digital_out_source = 1; + tascam->capture_12_source = 0; + tascam->capture_34_source = 1; + tascam->current_rate = 0; + + /* MIDI initialization */ + atomic_set(&tascam->midi_in_active, 0); + atomic_set(&tascam->midi_out_active, 0); + spin_lock_init(&tascam->midi_out_lock); + INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler); + tascam->midi_out_urbs_in_flight = 0; + + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), "TASCAM US-144MKII (VID:%04x, PID:%04x) at %s", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), + dev->bus->bus_name); + + tascam->iface1 = usb_ifnum_to_if(dev, 1); + if (!tascam->iface1) { + dev_err(&intf->dev, "Interface 1 not found.\n"); + err = -ENODEV; + goto free_card_obj; + } + err = usb_driver_claim_interface(&tascam_alsa_driver, tascam->iface1, tascam); + if (err < 0) { + dev_err(&intf->dev, "Could not claim interface 1: %d\n", err); + tascam->iface1 = NULL; + goto free_card_obj; + } + + err = usb_set_interface(dev, 0, 1); + if (err < 0) { + dev_err(&intf->dev, "Set Alt Setting on Intf 0 failed: %d\n", err); + goto release_iface1_and_free_card; + } + err = usb_set_interface(dev, 1, 1); + if (err < 0) { + dev_err(&intf->dev, "Set Alt Setting on Intf 1 failed: %d\n", err); + goto release_iface1_and_free_card; + } + + handshake_buf = kmalloc(1, GFP_KERNEL); + if (!handshake_buf) { + err = -ENOMEM; + goto release_iface1_and_free_card; + } + 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] == HANDSHAKE_SUCCESS_VAL) { + dev_info(&intf->dev, "Handshake successful.\n"); + } else { + /* FIX: Cast argument to unsigned int to fix format warning */ + dev_warn(&intf->dev, "Handshake failed (err %d, val 0x%02x), continuing anyway.\n", + err, (unsigned int)(err > 0 ? handshake_buf[0] : 0)); + } + kfree(handshake_buf); + + err = snd_pcm_new(tascam->card, "US144MKII", 0, 1, 1, &pcm); + if (err < 0) + goto release_iface1_and_free_card; + tascam->pcm = pcm; + pcm->private_data = tascam; + + err = tascam_create_pcm(pcm); + if (err < 0) + goto release_iface1_and_free_card; + + err = tascam_create_midi(tascam); + if (err < 0) + goto release_iface1_and_free_card; + + if (device_create_file(&intf->dev, &dev_attr_driver_version)) + dev_warn(&intf->dev, "Could not create sysfs attribute for driver version\n"); + + err = snd_card_register(card); + if (err < 0) + goto release_iface1_and_free_card; + + dev_info(&intf->dev, "TASCAM US-144MKII driver initialized.\n"); + dev_idx++; + return 0; + +release_iface1_and_free_card: + if (tascam->iface1) { + usb_set_intfdata(tascam->iface1, NULL); + usb_driver_release_interface(&tascam_alsa_driver, tascam->iface1); + tascam->iface1 = NULL; + } +free_card_obj: + snd_card_free(card); + 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); + + if (!tascam) + return; + + device_remove_file(&intf->dev, &dev_attr_driver_version); + + if (intf != tascam->iface0) + return; + + dev_info(&intf->dev, "TASCAM US-144MKII disconnecting...\n"); + snd_card_disconnect(tascam->card); + + cancel_work_sync(&tascam->capture_work); + cancel_work_sync(&tascam->midi_out_work); + + if (tascam->iface1) { + usb_set_intfdata(tascam->iface1, NULL); + usb_driver_release_interface(&tascam_alsa_driver, tascam->iface1); + tascam->iface1 = NULL; + } + + if (dev_idx > 0) + dev_idx--; + + snd_card_free_when_closed(tascam->card); +} + +static const struct usb_device_id tascam_id_table[] = { + { USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) }, + { } +}; +MODULE_DEVICE_TABLE(usb, tascam_id_table); + +static struct usb_driver tascam_alsa_driver = { + .name = DRIVER_NAME, + .probe = tascam_probe, + .disconnect = tascam_disconnect, + .id_table = tascam_id_table, + .suspend = tascam_suspend, + .resume = tascam_resume, +}; + +module_usb_driver(tascam_alsa_driver);