diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /sound/isa/wavefront/wavefront_midi.c | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/isa/wavefront/wavefront_midi.c')
-rw-r--r-- | sound/isa/wavefront/wavefront_midi.c | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c new file mode 100644 index 000000000..72e775ac7 --- /dev/null +++ b/sound/isa/wavefront/wavefront_midi.c @@ -0,0 +1,576 @@ +// 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, +}; + |