initial midi(not functional)
This commit is contained in:
parent
fe3d9beea6
commit
7284fb4c42
|
|
@ -0,0 +1,266 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#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; i<NUM_AUDIO_TRANSFERS; i++) if(audio_transfers[i]) { if (audio_transfers[i]->buffer) free(audio_transfers[i]->buffer); libusb_free_transfer(audio_transfers[i]); }
|
||||
for (int i=0; i<NUM_MIDI_IN_TRANSFERS; i++) if(midi_in_transfers[i]) { if (midi_in_transfers[i]->buffer) 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; i<len; i++) printf(" %02x", buf[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void LIBUSB_CALL midi_in_callback(struct libusb_transfer *transfer) {
|
||||
if (transfer->status == 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;
|
||||
}
|
||||
638
us144mkii.c
638
us144mkii.c
|
|
@ -2,21 +2,28 @@
|
|||
// Copyright (c) 2025 serifpersia <ramiserifpersia@gmail.com>
|
||||
/*
|
||||
* 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 <linux/usb.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/rawmidi.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/control.h>
|
||||
#include <linux/printk.h>
|
||||
|
||||
MODULE_AUTHOR("serifpersia <ramiserifpersia@gmail.com>");
|
||||
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) {
|
||||
/* 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);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue