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