577 lines
15 KiB
C
577 lines
15 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* Copyright (C) by Paul Barton-Davis 1998-1999
|
||
|
*/
|
||
|
|
||
|
/* The low level driver for the WaveFront ICS2115 MIDI interface(s)
|
||
|
*
|
||
|
* Note that there is also an MPU-401 emulation (actually, a UART-401
|
||
|
* emulation) on the CS4232 on the Tropez and Tropez Plus. This code
|
||
|
* has nothing to do with that interface at all.
|
||
|
*
|
||
|
* The interface is essentially just a UART-401, but is has the
|
||
|
* interesting property of supporting what Turtle Beach called
|
||
|
* "Virtual MIDI" mode. In this mode, there are effectively *two*
|
||
|
* MIDI buses accessible via the interface, one that is routed
|
||
|
* solely to/from the external WaveFront synthesizer and the other
|
||
|
* corresponding to the pin/socket connector used to link external
|
||
|
* MIDI devices to the board.
|
||
|
*
|
||
|
* This driver fully supports this mode, allowing two distinct MIDI
|
||
|
* busses to be used completely independently, giving 32 channels of
|
||
|
* MIDI routing, 16 to the WaveFront synth and 16 to the external MIDI
|
||
|
* bus. The devices are named /dev/snd/midiCnD0 and /dev/snd/midiCnD1,
|
||
|
* where `n' is the card number. Note that the device numbers may be
|
||
|
* something other than 0 and 1 if the CS4232 UART/MPU-401 interface
|
||
|
* is enabled.
|
||
|
*
|
||
|
* Switching between the two is accomplished externally by the driver
|
||
|
* using the two otherwise unused MIDI bytes. See the code for more details.
|
||
|
*
|
||
|
* NOTE: VIRTUAL MIDI MODE IS ON BY DEFAULT (see lowlevel/isa/wavefront.c)
|
||
|
*
|
||
|
* The main reason to turn off Virtual MIDI mode is when you want to
|
||
|
* tightly couple the WaveFront synth with an external MIDI
|
||
|
* device. You won't be able to distinguish the source of any MIDI
|
||
|
* data except via SysEx ID, but thats probably OK, since for the most
|
||
|
* part, the WaveFront won't be sending any MIDI data at all.
|
||
|
*
|
||
|
* The main reason to turn on Virtual MIDI Mode is to provide two
|
||
|
* completely independent 16-channel MIDI buses, one to the
|
||
|
* WaveFront and one to any external MIDI devices. Given the 32
|
||
|
* voice nature of the WaveFront, its pretty easy to find a use
|
||
|
* for all 16 channels driving just that synth.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/time.h>
|
||
|
#include <linux/wait.h>
|
||
|
#include <sound/core.h>
|
||
|
#include <sound/snd_wavefront.h>
|
||
|
|
||
|
static inline int
|
||
|
wf_mpu_status (snd_wavefront_midi_t *midi)
|
||
|
|
||
|
{
|
||
|
return inb (midi->mpu_status_port);
|
||
|
}
|
||
|
|
||
|
static inline int
|
||
|
input_avail (snd_wavefront_midi_t *midi)
|
||
|
|
||
|
{
|
||
|
return !(wf_mpu_status(midi) & INPUT_AVAIL);
|
||
|
}
|
||
|
|
||
|
static inline int
|
||
|
output_ready (snd_wavefront_midi_t *midi)
|
||
|
|
||
|
{
|
||
|
return !(wf_mpu_status(midi) & OUTPUT_READY);
|
||
|
}
|
||
|
|
||
|
static inline int
|
||
|
read_data (snd_wavefront_midi_t *midi)
|
||
|
|
||
|
{
|
||
|
return inb (midi->mpu_data_port);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
write_data (snd_wavefront_midi_t *midi, unsigned char byte)
|
||
|
|
||
|
{
|
||
|
outb (byte, midi->mpu_data_port);
|
||
|
}
|
||
|
|
||
|
static snd_wavefront_midi_t *
|
||
|
get_wavefront_midi (struct snd_rawmidi_substream *substream)
|
||
|
|
||
|
{
|
||
|
struct snd_card *card;
|
||
|
snd_wavefront_card_t *acard;
|
||
|
|
||
|
if (substream == NULL || substream->rmidi == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
card = substream->rmidi->card;
|
||
|
|
||
|
if (card == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
if (card->private_data == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
acard = card->private_data;
|
||
|
|
||
|
return &acard->wavefront.midi;
|
||
|
}
|
||
|
|
||
|
static void snd_wavefront_midi_output_write(snd_wavefront_card_t *card)
|
||
|
{
|
||
|
snd_wavefront_midi_t *midi = &card->wavefront.midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
unsigned long flags;
|
||
|
unsigned char midi_byte;
|
||
|
int max = 256, mask = 1;
|
||
|
int timeout;
|
||
|
|
||
|
/* Its not OK to try to change the status of "virtuality" of
|
||
|
the MIDI interface while we're outputting stuff. See
|
||
|
snd_wavefront_midi_{enable,disable}_virtual () for the
|
||
|
other half of this.
|
||
|
|
||
|
The first loop attempts to flush any data from the
|
||
|
current output device, and then the second
|
||
|
emits the switch byte (if necessary), and starts
|
||
|
outputting data for the output device currently in use.
|
||
|
*/
|
||
|
|
||
|
if (midi->substream_output[midi->output_mpu] == NULL) {
|
||
|
goto __second;
|
||
|
}
|
||
|
|
||
|
while (max > 0) {
|
||
|
|
||
|
/* XXX fix me - no hard timing loops allowed! */
|
||
|
|
||
|
for (timeout = 30000; timeout > 0; timeout--) {
|
||
|
if (output_ready (midi))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave (&midi->virtual, flags);
|
||
|
if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) {
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
goto __second;
|
||
|
}
|
||
|
if (output_ready (midi)) {
|
||
|
if (snd_rawmidi_transmit(midi->substream_output[midi->output_mpu], &midi_byte, 1) == 1) {
|
||
|
if (!midi->isvirtual ||
|
||
|
(midi_byte != WF_INTERNAL_SWITCH &&
|
||
|
midi_byte != WF_EXTERNAL_SWITCH))
|
||
|
write_data(midi, midi_byte);
|
||
|
max--;
|
||
|
} else {
|
||
|
if (midi->istimer) {
|
||
|
if (--midi->istimer <= 0)
|
||
|
del_timer(&midi->timer);
|
||
|
}
|
||
|
midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
goto __second;
|
||
|
}
|
||
|
} else {
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
return;
|
||
|
}
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
}
|
||
|
|
||
|
__second:
|
||
|
|
||
|
if (midi->substream_output[!midi->output_mpu] == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
while (max > 0) {
|
||
|
|
||
|
/* XXX fix me - no hard timing loops allowed! */
|
||
|
|
||
|
for (timeout = 30000; timeout > 0; timeout--) {
|
||
|
if (output_ready (midi))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave (&midi->virtual, flags);
|
||
|
if (!midi->isvirtual)
|
||
|
mask = 0;
|
||
|
mpu = midi->output_mpu ^ mask;
|
||
|
mask = 0; /* don't invert the value from now */
|
||
|
if ((midi->mode[mpu] & MPU401_MODE_OUTPUT) == 0) {
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
return;
|
||
|
}
|
||
|
if (snd_rawmidi_transmit_empty(midi->substream_output[mpu]))
|
||
|
goto __timer;
|
||
|
if (output_ready (midi)) {
|
||
|
if (mpu != midi->output_mpu) {
|
||
|
write_data(midi, mpu == internal_mpu ?
|
||
|
WF_INTERNAL_SWITCH :
|
||
|
WF_EXTERNAL_SWITCH);
|
||
|
midi->output_mpu = mpu;
|
||
|
} else if (snd_rawmidi_transmit(midi->substream_output[mpu], &midi_byte, 1) == 1) {
|
||
|
if (!midi->isvirtual ||
|
||
|
(midi_byte != WF_INTERNAL_SWITCH &&
|
||
|
midi_byte != WF_EXTERNAL_SWITCH))
|
||
|
write_data(midi, midi_byte);
|
||
|
max--;
|
||
|
} else {
|
||
|
__timer:
|
||
|
if (midi->istimer) {
|
||
|
if (--midi->istimer <= 0)
|
||
|
del_timer(&midi->timer);
|
||
|
}
|
||
|
midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
return;
|
||
|
}
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int snd_wavefront_midi_input_open(struct snd_rawmidi_substream *substream)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
|
||
|
if (snd_BUG_ON(!substream || !substream->rmidi))
|
||
|
return -ENXIO;
|
||
|
if (snd_BUG_ON(!substream->rmidi->private_data))
|
||
|
return -ENXIO;
|
||
|
|
||
|
mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
|
||
|
|
||
|
midi = get_wavefront_midi(substream);
|
||
|
if (!midi)
|
||
|
return -EIO;
|
||
|
|
||
|
spin_lock_irqsave (&midi->open, flags);
|
||
|
midi->mode[mpu] |= MPU401_MODE_INPUT;
|
||
|
midi->substream_input[mpu] = substream;
|
||
|
spin_unlock_irqrestore (&midi->open, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int snd_wavefront_midi_output_open(struct snd_rawmidi_substream *substream)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
|
||
|
if (snd_BUG_ON(!substream || !substream->rmidi))
|
||
|
return -ENXIO;
|
||
|
if (snd_BUG_ON(!substream->rmidi->private_data))
|
||
|
return -ENXIO;
|
||
|
|
||
|
mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
|
||
|
|
||
|
midi = get_wavefront_midi(substream);
|
||
|
if (!midi)
|
||
|
return -EIO;
|
||
|
|
||
|
spin_lock_irqsave (&midi->open, flags);
|
||
|
midi->mode[mpu] |= MPU401_MODE_OUTPUT;
|
||
|
midi->substream_output[mpu] = substream;
|
||
|
spin_unlock_irqrestore (&midi->open, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int snd_wavefront_midi_input_close(struct snd_rawmidi_substream *substream)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
|
||
|
if (snd_BUG_ON(!substream || !substream->rmidi))
|
||
|
return -ENXIO;
|
||
|
if (snd_BUG_ON(!substream->rmidi->private_data))
|
||
|
return -ENXIO;
|
||
|
|
||
|
mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
|
||
|
|
||
|
midi = get_wavefront_midi(substream);
|
||
|
if (!midi)
|
||
|
return -EIO;
|
||
|
|
||
|
spin_lock_irqsave (&midi->open, flags);
|
||
|
midi->mode[mpu] &= ~MPU401_MODE_INPUT;
|
||
|
spin_unlock_irqrestore (&midi->open, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int snd_wavefront_midi_output_close(struct snd_rawmidi_substream *substream)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
|
||
|
if (snd_BUG_ON(!substream || !substream->rmidi))
|
||
|
return -ENXIO;
|
||
|
if (snd_BUG_ON(!substream->rmidi->private_data))
|
||
|
return -ENXIO;
|
||
|
|
||
|
mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
|
||
|
|
||
|
midi = get_wavefront_midi(substream);
|
||
|
if (!midi)
|
||
|
return -EIO;
|
||
|
|
||
|
spin_lock_irqsave (&midi->open, flags);
|
||
|
midi->mode[mpu] &= ~MPU401_MODE_OUTPUT;
|
||
|
spin_unlock_irqrestore (&midi->open, flags);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void snd_wavefront_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
|
||
|
if (substream == NULL || substream->rmidi == NULL)
|
||
|
return;
|
||
|
|
||
|
if (substream->rmidi->private_data == NULL)
|
||
|
return;
|
||
|
|
||
|
mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
|
||
|
|
||
|
midi = get_wavefront_midi(substream);
|
||
|
if (!midi)
|
||
|
return;
|
||
|
|
||
|
spin_lock_irqsave (&midi->virtual, flags);
|
||
|
if (up) {
|
||
|
midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER;
|
||
|
} else {
|
||
|
midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER;
|
||
|
}
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
}
|
||
|
|
||
|
static void snd_wavefront_midi_output_timer(struct timer_list *t)
|
||
|
{
|
||
|
snd_wavefront_midi_t *midi = from_timer(midi, t, timer);
|
||
|
snd_wavefront_card_t *card = midi->timer_card;
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave (&midi->virtual, flags);
|
||
|
mod_timer(&midi->timer, 1 + jiffies);
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
snd_wavefront_midi_output_write(card);
|
||
|
}
|
||
|
|
||
|
static void snd_wavefront_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
snd_wavefront_mpu_id mpu;
|
||
|
|
||
|
if (substream == NULL || substream->rmidi == NULL)
|
||
|
return;
|
||
|
|
||
|
if (substream->rmidi->private_data == NULL)
|
||
|
return;
|
||
|
|
||
|
mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
|
||
|
|
||
|
midi = get_wavefront_midi(substream);
|
||
|
if (!midi)
|
||
|
return;
|
||
|
|
||
|
spin_lock_irqsave (&midi->virtual, flags);
|
||
|
if (up) {
|
||
|
if ((midi->mode[mpu] & MPU401_MODE_OUTPUT_TRIGGER) == 0) {
|
||
|
if (!midi->istimer) {
|
||
|
timer_setup(&midi->timer,
|
||
|
snd_wavefront_midi_output_timer,
|
||
|
0);
|
||
|
mod_timer(&midi->timer, 1 + jiffies);
|
||
|
}
|
||
|
midi->istimer++;
|
||
|
midi->mode[mpu] |= MPU401_MODE_OUTPUT_TRIGGER;
|
||
|
}
|
||
|
} else {
|
||
|
midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
|
||
|
}
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
|
||
|
if (up)
|
||
|
snd_wavefront_midi_output_write((snd_wavefront_card_t *)substream->rmidi->card->private_data);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
snd_wavefront_midi_interrupt (snd_wavefront_card_t *card)
|
||
|
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
static struct snd_rawmidi_substream *substream = NULL;
|
||
|
static int mpu = external_mpu;
|
||
|
int max = 128;
|
||
|
unsigned char byte;
|
||
|
|
||
|
midi = &card->wavefront.midi;
|
||
|
|
||
|
if (!input_avail (midi)) { /* not for us */
|
||
|
snd_wavefront_midi_output_write(card);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave (&midi->virtual, flags);
|
||
|
while (--max) {
|
||
|
|
||
|
if (input_avail (midi)) {
|
||
|
byte = read_data (midi);
|
||
|
|
||
|
if (midi->isvirtual) {
|
||
|
if (byte == WF_EXTERNAL_SWITCH) {
|
||
|
substream = midi->substream_input[external_mpu];
|
||
|
mpu = external_mpu;
|
||
|
} else if (byte == WF_INTERNAL_SWITCH) {
|
||
|
substream = midi->substream_output[internal_mpu];
|
||
|
mpu = internal_mpu;
|
||
|
} /* else just leave it as it is */
|
||
|
} else {
|
||
|
substream = midi->substream_input[internal_mpu];
|
||
|
mpu = internal_mpu;
|
||
|
}
|
||
|
|
||
|
if (substream == NULL) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (midi->mode[mpu] & MPU401_MODE_INPUT_TRIGGER) {
|
||
|
snd_rawmidi_receive(substream, &byte, 1);
|
||
|
}
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
spin_unlock_irqrestore (&midi->virtual, flags);
|
||
|
|
||
|
snd_wavefront_midi_output_write(card);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
snd_wavefront_midi_enable_virtual (snd_wavefront_card_t *card)
|
||
|
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
|
||
|
card->wavefront.midi.isvirtual = 1;
|
||
|
card->wavefront.midi.output_mpu = internal_mpu;
|
||
|
card->wavefront.midi.input_mpu = internal_mpu;
|
||
|
spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
snd_wavefront_midi_disable_virtual (snd_wavefront_card_t *card)
|
||
|
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
|
||
|
// snd_wavefront_midi_input_close (card->ics2115_external_rmidi);
|
||
|
// snd_wavefront_midi_output_close (card->ics2115_external_rmidi);
|
||
|
card->wavefront.midi.isvirtual = 0;
|
||
|
spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
snd_wavefront_midi_start (snd_wavefront_card_t *card)
|
||
|
|
||
|
{
|
||
|
int ok, i;
|
||
|
unsigned char rbuf[4], wbuf[4];
|
||
|
snd_wavefront_t *dev;
|
||
|
snd_wavefront_midi_t *midi;
|
||
|
|
||
|
dev = &card->wavefront;
|
||
|
midi = &dev->midi;
|
||
|
|
||
|
/* The ICS2115 MPU-401 interface doesn't do anything
|
||
|
until its set into UART mode.
|
||
|
*/
|
||
|
|
||
|
/* XXX fix me - no hard timing loops allowed! */
|
||
|
|
||
|
for (i = 0; i < 30000 && !output_ready (midi); i++);
|
||
|
|
||
|
if (!output_ready (midi)) {
|
||
|
snd_printk ("MIDI interface not ready for command\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* Any interrupts received from now on
|
||
|
are owned by the MIDI side of things.
|
||
|
*/
|
||
|
|
||
|
dev->interrupts_are_midi = 1;
|
||
|
|
||
|
outb (UART_MODE_ON, midi->mpu_command_port);
|
||
|
|
||
|
for (ok = 0, i = 50000; i > 0 && !ok; i--) {
|
||
|
if (input_avail (midi)) {
|
||
|
if (read_data (midi) == MPU_ACK) {
|
||
|
ok = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!ok) {
|
||
|
snd_printk ("cannot set UART mode for MIDI interface");
|
||
|
dev->interrupts_are_midi = 0;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* Route external MIDI to WaveFront synth (by default) */
|
||
|
|
||
|
if (snd_wavefront_cmd (dev, WFC_MISYNTH_ON, rbuf, wbuf)) {
|
||
|
snd_printk ("can't enable MIDI-IN-2-synth routing.\n");
|
||
|
/* XXX error ? */
|
||
|
}
|
||
|
|
||
|
/* Turn on Virtual MIDI, but first *always* turn it off,
|
||
|
since otherwise consecutive reloads of the driver will
|
||
|
never cause the hardware to generate the initial "internal" or
|
||
|
"external" source bytes in the MIDI data stream. This
|
||
|
is pretty important, since the internal hardware generally will
|
||
|
be used to generate none or very little MIDI output, and
|
||
|
thus the only source of MIDI data is actually external. Without
|
||
|
the switch bytes, the driver will think it all comes from
|
||
|
the internal interface. Duh.
|
||
|
*/
|
||
|
|
||
|
if (snd_wavefront_cmd (dev, WFC_VMIDI_OFF, rbuf, wbuf)) {
|
||
|
snd_printk ("virtual MIDI mode not disabled\n");
|
||
|
return 0; /* We're OK, but missing the external MIDI dev */
|
||
|
}
|
||
|
|
||
|
snd_wavefront_midi_enable_virtual (card);
|
||
|
|
||
|
if (snd_wavefront_cmd (dev, WFC_VMIDI_ON, rbuf, wbuf)) {
|
||
|
snd_printk ("cannot enable virtual MIDI mode.\n");
|
||
|
snd_wavefront_midi_disable_virtual (card);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const struct snd_rawmidi_ops snd_wavefront_midi_output =
|
||
|
{
|
||
|
.open = snd_wavefront_midi_output_open,
|
||
|
.close = snd_wavefront_midi_output_close,
|
||
|
.trigger = snd_wavefront_midi_output_trigger,
|
||
|
};
|
||
|
|
||
|
const struct snd_rawmidi_ops snd_wavefront_midi_input =
|
||
|
{
|
||
|
.open = snd_wavefront_midi_input_open,
|
||
|
.close = snd_wavefront_midi_input_close,
|
||
|
.trigger = snd_wavefront_midi_input_trigger,
|
||
|
};
|
||
|
|