| // 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 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; | |
| } | |
| guard(spinlock_irqsave)(&midi->virtual); | |
| if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) | |
| 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) | |
| timer_delete(&midi->timer); | |
| } | |
| midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; | |
| goto __second; | |
| } | |
| } else { | |
| return; | |
| } | |
| } | |
| __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; | |
| } | |
| guard(spinlock_irqsave)(&midi->virtual); | |
| 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) | |
| 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) | |
| timer_delete(&midi->timer); | |
| } | |
| midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; | |
| return; | |
| } | |
| } else { | |
| return; | |
| } | |
| } | |
| } | |
| static int snd_wavefront_midi_input_open(struct snd_rawmidi_substream *substream) | |
| { | |
| 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; | |
| guard(spinlock_irqsave)(&midi->open); | |
| midi->mode[mpu] |= MPU401_MODE_INPUT; | |
| midi->substream_input[mpu] = substream; | |
| return 0; | |
| } | |
| static int snd_wavefront_midi_output_open(struct snd_rawmidi_substream *substream) | |
| { | |
| 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; | |
| guard(spinlock_irqsave)(&midi->open); | |
| midi->mode[mpu] |= MPU401_MODE_OUTPUT; | |
| midi->substream_output[mpu] = substream; | |
| return 0; | |
| } | |
| static int snd_wavefront_midi_input_close(struct snd_rawmidi_substream *substream) | |
| { | |
| 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; | |
| guard(spinlock_irqsave)(&midi->open); | |
| midi->substream_input[mpu] = NULL; | |
| midi->mode[mpu] &= ~MPU401_MODE_INPUT; | |
| return 0; | |
| } | |
| static int snd_wavefront_midi_output_close(struct snd_rawmidi_substream *substream) | |
| { | |
| 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; | |
| guard(spinlock_irqsave)(&midi->open); | |
| midi->substream_output[mpu] = NULL; | |
| midi->mode[mpu] &= ~MPU401_MODE_OUTPUT; | |
| return 0; | |
| } | |
| static void snd_wavefront_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) | |
| { | |
| 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; | |
| guard(spinlock_irqsave)(&midi->virtual); | |
| if (up) { | |
| midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER; | |
| } else { | |
| midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER; | |
| } | |
| } | |
| static void snd_wavefront_midi_output_timer(struct timer_list *t) | |
| { | |
| snd_wavefront_midi_t *midi = timer_container_of(midi, t, timer); | |
| snd_wavefront_card_t *card = midi->timer_card; | |
| scoped_guard(spinlock_irqsave, &midi->virtual) { | |
| mod_timer(&midi->timer, 1 + jiffies); | |
| } | |
| snd_wavefront_midi_output_write(card); | |
| } | |
| static void snd_wavefront_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) | |
| { | |
| 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; | |
| scoped_guard(spinlock_irqsave, &midi->virtual) { | |
| 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; | |
| } | |
| } | |
| 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) | |
| { | |
| 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; | |
| } | |
| scoped_guard(spinlock_irqsave, &midi->virtual) { | |
| 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; | |
| } | |
| } | |
| } | |
| 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) | |
| { | |
| guard(spinlock_irqsave)(&card->wavefront.midi.virtual); | |
| // snd_wavefront_midi_input_close (card->ics2115_external_rmidi); | |
| // snd_wavefront_midi_output_close (card->ics2115_external_rmidi); | |
| card->wavefront.midi.isvirtual = 0; | |
| } | |
| void | |
| snd_wavefront_midi_suspend(snd_wavefront_card_t *card) | |
| { | |
| snd_wavefront_midi_t *midi = &card->wavefront.midi; | |
| if (!midi->istimer) | |
| return; | |
| timer_delete_sync(&midi->timer); | |
| guard(spinlock_irqsave)(&midi->virtual); | |
| midi->istimer = 0; | |
| } | |
| void | |
| snd_wavefront_midi_resume(snd_wavefront_card_t *card) | |
| { | |
| snd_wavefront_midi_t *midi = &card->wavefront.midi; | |
| int istimer = 0; | |
| bool pending_output = false; | |
| midi->timer_card = card; | |
| scoped_guard(spinlock_irqsave, &midi->virtual) { | |
| if (midi->mode[internal_mpu] & MPU401_MODE_OUTPUT_TRIGGER) | |
| istimer++; | |
| if (midi->mode[external_mpu] & MPU401_MODE_OUTPUT_TRIGGER) | |
| istimer++; | |
| if (!istimer) | |
| return; | |
| midi->istimer = istimer; | |
| timer_setup(&midi->timer, snd_wavefront_midi_output_timer, 0); | |
| mod_timer(&midi->timer, 1 + jiffies); | |
| pending_output = true; | |
| } | |
| if (pending_output) | |
| snd_wavefront_midi_output_write(card); | |
| } | |
| 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; | |
| midi->timer_card = card; | |
| /* 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)) { | |
| dev_err(card->wavefront.card->dev, | |
| "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) { | |
| dev_err(card->wavefront.card->dev, | |
| "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)) { | |
| dev_warn(card->wavefront.card->dev, | |
| "can't enable MIDI-IN-2-synth routing.\n"); | |
| /* XXX error ? */ | |
| } else { | |
| dev->midi_in_to_synth = 1; | |
| } | |
| /* 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)) { | |
| dev_warn(card->wavefront.card->dev, | |
| "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)) { | |
| dev_warn(card->wavefront.card->dev, | |
| "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, | |
| }; |