diff options
Diffstat (limited to 'kernel/sound/usb/6fire/midi.c')
-rw-r--r-- | kernel/sound/usb/6fire/midi.c | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/kernel/sound/usb/6fire/midi.c b/kernel/sound/usb/6fire/midi.c new file mode 100644 index 000000000..3d4109695 --- /dev/null +++ b/kernel/sound/usb/6fire/midi.c @@ -0,0 +1,218 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Rawmidi driver + * + * Author: Torsten Schenk <torsten.schenk@zoho.com> + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <sound/rawmidi.h> + +#include "midi.h" +#include "chip.h" +#include "comm.h" + +enum { + MIDI_BUFSIZE = 64 +}; + +static void usb6fire_midi_out_handler(struct urb *urb) +{ + struct midi_runtime *rt = urb->context; + int ret; + unsigned long flags; + + spin_lock_irqsave(&rt->out_lock, flags); + + if (rt->out) { + ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4, + MIDI_BUFSIZE - 4); + if (ret > 0) { /* more data available, send next packet */ + rt->out_buffer[1] = ret + 2; + rt->out_buffer[3] = rt->out_serial++; + urb->transfer_buffer_length = ret + 4; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&urb->dev->dev, + "midi out urb submit failed: %d\n", + ret); + } else /* no more data to transmit */ + rt->out = NULL; + } + spin_unlock_irqrestore(&rt->out_lock, flags); +} + +static void usb6fire_midi_in_received( + struct midi_runtime *rt, u8 *data, int length) +{ + unsigned long flags; + + spin_lock_irqsave(&rt->in_lock, flags); + if (rt->in) + snd_rawmidi_receive(rt->in, data, length); + spin_unlock_irqrestore(&rt->in_lock, flags); +} + +static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static void usb6fire_midi_out_trigger( + struct snd_rawmidi_substream *alsa_sub, int up) +{ + struct midi_runtime *rt = alsa_sub->rmidi->private_data; + struct urb *urb = &rt->out_urb; + __s8 ret; + unsigned long flags; + + spin_lock_irqsave(&rt->out_lock, flags); + if (up) { /* start transfer */ + if (rt->out) { /* we are already transmitting so just return */ + spin_unlock_irqrestore(&rt->out_lock, flags); + return; + } + + ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4, + MIDI_BUFSIZE - 4); + if (ret > 0) { + rt->out_buffer[1] = ret + 2; + rt->out_buffer[3] = rt->out_serial++; + urb->transfer_buffer_length = ret + 4; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&urb->dev->dev, + "midi out urb submit failed: %d\n", + ret); + else + rt->out = alsa_sub; + } + } else if (rt->out == alsa_sub) + rt->out = NULL; + spin_unlock_irqrestore(&rt->out_lock, flags); +} + +static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub) +{ + struct midi_runtime *rt = alsa_sub->rmidi->private_data; + int retry = 0; + + while (rt->out && retry++ < 100) + msleep(10); +} + +static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static void usb6fire_midi_in_trigger( + struct snd_rawmidi_substream *alsa_sub, int up) +{ + struct midi_runtime *rt = alsa_sub->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&rt->in_lock, flags); + if (up) + rt->in = alsa_sub; + else + rt->in = NULL; + spin_unlock_irqrestore(&rt->in_lock, flags); +} + +static struct snd_rawmidi_ops out_ops = { + .open = usb6fire_midi_out_open, + .close = usb6fire_midi_out_close, + .trigger = usb6fire_midi_out_trigger, + .drain = usb6fire_midi_out_drain +}; + +static struct snd_rawmidi_ops in_ops = { + .open = usb6fire_midi_in_open, + .close = usb6fire_midi_in_close, + .trigger = usb6fire_midi_in_trigger +}; + +int usb6fire_midi_init(struct sfire_chip *chip) +{ + int ret; + struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime), + GFP_KERNEL); + struct comm_runtime *comm_rt = chip->comm; + + if (!rt) + return -ENOMEM; + + rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL); + if (!rt->out_buffer) { + kfree(rt); + return -ENOMEM; + } + + rt->chip = chip; + rt->in_received = usb6fire_midi_in_received; + rt->out_buffer[0] = 0x80; /* 'send midi' command */ + rt->out_buffer[1] = 0x00; /* size of data */ + rt->out_buffer[2] = 0x00; /* always 0 */ + spin_lock_init(&rt->in_lock); + spin_lock_init(&rt->out_lock); + + comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt, + usb6fire_midi_out_handler); + + ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance); + if (ret < 0) { + kfree(rt->out_buffer); + kfree(rt); + dev_err(&chip->dev->dev, "unable to create midi.\n"); + return ret; + } + rt->instance->private_data = rt; + strcpy(rt->instance->name, "DMX6FireUSB MIDI"); + rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT, + &out_ops); + snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT, + &in_ops); + + chip->midi = rt; + return 0; +} + +void usb6fire_midi_abort(struct sfire_chip *chip) +{ + struct midi_runtime *rt = chip->midi; + + if (rt) + usb_poison_urb(&rt->out_urb); +} + +void usb6fire_midi_destroy(struct sfire_chip *chip) +{ + struct midi_runtime *rt = chip->midi; + + kfree(rt->out_buffer); + kfree(rt); + chip->midi = NULL; +} |