forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
576 lines
15 KiB
576 lines
15 KiB
// 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, |
|
}; |
|
|
|
|