diff options
Diffstat (limited to 'sound/oss/wf_midi.c')
-rw-r--r-- | sound/oss/wf_midi.c | 880 |
1 files changed, 880 insertions, 0 deletions
diff --git a/sound/oss/wf_midi.c b/sound/oss/wf_midi.c new file mode 100644 index 000000000000..7b167b74375b --- /dev/null +++ b/sound/oss/wf_midi.c @@ -0,0 +1,880 @@ +/* + * sound/wf_midi.c + * + * 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 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 devices (/dev/midiNN and /dev/midiNN+1) to be used + * completely independently, giving 32 channels of MIDI routing, + * 16 to the WaveFront synth and 16 to the external MIDI bus. + * + * 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 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. + * + */ + +/* + * Copyright (C) by Paul Barton-Davis 1998 + * Some portions of this file are derived from work that is: + * + * CopyriGht (C) by Hannu Savolainen 1993-1996 + * + * USS/Lite for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this software + * for more info. + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include "sound_config.h" + +#include <linux/wavefront.h> + +#ifdef MODULE + +struct wf_mpu_config { + int base; +#define DATAPORT(d) (d)->base +#define COMDPORT(d) (d)->base+1 +#define STATPORT(d) (d)->base+1 + + int irq; + int opened; + int devno; + int synthno; + int mode; +#define MODE_MIDI 1 +#define MODE_SYNTH 2 + + void (*inputintr) (int dev, unsigned char data); + char isvirtual; /* do virtual I/O stuff */ +}; + +static struct wf_mpu_config devs[2]; +static struct wf_mpu_config *phys_dev = &devs[0]; +static struct wf_mpu_config *virt_dev = &devs[1]; + +static void start_uart_mode (void); +static DEFINE_SPINLOCK(lock); + +#define OUTPUT_READY 0x40 +#define INPUT_AVAIL 0x80 +#define MPU_ACK 0xFE +#define UART_MODE_ON 0x3F + +static inline int wf_mpu_status (void) +{ + return inb (STATPORT (phys_dev)); +} + +static inline int input_avail (void) +{ + return !(wf_mpu_status() & INPUT_AVAIL); +} + +static inline int output_ready (void) +{ + return !(wf_mpu_status() & OUTPUT_READY); +} + +static inline int read_data (void) +{ + return inb (DATAPORT (phys_dev)); +} + +static inline void write_data (unsigned char byte) +{ + outb (byte, DATAPORT (phys_dev)); +} + +/* + * States for the input scanner (should be in dev_table.h) + */ + +#define MST_SYSMSG 100 /* System message (sysx etc). */ +#define MST_MTC 102 /* Midi Time Code (MTC) qframe msg */ +#define MST_SONGSEL 103 /* Song select */ +#define MST_SONGPOS 104 /* Song position pointer */ +#define MST_TIMED 105 /* Leading timing byte rcvd */ + +/* buffer space check for input scanner */ + +#define BUFTEST(mi) if (mi->m_ptr >= MI_MAX || mi->m_ptr < 0) \ +{printk(KERN_ERR "WF-MPU: Invalid buffer pointer %d/%d, s=%d\n", \ + mi->m_ptr, mi->m_left, mi->m_state);mi->m_ptr--;} + +static unsigned char len_tab[] = /* # of data bytes following a status + */ +{ + 2, /* 8x */ + 2, /* 9x */ + 2, /* Ax */ + 2, /* Bx */ + 1, /* Cx */ + 1, /* Dx */ + 2, /* Ex */ + 0 /* Fx */ +}; + +static int +wf_mpu_input_scanner (int devno, int synthdev, unsigned char midic) + +{ + struct midi_input_info *mi = &midi_devs[devno]->in_info; + + switch (mi->m_state) { + case MST_INIT: + switch (midic) { + case 0xf8: + /* Timer overflow */ + break; + + case 0xfc: + break; + + case 0xfd: + /* XXX do something useful with this. If there is + an external MIDI timer (e.g. a hardware sequencer, + a useful timer can be derived ... + + For now, no timer support. + */ + break; + + case 0xfe: + return MPU_ACK; + break; + + case 0xf0: + case 0xf1: + case 0xf2: + case 0xf3: + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + break; + + case 0xf9: + break; + + case 0xff: + mi->m_state = MST_SYSMSG; + break; + + default: + if (midic <= 0xef) { + mi->m_state = MST_TIMED; + } + else + printk (KERN_ERR "<MPU: Unknown event %02x> ", + midic); + } + break; + + case MST_TIMED: + { + int msg = ((int) (midic & 0xf0) >> 4); + + mi->m_state = MST_DATA; + + if (msg < 8) { /* Data byte */ + + msg = ((int) (mi->m_prev_status & 0xf0) >> 4); + msg -= 8; + mi->m_left = len_tab[msg] - 1; + + mi->m_ptr = 2; + mi->m_buf[0] = mi->m_prev_status; + mi->m_buf[1] = midic; + + if (mi->m_left <= 0) { + mi->m_state = MST_INIT; + do_midi_msg (synthdev, mi->m_buf, mi->m_ptr); + mi->m_ptr = 0; + } + } else if (msg == 0xf) { /* MPU MARK */ + + mi->m_state = MST_INIT; + + switch (midic) { + case 0xf8: + break; + + case 0xf9: + break; + + case 0xfc: + break; + + default: + break; + } + } else { + mi->m_prev_status = midic; + msg -= 8; + mi->m_left = len_tab[msg]; + + mi->m_ptr = 1; + mi->m_buf[0] = midic; + + if (mi->m_left <= 0) { + mi->m_state = MST_INIT; + do_midi_msg (synthdev, mi->m_buf, mi->m_ptr); + mi->m_ptr = 0; + } + } + } + break; + + case MST_SYSMSG: + switch (midic) { + case 0xf0: + mi->m_state = MST_SYSEX; + break; + + case 0xf1: + mi->m_state = MST_MTC; + break; + + case 0xf2: + mi->m_state = MST_SONGPOS; + mi->m_ptr = 0; + break; + + case 0xf3: + mi->m_state = MST_SONGSEL; + break; + + case 0xf6: + mi->m_state = MST_INIT; + + /* + * Real time messages + */ + case 0xf8: + /* midi clock */ + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xfA: + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xFB: + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xFC: + mi->m_state = MST_INIT; + /* XXX need ext MIDI timer support */ + break; + + case 0xFE: + /* active sensing */ + mi->m_state = MST_INIT; + break; + + case 0xff: + mi->m_state = MST_INIT; + break; + + default: + printk (KERN_ERR "unknown MIDI sysmsg %0x\n", midic); + mi->m_state = MST_INIT; + } + break; + + case MST_MTC: + mi->m_state = MST_INIT; + break; + + case MST_SYSEX: + if (midic == 0xf7) { + mi->m_state = MST_INIT; + } else { + /* XXX fix me */ + } + break; + + case MST_SONGPOS: + BUFTEST (mi); + mi->m_buf[mi->m_ptr++] = midic; + if (mi->m_ptr == 2) { + mi->m_state = MST_INIT; + mi->m_ptr = 0; + /* XXX need ext MIDI timer support */ + } + break; + + case MST_DATA: + BUFTEST (mi); + mi->m_buf[mi->m_ptr++] = midic; + if ((--mi->m_left) <= 0) { + mi->m_state = MST_INIT; + do_midi_msg (synthdev, mi->m_buf, mi->m_ptr); + mi->m_ptr = 0; + } + break; + + default: + printk (KERN_ERR "Bad state %d ", mi->m_state); + mi->m_state = MST_INIT; + } + + return 1; +} + +static irqreturn_t +wf_mpuintr(int irq, void *dev_id, struct pt_regs *dummy) + +{ + struct wf_mpu_config *physical_dev = dev_id; + static struct wf_mpu_config *input_dev; + struct midi_input_info *mi = &midi_devs[physical_dev->devno]->in_info; + int n; + + if (!input_avail()) { /* not for us */ + return IRQ_NONE; + } + + if (mi->m_busy) + return IRQ_HANDLED; + spin_lock(&lock); + mi->m_busy = 1; + + if (!input_dev) { + input_dev = physical_dev; + } + + n = 50; /* XXX why ? */ + + do { + unsigned char c = read_data (); + + if (phys_dev->isvirtual) { + + if (c == WF_EXTERNAL_SWITCH) { + input_dev = virt_dev; + continue; + } else if (c == WF_INTERNAL_SWITCH) { + input_dev = phys_dev; + continue; + } /* else just leave it as it is */ + + } else { + input_dev = phys_dev; + } + + if (input_dev->mode == MODE_SYNTH) { + + wf_mpu_input_scanner (input_dev->devno, + input_dev->synthno, c); + + } else if (input_dev->opened & OPEN_READ) { + + if (input_dev->inputintr) { + input_dev->inputintr (input_dev->devno, c); + } + } + + } while (input_avail() && n-- > 0); + + mi->m_busy = 0; + spin_unlock(&lock); + return IRQ_HANDLED; +} + +static int +wf_mpu_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) + ) +{ + struct wf_mpu_config *devc; + + if (dev < 0 || dev >= num_midis || midi_devs[dev]==NULL) + return -(ENXIO); + + if (phys_dev->devno == dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return -(EINVAL); + } + + if (devc->opened) { + return -(EBUSY); + } + + devc->mode = MODE_MIDI; + devc->opened = mode; + devc->synthno = 0; + + devc->inputintr = input; + return 0; +} + +static void +wf_mpu_close (int dev) +{ + struct wf_mpu_config *devc; + + if (dev < 0 || dev >= num_midis || midi_devs[dev]==NULL) + return; + + if (phys_dev->devno == dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return; + } + + devc->mode = 0; + devc->inputintr = NULL; + devc->opened = 0; +} + +static int +wf_mpu_out (int dev, unsigned char midi_byte) +{ + int timeout; + unsigned long flags; + static int lastoutdev = -1; + unsigned char switchch; + + if (phys_dev->isvirtual && lastoutdev != dev) { + + if (dev == phys_dev->devno) { + switchch = WF_INTERNAL_SWITCH; + } else if (dev == virt_dev->devno) { + switchch = WF_EXTERNAL_SWITCH; + } else { + printk (KERN_ERR "WF-MPU: bad device number %d", dev); + return (0); + } + + /* XXX fix me */ + + for (timeout = 30000; timeout > 0 && !output_ready (); + timeout--); + + spin_lock_irqsave(&lock,flags); + + if (!output_ready ()) { + printk (KERN_WARNING "WF-MPU: Send switch " + "byte timeout\n"); + spin_unlock_irqrestore(&lock,flags); + return 0; + } + + write_data (switchch); + spin_unlock_irqrestore(&lock,flags); + } + + lastoutdev = dev; + + /* + * Sometimes it takes about 30000 loops before the output becomes ready + * (After reset). Normally it takes just about 10 loops. + */ + + /* XXX fix me */ + + for (timeout = 30000; timeout > 0 && !output_ready (); timeout--); + + spin_lock_irqsave(&lock,flags); + if (!output_ready ()) { + spin_unlock_irqrestore(&lock,flags); + printk (KERN_WARNING "WF-MPU: Send data timeout\n"); + return 0; + } + + write_data (midi_byte); + spin_unlock_irqrestore(&lock,flags); + + return 1; +} + +static inline int wf_mpu_start_read (int dev) { + return 0; +} + +static inline int wf_mpu_end_read (int dev) { + return 0; +} + +static int wf_mpu_ioctl (int dev, unsigned cmd, void __user *arg) +{ + printk (KERN_WARNING + "WF-MPU: Intelligent mode not supported by hardware.\n"); + return -(EINVAL); +} + +static int wf_mpu_buffer_status (int dev) +{ + return 0; +} + +static struct synth_operations wf_mpu_synth_operations[2]; +static struct midi_operations wf_mpu_midi_operations[2]; + +static struct midi_operations wf_mpu_midi_proto = +{ + .owner = THIS_MODULE, + .info = {"WF-MPU MIDI", 0, MIDI_CAP_MPU401, SNDCARD_MPU401}, + .in_info = {0}, /* in_info */ + .open = wf_mpu_open, + .close = wf_mpu_close, + .ioctl = wf_mpu_ioctl, + .outputc = wf_mpu_out, + .start_read = wf_mpu_start_read, + .end_read = wf_mpu_end_read, + .buffer_status = wf_mpu_buffer_status, +}; + +static struct synth_info wf_mpu_synth_info_proto = +{"WaveFront MPU-401 interface", 0, + SYNTH_TYPE_MIDI, MIDI_TYPE_MPU401, 0, 128, 0, 128, SYNTH_CAP_INPUT}; + +static struct synth_info wf_mpu_synth_info[2]; + +static int +wf_mpu_synth_ioctl (int dev, unsigned int cmd, void __user *arg) +{ + int midi_dev; + int index; + + midi_dev = synth_devs[dev]->midi_dev; + + if (midi_dev < 0 || midi_dev > num_midis || midi_devs[midi_dev]==NULL) + return -(ENXIO); + + if (midi_dev == phys_dev->devno) { + index = 0; + } else if (phys_dev->isvirtual && midi_dev == virt_dev->devno) { + index = 1; + } else { + return -(EINVAL); + } + + switch (cmd) { + + case SNDCTL_SYNTH_INFO: + if (copy_to_user(arg, + &wf_mpu_synth_info[index], + sizeof (struct synth_info))) + return -EFAULT; + return 0; + + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + + default: + return -EINVAL; + } +} + +static int +wf_mpu_synth_open (int dev, int mode) +{ + int midi_dev; + struct wf_mpu_config *devc; + + midi_dev = synth_devs[dev]->midi_dev; + + if (midi_dev < 0 || midi_dev > num_midis || midi_devs[midi_dev]==NULL) { + return -(ENXIO); + } + + if (phys_dev->devno == midi_dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == midi_dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return -(EINVAL); + } + + if (devc->opened) { + return -(EBUSY); + } + + devc->mode = MODE_SYNTH; + devc->synthno = dev; + devc->opened = mode; + devc->inputintr = NULL; + return 0; +} + +static void +wf_mpu_synth_close (int dev) +{ + int midi_dev; + struct wf_mpu_config *devc; + + midi_dev = synth_devs[dev]->midi_dev; + + if (phys_dev->devno == midi_dev) { + devc = phys_dev; + } else if (phys_dev->isvirtual && virt_dev->devno == midi_dev) { + devc = virt_dev; + } else { + printk (KERN_ERR "WF-MPU: unknown device number %d\n", dev); + return; + } + + devc->inputintr = NULL; + devc->opened = 0; + devc->mode = 0; +} + +#define _MIDI_SYNTH_C_ +#define MIDI_SYNTH_NAME "WaveFront (MIDI)" +#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT +#include "midi_synth.h" + +static struct synth_operations wf_mpu_synth_proto = +{ + .owner = THIS_MODULE, + .id = "WaveFront (ICS2115)", + .info = NULL, /* info field, filled in during configuration */ + .midi_dev = 0, /* MIDI dev XXX should this be -1 ? */ + .synth_type = SYNTH_TYPE_MIDI, + .synth_subtype = SAMPLE_TYPE_WAVEFRONT, + .open = wf_mpu_synth_open, + .close = wf_mpu_synth_close, + .ioctl = wf_mpu_synth_ioctl, + .kill_note = midi_synth_kill_note, + .start_note = midi_synth_start_note, + .set_instr = midi_synth_set_instr, + .reset = midi_synth_reset, + .hw_control = midi_synth_hw_control, + .load_patch = midi_synth_load_patch, + .aftertouch = midi_synth_aftertouch, + .controller = midi_synth_controller, + .panning = midi_synth_panning, + .bender = midi_synth_bender, + .setup_voice = midi_synth_setup_voice, + .send_sysex = midi_synth_send_sysex +}; + +static int +config_wf_mpu (struct wf_mpu_config *dev) + +{ + int is_external; + char *name; + int index; + + if (dev == phys_dev) { + name = "WaveFront internal MIDI"; + is_external = 0; + index = 0; + memcpy ((char *) &wf_mpu_synth_operations[index], + (char *) &wf_mpu_synth_proto, + sizeof (struct synth_operations)); + } else { + name = "WaveFront external MIDI"; + is_external = 1; + index = 1; + /* no synth operations for an external MIDI interface */ + } + + memcpy ((char *) &wf_mpu_synth_info[dev->devno], + (char *) &wf_mpu_synth_info_proto, + sizeof (struct synth_info)); + + strcpy (wf_mpu_synth_info[index].name, name); + + wf_mpu_synth_operations[index].midi_dev = dev->devno; + wf_mpu_synth_operations[index].info = &wf_mpu_synth_info[index]; + + memcpy ((char *) &wf_mpu_midi_operations[index], + (char *) &wf_mpu_midi_proto, + sizeof (struct midi_operations)); + + if (is_external) { + wf_mpu_midi_operations[index].converter = NULL; + } else { + wf_mpu_midi_operations[index].converter = + &wf_mpu_synth_operations[index]; + } + + strcpy (wf_mpu_midi_operations[index].info.name, name); + + midi_devs[dev->devno] = &wf_mpu_midi_operations[index]; + midi_devs[dev->devno]->in_info.m_busy = 0; + midi_devs[dev->devno]->in_info.m_state = MST_INIT; + midi_devs[dev->devno]->in_info.m_ptr = 0; + midi_devs[dev->devno]->in_info.m_left = 0; + midi_devs[dev->devno]->in_info.m_prev_status = 0; + + devs[index].opened = 0; + devs[index].mode = 0; + + return (0); +} + +int virtual_midi_enable (void) + +{ + if ((virt_dev->devno < 0) && + (virt_dev->devno = sound_alloc_mididev()) == -1) { + printk (KERN_ERR + "WF-MPU: too many midi devices detected\n"); + return -1; + } + + config_wf_mpu (virt_dev); + + phys_dev->isvirtual = 1; + return virt_dev->devno; +} + +int +virtual_midi_disable (void) + +{ + unsigned long flags; + + spin_lock_irqsave(&lock,flags); + + wf_mpu_close (virt_dev->devno); + /* no synth on virt_dev, so no need to call wf_mpu_synth_close() */ + phys_dev->isvirtual = 0; + + spin_unlock_irqrestore(&lock,flags); + + return 0; +} + +int __init detect_wf_mpu (int irq, int io_base) +{ + if (!request_region(io_base, 2, "wavefront midi")) { + printk (KERN_WARNING "WF-MPU: I/O port %x already in use.\n", + io_base); + return -1; + } + + phys_dev->base = io_base; + phys_dev->irq = irq; + phys_dev->devno = -1; + virt_dev->devno = -1; + + return 0; +} + +int __init install_wf_mpu (void) +{ + if ((phys_dev->devno = sound_alloc_mididev()) < 0){ + + printk (KERN_ERR "WF-MPU: Too many MIDI devices detected.\n"); + release_region(phys_dev->base, 2); + return -1; + } + + phys_dev->isvirtual = 0; + + if (config_wf_mpu (phys_dev)) { + + printk (KERN_WARNING + "WF-MPU: configuration for MIDI device %d failed\n", + phys_dev->devno); + sound_unload_mididev (phys_dev->devno); + + } + + /* OK, now we're configured to handle an interrupt ... */ + + if (request_irq (phys_dev->irq, wf_mpuintr, SA_INTERRUPT|SA_SHIRQ, + "wavefront midi", phys_dev) < 0) { + + printk (KERN_ERR "WF-MPU: Failed to allocate IRQ%d\n", + phys_dev->irq); + return -1; + + } + + /* This being a WaveFront (ICS-2115) emulated MPU-401, we have + to switch it into UART (dumb) mode, because otherwise, it + won't do anything at all. + */ + + start_uart_mode (); + + return phys_dev->devno; +} + +void +uninstall_wf_mpu (void) + +{ + release_region (phys_dev->base, 2); + free_irq (phys_dev->irq, phys_dev); + sound_unload_mididev (phys_dev->devno); + + if (virt_dev->devno >= 0) { + sound_unload_mididev (virt_dev->devno); + } +} + +static void +start_uart_mode (void) + +{ + int ok, i; + unsigned long flags; + + spin_lock_irqsave(&lock,flags); + + /* XXX fix me */ + + for (i = 0; i < 30000 && !output_ready (); i++); + + outb (UART_MODE_ON, COMDPORT(phys_dev)); + + for (ok = 0, i = 50000; i > 0 && !ok; i--) { + if (input_avail ()) { + if (read_data () == MPU_ACK) { + ok = 1; + } + } + } + + spin_unlock_irqrestore(&lock,flags); +} +#endif |