| /* | |
| * 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; | |
| } |