Merge pull request #2 from serifpersia/wip-midi
Full MIDI IN/OUT Implementation
This commit is contained in:
commit
271a403da9
493
us144mkii.c
493
us144mkii.c
|
|
@ -6,8 +6,10 @@
|
||||||
|
|
||||||
#include <linux/usb.h>
|
#include <linux/usb.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
#include <sound/core.h>
|
#include <sound/core.h>
|
||||||
#include <sound/pcm.h>
|
#include <sound/pcm.h>
|
||||||
|
#include <sound/rawmidi.h>
|
||||||
#include <sound/initval.h>
|
#include <sound/initval.h>
|
||||||
#include <sound/control.h>
|
#include <sound/control.h>
|
||||||
|
|
||||||
|
|
@ -16,7 +18,7 @@ MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII");
|
||||||
MODULE_LICENSE("GPL v2");
|
MODULE_LICENSE("GPL v2");
|
||||||
|
|
||||||
#define DRIVER_NAME "us144mkii"
|
#define DRIVER_NAME "us144mkii"
|
||||||
#define DRIVER_VERSION "1.5"
|
#define DRIVER_VERSION "1.6"
|
||||||
|
|
||||||
/* --- Module Parameters --- */
|
/* --- Module Parameters --- */
|
||||||
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
|
||||||
|
|
@ -68,6 +70,10 @@ static int dev_idx;
|
||||||
#define NUM_CAPTURE_URBS 8
|
#define NUM_CAPTURE_URBS 8
|
||||||
#define CAPTURE_URB_SIZE 512
|
#define CAPTURE_URB_SIZE 512
|
||||||
#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4)
|
#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
|
||||||
|
#define NUM_MIDI_OUT_URBS 4
|
||||||
#define USB_CTRL_TIMEOUT_MS 1000
|
#define USB_CTRL_TIMEOUT_MS 1000
|
||||||
|
|
||||||
/* --- Audio Format Configuration --- */
|
/* --- Audio Format Configuration --- */
|
||||||
|
|
@ -89,6 +95,7 @@ struct tascam_card {
|
||||||
struct usb_interface *iface1;
|
struct usb_interface *iface1;
|
||||||
struct snd_card *card;
|
struct snd_card *card;
|
||||||
struct snd_pcm *pcm;
|
struct snd_pcm *pcm;
|
||||||
|
struct snd_rawmidi *rmidi;
|
||||||
|
|
||||||
/* Playback stream */
|
/* Playback stream */
|
||||||
struct snd_pcm_substream *playback_substream;
|
struct snd_pcm_substream *playback_substream;
|
||||||
|
|
@ -118,6 +125,21 @@ struct tascam_card {
|
||||||
s32 *capture_routing_buffer;
|
s32 *capture_routing_buffer;
|
||||||
struct work_struct capture_work;
|
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;
|
||||||
|
spinlock_t midi_out_lock;
|
||||||
|
u8 midi_running_status;
|
||||||
|
/* State for proprietary MIDI input protocol */
|
||||||
|
u8 midi_in_pending_packet[9];
|
||||||
|
bool midi_in_has_pending_packet;
|
||||||
|
|
||||||
/* Shared state & Routing Matrix */
|
/* Shared state & Routing Matrix */
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
atomic_t active_urbs;
|
atomic_t active_urbs;
|
||||||
|
|
@ -146,6 +168,9 @@ static void playback_urb_complete(struct urb *urb);
|
||||||
static void feedback_urb_complete(struct urb *urb);
|
static void feedback_urb_complete(struct urb *urb);
|
||||||
static void capture_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_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 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 int tascam_probe(struct usb_interface *intf, const struct usb_device_id *id);
|
||||||
static void tascam_disconnect(struct usb_interface *intf);
|
static void tascam_disconnect(struct usb_interface *intf);
|
||||||
|
|
@ -435,8 +460,9 @@ static const struct snd_pcm_hardware tascam_pcm_hw = {
|
||||||
* tascam_free_urbs - Free all allocated URBs and associated buffers.
|
* tascam_free_urbs - Free all allocated URBs and associated buffers.
|
||||||
* @tascam: the tascam_card instance.
|
* @tascam: the tascam_card instance.
|
||||||
*
|
*
|
||||||
* This function kills, unlinks, and frees all playback, feedback, and capture
|
* This function kills, unlinks, and frees all playback, feedback, capture,
|
||||||
* URBs, along with their transfer buffers and the capture ring/decode buffers.
|
* and MIDI URBs, along with their transfer buffers and the capture
|
||||||
|
* ring/decode buffers.
|
||||||
*/
|
*/
|
||||||
static void tascam_free_urbs(struct tascam_card *tascam)
|
static void tascam_free_urbs(struct tascam_card *tascam)
|
||||||
{
|
{
|
||||||
|
|
@ -475,11 +501,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);
|
kfree(tascam->playback_routing_buffer);
|
||||||
tascam->playback_routing_buffer = NULL;
|
tascam->playback_routing_buffer = NULL;
|
||||||
kfree(tascam->capture_routing_buffer);
|
kfree(tascam->capture_routing_buffer);
|
||||||
tascam->capture_routing_buffer = NULL;
|
tascam->capture_routing_buffer = NULL;
|
||||||
kfree(tascam->capture_decode_dst_block);
|
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 +541,7 @@ static void tascam_free_urbs(struct tascam_card *tascam)
|
||||||
* @tascam: the tascam_card instance.
|
* @tascam: the tascam_card instance.
|
||||||
*
|
*
|
||||||
* This function allocates and initializes all URBs for playback, feedback,
|
* 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.
|
* Return: 0 on success, or a negative error code on failure.
|
||||||
*/
|
*/
|
||||||
|
|
@ -560,6 +614,38 @@ static int tascam_alloc_urbs(struct tascam_card *tascam)
|
||||||
c_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
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);
|
tascam->capture_ring_buffer = kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL);
|
||||||
if (!tascam->capture_ring_buffer)
|
if (!tascam->capture_ring_buffer)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
@ -591,34 +677,22 @@ error:
|
||||||
static int tascam_playback_open(struct snd_pcm_substream *substream)
|
static int tascam_playback_open(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
substream->runtime->hw = tascam_pcm_hw;
|
substream->runtime->hw = tascam_pcm_hw;
|
||||||
tascam->playback_substream = substream;
|
tascam->playback_substream = substream;
|
||||||
atomic_set(&tascam->playback_active, 0);
|
atomic_set(&tascam->playback_active, 0);
|
||||||
|
|
||||||
if (!tascam->capture_substream) {
|
|
||||||
err = tascam_alloc_urbs(tascam);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tascam_capture_open(struct snd_pcm_substream *substream)
|
static int tascam_capture_open(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
substream->runtime->hw = tascam_pcm_hw;
|
substream->runtime->hw = tascam_pcm_hw;
|
||||||
tascam->capture_substream = substream;
|
tascam->capture_substream = substream;
|
||||||
atomic_set(&tascam->capture_active, 0);
|
atomic_set(&tascam->capture_active, 0);
|
||||||
|
|
||||||
if (!tascam->playback_substream) {
|
|
||||||
err = tascam_alloc_urbs(tascam);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -626,8 +700,7 @@ static int tascam_playback_close(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||||
tascam->playback_substream = NULL;
|
tascam->playback_substream = NULL;
|
||||||
if (!tascam->capture_substream)
|
|
||||||
tascam_free_urbs(tascam);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -635,8 +708,7 @@ static int tascam_capture_close(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
|
||||||
tascam->capture_substream = NULL;
|
tascam->capture_substream = NULL;
|
||||||
if (!tascam->playback_substream)
|
|
||||||
tascam_free_urbs(tascam);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -780,7 +852,7 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream)
|
||||||
for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++)
|
for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++)
|
||||||
tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet;
|
tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet;
|
||||||
|
|
||||||
feedback_packets = 1; /* Lowest latency */
|
feedback_packets = 1;
|
||||||
|
|
||||||
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
|
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
|
||||||
struct urb *f_urb = tascam->feedback_urbs[i];
|
struct urb *f_urb = tascam->feedback_urbs[i];
|
||||||
|
|
@ -1350,15 +1422,315 @@ static void capture_urb_complete(struct urb *urb)
|
||||||
dev_err_ratelimited(tascam->card->dev, "Failed to resubmit capture URB: %d\n", ret);
|
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
|
||||||
|
* @urb: The completed URB.
|
||||||
|
* each 9-byte USB packet contains a variable-length MIDI message fragment.
|
||||||
|
* All 0xFD bytes are padding and must be stripped.
|
||||||
|
*/
|
||||||
|
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_err(tascam->card->dev, "MIDI IN URB failed: status %d\n", urb->status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tascam || !atomic_read(&tascam->midi_in_active))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tascam->midi_in_substream && urb->actual_length > 0) {
|
||||||
|
u8 *raw_buf = urb->transfer_buffer;
|
||||||
|
u8 stripped_buf[9]; /* Max possible size is 9 */
|
||||||
|
int i;
|
||||||
|
int stripped_len = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Strip all 0xFD padding bytes from the raw USB packet,
|
||||||
|
* copying the result into a temporary buffer.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < urb->actual_length; i++) {
|
||||||
|
if (raw_buf[i] != 0xfd) {
|
||||||
|
stripped_buf[stripped_len] = raw_buf[i];
|
||||||
|
stripped_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The last byte is often a terminator (0x00, 0xFF, etc.).
|
||||||
|
* If the stripped message is a single byte and it's a terminator,
|
||||||
|
* it's likely an empty keep-alive packet. We can ignore it.
|
||||||
|
* A real single-byte message like Active Sensing (0xFE) will pass.
|
||||||
|
*/
|
||||||
|
if (stripped_len == 1 && stripped_buf[0] < 0x80) {
|
||||||
|
/* This is likely an empty packet, do nothing. */
|
||||||
|
} else if (stripped_len > 0) {
|
||||||
|
/*
|
||||||
|
* The last byte might be a terminator. If so, don't send it.
|
||||||
|
* A real MIDI data byte will never be > 0x7F.
|
||||||
|
* A real status byte will be handled by the ALSA core.
|
||||||
|
* Terminators like 0xFF are ambiguous (System Reset vs terminator).
|
||||||
|
* Let's assume for now that if the last byte is not a valid
|
||||||
|
* data byte for the preceding status, it's a terminator.
|
||||||
|
* A simpler approach is to just check for 0x00 or 0xFF.
|
||||||
|
*/
|
||||||
|
if (stripped_buf[stripped_len - 1] == 0x00 ||
|
||||||
|
stripped_buf[stripped_len - 1] == 0xff) {
|
||||||
|
stripped_len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stripped_len > 0) {
|
||||||
|
snd_rawmidi_receive(tascam->midi_in_substream, stripped_buf, stripped_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||||
|
if (ret < 0)
|
||||||
|
dev_err(tascam->card->dev, "Failed to resubmit MIDI IN URB: error %d\n", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
dev_info(tascam->card->dev, "MIDI IN TRIGGER: START\n");
|
||||||
|
if (atomic_xchg(&tascam->midi_in_active, 1) == 0) {
|
||||||
|
tascam->midi_in_has_pending_packet = false;
|
||||||
|
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 {
|
||||||
|
dev_info(tascam->card->dev, "MIDI IN TRIGGER: STOP\n");
|
||||||
|
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
|
||||||
|
* @work: The work_struct instance.
|
||||||
|
* output protocol: take the raw MIDI
|
||||||
|
* message bytes from the application, place them at the start of a 9-byte
|
||||||
|
* buffer, pad the rest with 0xFD, and add a terminator byte (0x00).
|
||||||
|
* This function pulls as many bytes as will fit into one packet from the
|
||||||
|
* ALSA buffer and sends them.
|
||||||
|
*/
|
||||||
|
static void tascam_midi_out_work_handler(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct tascam_card *tascam = container_of(work, struct tascam_card, midi_out_work);
|
||||||
|
struct snd_rawmidi_substream *substream = tascam->midi_out_substream;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can send up to 8 bytes of MIDI data in one 9-byte packet.
|
||||||
|
* The 9th byte is a terminator.
|
||||||
|
*/
|
||||||
|
bytes_to_send = snd_rawmidi_transmit(substream, buf, 8);
|
||||||
|
|
||||||
|
if (bytes_to_send > 0) {
|
||||||
|
/* Pad the rest of the 9-byte packet with 0xFD */
|
||||||
|
if (bytes_to_send < 9)
|
||||||
|
memset(buf + bytes_to_send, 0xfd, 9 - bytes_to_send);
|
||||||
|
|
||||||
|
/* The last byte is a terminator. 0x00 is a safe choice. */
|
||||||
|
buf[8] = 0x00;
|
||||||
|
|
||||||
|
set_bit(urb_index, &tascam->midi_out_urbs_in_flight);
|
||||||
|
urb->transfer_buffer_length = 9;
|
||||||
|
spin_unlock_irqrestore(&tascam->midi_out_lock, flags);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there's more data, try to fill another URB immediately */
|
||||||
|
more_data = (snd_rawmidi_transmit_peek(substream, &buf[0], 1) == 1);
|
||||||
|
if (more_data)
|
||||||
|
goto start_work;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
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 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)
|
||||||
{
|
{
|
||||||
struct snd_pcm *pcm;
|
|
||||||
int err;
|
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)
|
if (err < 0)
|
||||||
return err;
|
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));
|
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_line_out_control, tascam));
|
||||||
if (err < 0) return err;
|
if (err < 0) return err;
|
||||||
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_digital_out_control, tascam));
|
err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_digital_out_control, tascam));
|
||||||
|
|
@ -1372,8 +1744,6 @@ static int tascam_create_pcm(struct tascam_card *tascam)
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
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_PLAYBACK, &tascam_playback_ops);
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_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,
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
|
|
@ -1401,6 +1771,18 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
|
||||||
|
|
||||||
snd_pcm_suspend_all(tascam->pcm);
|
snd_pcm_suspend_all(tascam->pcm);
|
||||||
cancel_work_sync(&tascam->capture_work);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1438,6 +1820,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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1448,8 +1842,8 @@ static int tascam_resume(struct usb_interface *intf)
|
||||||
*
|
*
|
||||||
* This function is called by the USB core when a device matching the driver's
|
* 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
|
* ID table is connected. It allocates the sound card, initializes the driver
|
||||||
* data structure, claims interfaces, sets up the device, creates the PCM
|
* data structure, claims interfaces, sets up the device, creates the PCM,
|
||||||
* and control interfaces, and registers the sound card with ALSA.
|
* MIDI, and control interfaces, and registers the sound card with ALSA.
|
||||||
*
|
*
|
||||||
* Return: 0 on success, or a negative error code on failure.
|
* Return: 0 on success, or a negative error code on failure.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1458,6 +1852,7 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
|
||||||
struct usb_device *dev = interface_to_usbdev(intf);
|
struct usb_device *dev = interface_to_usbdev(intf);
|
||||||
struct tascam_card *tascam;
|
struct tascam_card *tascam;
|
||||||
struct snd_card *card;
|
struct snd_card *card;
|
||||||
|
struct snd_pcm *pcm;
|
||||||
int err;
|
int err;
|
||||||
u8 *handshake_buf;
|
u8 *handshake_buf;
|
||||||
|
|
||||||
|
|
@ -1491,6 +1886,14 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
|
||||||
tascam->capture_34_source = 1;
|
tascam->capture_34_source = 1;
|
||||||
tascam->current_rate = 0;
|
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;
|
||||||
|
tascam->midi_in_has_pending_packet = false;
|
||||||
|
|
||||||
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
|
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
|
||||||
strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname));
|
strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname));
|
||||||
snprintf(card->longname, sizeof(card->longname), "TASCAM US-144MKII (VID:%04x, PID:%04x) at %s",
|
snprintf(card->longname, sizeof(card->longname), "TASCAM US-144MKII (VID:%04x, PID:%04x) at %s",
|
||||||
|
|
@ -1530,13 +1933,33 @@ 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,
|
err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), VENDOR_REQ_MODE_CONTROL,
|
||||||
RT_D2H_VENDOR_DEV, MODE_VAL_HANDSHAKE_READ, 0x0000,
|
RT_D2H_VENDOR_DEV, MODE_VAL_HANDSHAKE_READ, 0x0000,
|
||||||
handshake_buf, 1, USB_CTRL_TIMEOUT_MS);
|
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");
|
dev_info(&intf->dev, "Handshake successful.\n");
|
||||||
else
|
} else {
|
||||||
dev_warn(&intf->dev, "Handshake failed (err %d, val 0x%02x), continuing anyway.\n", err, err > 0 ? handshake_buf[0] : 0);
|
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);
|
kfree(handshake_buf);
|
||||||
|
|
||||||
err = tascam_create_pcm(tascam);
|
|
||||||
|
/*
|
||||||
|
* Allocate all URBs now that the device is initialized.
|
||||||
|
*/
|
||||||
|
err = tascam_alloc_urbs(tascam);
|
||||||
|
if (err < 0)
|
||||||
|
goto release_iface1_and_free_card;
|
||||||
|
|
||||||
|
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)
|
if (err < 0)
|
||||||
goto release_iface1_and_free_card;
|
goto release_iface1_and_free_card;
|
||||||
|
|
||||||
|
|
@ -1586,6 +2009,12 @@ static void tascam_disconnect(struct usb_interface *intf)
|
||||||
snd_card_disconnect(tascam->card);
|
snd_card_disconnect(tascam->card);
|
||||||
|
|
||||||
cancel_work_sync(&tascam->capture_work);
|
cancel_work_sync(&tascam->capture_work);
|
||||||
|
cancel_work_sync(&tascam->midi_out_work);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free all URBs before freeing the card.
|
||||||
|
*/
|
||||||
|
tascam_free_urbs(tascam);
|
||||||
|
|
||||||
if (tascam->iface1) {
|
if (tascam->iface1) {
|
||||||
usb_set_intfdata(tascam->iface1, NULL);
|
usb_set_intfdata(tascam->iface1, NULL);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue