summaryrefslogtreecommitdiff
path: root/sound/oss/uart401.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/uart401.c')
-rw-r--r--sound/oss/uart401.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/sound/oss/uart401.c b/sound/oss/uart401.c
new file mode 100644
index 00000000..8e514a67
--- /dev/null
+++ b/sound/oss/uart401.c
@@ -0,0 +1,482 @@
+/*
+ * sound/oss/uart401.c
+ *
+ * MPU-401 UART driver (formerly uart401_midi.c)
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free 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.
+ *
+ * Changes:
+ * Alan Cox Reformatted, removed sound_mem usage, use normal Linux
+ * interrupt allocation. Protect against bogus unload
+ * Fixed to allow IRQ > 15
+ * Christoph Hellwig Adapted to module_init/module_exit
+ * Arnaldo C. de Melo got rid of check_region
+ *
+ * Status:
+ * Untested
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "mpu401.h"
+
+typedef struct uart401_devc
+{
+ int base;
+ int irq;
+ int *osp;
+ void (*midi_input_intr) (int dev, unsigned char data);
+ int opened, disabled;
+ volatile unsigned char input_byte;
+ int my_dev;
+ int share_irq;
+ spinlock_t lock;
+}
+uart401_devc;
+
+#define DATAPORT (devc->base)
+#define COMDPORT (devc->base+1)
+#define STATPORT (devc->base+1)
+
+static int uart401_status(uart401_devc * devc)
+{
+ return inb(STATPORT);
+}
+
+#define input_avail(devc) (!(uart401_status(devc)&INPUT_AVAIL))
+#define output_ready(devc) (!(uart401_status(devc)&OUTPUT_READY))
+
+static void uart401_cmd(uart401_devc * devc, unsigned char cmd)
+{
+ outb((cmd), COMDPORT);
+}
+
+static int uart401_read(uart401_devc * devc)
+{
+ return inb(DATAPORT);
+}
+
+static void uart401_write(uart401_devc * devc, unsigned char byte)
+{
+ outb((byte), DATAPORT);
+}
+
+#define OUTPUT_READY 0x40
+#define INPUT_AVAIL 0x80
+#define MPU_ACK 0xFE
+#define MPU_RESET 0xFF
+#define UART_MODE_ON 0x3F
+
+static int reset_uart401(uart401_devc * devc);
+static void enter_uart_mode(uart401_devc * devc);
+
+static void uart401_input_loop(uart401_devc * devc)
+{
+ int work_limit=30000;
+
+ while (input_avail(devc) && --work_limit)
+ {
+ unsigned char c = uart401_read(devc);
+
+ if (c == MPU_ACK)
+ devc->input_byte = c;
+ else if (devc->opened & OPEN_READ && devc->midi_input_intr)
+ devc->midi_input_intr(devc->my_dev, c);
+ }
+ if(work_limit==0)
+ printk(KERN_WARNING "Too much work in interrupt on uart401 (0x%X). UART jabbering ??\n", devc->base);
+}
+
+irqreturn_t uart401intr(int irq, void *dev_id)
+{
+ uart401_devc *devc = dev_id;
+
+ if (devc == NULL)
+ {
+ printk(KERN_ERR "uart401: bad devc\n");
+ return IRQ_NONE;
+ }
+
+ if (input_avail(devc))
+ uart401_input_loop(devc);
+ return IRQ_HANDLED;
+}
+
+static int
+uart401_open(int dev, int mode,
+ void (*input) (int dev, unsigned char data),
+ void (*output) (int dev)
+)
+{
+ uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+ if (devc->opened)
+ return -EBUSY;
+
+ /* Flush the UART */
+
+ while (input_avail(devc))
+ uart401_read(devc);
+
+ devc->midi_input_intr = input;
+ devc->opened = mode;
+ enter_uart_mode(devc);
+ devc->disabled = 0;
+
+ return 0;
+}
+
+static void uart401_close(int dev)
+{
+ uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+ reset_uart401(devc);
+ devc->opened = 0;
+}
+
+static int uart401_out(int dev, unsigned char midi_byte)
+{
+ int timeout;
+ unsigned long flags;
+ uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+ if (devc->disabled)
+ return 1;
+ /*
+ * Test for input since pending input seems to block the output.
+ */
+
+ spin_lock_irqsave(&devc->lock,flags);
+ if (input_avail(devc))
+ uart401_input_loop(devc);
+
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ /*
+ * Sometimes it takes about 13000 loops before the output becomes ready
+ * (After reset). Normally it takes just about 10 loops.
+ */
+
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+ if (!output_ready(devc))
+ {
+ printk(KERN_WARNING "uart401: Timeout - Device not responding\n");
+ devc->disabled = 1;
+ reset_uart401(devc);
+ enter_uart_mode(devc);
+ return 1;
+ }
+ uart401_write(devc, midi_byte);
+ return 1;
+}
+
+static inline int uart401_start_read(int dev)
+{
+ return 0;
+}
+
+static inline int uart401_end_read(int dev)
+{
+ return 0;
+}
+
+static inline void uart401_kick(int dev)
+{
+}
+
+static inline int uart401_buffer_status(int dev)
+{
+ return 0;
+}
+
+#define MIDI_SYNTH_NAME "MPU-401 UART"
+#define MIDI_SYNTH_CAPS SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static const struct midi_operations uart401_operations =
+{
+ .owner = THIS_MODULE,
+ .info = {"MPU-401 (UART) MIDI", 0, 0, SNDCARD_MPU401},
+ .converter = &std_midi_synth,
+ .in_info = {0},
+ .open = uart401_open,
+ .close = uart401_close,
+ .outputc = uart401_out,
+ .start_read = uart401_start_read,
+ .end_read = uart401_end_read,
+ .kick = uart401_kick,
+ .buffer_status = uart401_buffer_status,
+};
+
+static void enter_uart_mode(uart401_devc * devc)
+{
+ int ok, timeout;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devc->lock,flags);
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+ devc->input_byte = 0;
+ uart401_cmd(devc, UART_MODE_ON);
+
+ ok = 0;
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ if (devc->input_byte == MPU_ACK)
+ ok = 1;
+ else if (input_avail(devc))
+ if (uart401_read(devc) == MPU_ACK)
+ ok = 1;
+
+ spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int reset_uart401(uart401_devc * devc)
+{
+ int ok, timeout, n;
+
+ /*
+ * Send the RESET command. Try again if no success at the first time.
+ */
+
+ ok = 0;
+
+ for (n = 0; n < 2 && !ok; n++)
+ {
+ for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+ devc->input_byte = 0;
+ uart401_cmd(devc, MPU_RESET);
+
+ /*
+ * Wait at least 25 msec. This method is not accurate so let's make the
+ * loop bit longer. Cannot sleep since this is called during boot.
+ */
+
+ for (timeout = 50000; timeout > 0 && !ok; timeout--)
+ {
+ if (devc->input_byte == MPU_ACK) /* Interrupt */
+ ok = 1;
+ else if (input_avail(devc))
+ {
+ if (uart401_read(devc) == MPU_ACK)
+ ok = 1;
+ }
+ }
+ }
+
+
+ if (ok)
+ {
+ DEB(printk("Reset UART401 OK\n"));
+ }
+ else
+ DDB(printk("Reset UART401 failed - No hardware detected.\n"));
+
+ if (ok)
+ uart401_input_loop(devc); /*
+ * Flush input before enabling interrupts
+ */
+
+ return ok;
+}
+
+int probe_uart401(struct address_info *hw_config, struct module *owner)
+{
+ uart401_devc *devc;
+ char *name = "MPU-401 (UART) MIDI";
+ int ok = 0;
+ unsigned long flags;
+
+ DDB(printk("Entered probe_uart401()\n"));
+
+ /* Default to "not found" */
+ hw_config->slots[4] = -1;
+
+ if (!request_region(hw_config->io_base, 4, "MPU-401 UART")) {
+ printk(KERN_INFO "uart401: could not request_region(%d, 4)\n", hw_config->io_base);
+ return 0;
+ }
+
+ devc = kmalloc(sizeof(uart401_devc), GFP_KERNEL);
+ if (!devc) {
+ printk(KERN_WARNING "uart401: Can't allocate memory\n");
+ goto cleanup_region;
+ }
+
+ devc->base = hw_config->io_base;
+ devc->irq = hw_config->irq;
+ devc->osp = hw_config->osp;
+ devc->midi_input_intr = NULL;
+ devc->opened = 0;
+ devc->input_byte = 0;
+ devc->my_dev = 0;
+ devc->share_irq = 0;
+ spin_lock_init(&devc->lock);
+
+ spin_lock_irqsave(&devc->lock,flags);
+ ok = reset_uart401(devc);
+ spin_unlock_irqrestore(&devc->lock,flags);
+
+ if (!ok)
+ goto cleanup_devc;
+
+ if (hw_config->name)
+ name = hw_config->name;
+
+ if (devc->irq < 0) {
+ devc->share_irq = 1;
+ devc->irq *= -1;
+ } else
+ devc->share_irq = 0;
+
+ if (!devc->share_irq)
+ if (request_irq(devc->irq, uart401intr, 0, "MPU-401 UART", devc) < 0) {
+ printk(KERN_WARNING "uart401: Failed to allocate IRQ%d\n", devc->irq);
+ devc->share_irq = 1;
+ }
+ devc->my_dev = sound_alloc_mididev();
+ enter_uart_mode(devc);
+
+ if (devc->my_dev == -1) {
+ printk(KERN_INFO "uart401: Too many midi devices detected\n");
+ goto cleanup_irq;
+ }
+ conf_printf(name, hw_config);
+ midi_devs[devc->my_dev] = kmalloc(sizeof(struct midi_operations), GFP_KERNEL);
+ if (!midi_devs[devc->my_dev]) {
+ printk(KERN_ERR "uart401: Failed to allocate memory\n");
+ goto cleanup_unload_mididev;
+ }
+ memcpy(midi_devs[devc->my_dev], &uart401_operations, sizeof(struct midi_operations));
+
+ if (owner)
+ midi_devs[devc->my_dev]->owner = owner;
+
+ midi_devs[devc->my_dev]->devc = devc;
+ midi_devs[devc->my_dev]->converter = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+ if (!midi_devs[devc->my_dev]->converter) {
+ printk(KERN_WARNING "uart401: Failed to allocate memory\n");
+ goto cleanup_midi_devs;
+ }
+ memcpy(midi_devs[devc->my_dev]->converter, &std_midi_synth, sizeof(struct synth_operations));
+ strcpy(midi_devs[devc->my_dev]->info.name, name);
+ midi_devs[devc->my_dev]->converter->id = "UART401";
+ midi_devs[devc->my_dev]->converter->midi_dev = devc->my_dev;
+
+ if (owner)
+ midi_devs[devc->my_dev]->converter->owner = owner;
+
+ hw_config->slots[4] = devc->my_dev;
+ sequencer_init();
+ devc->opened = 0;
+ return 1;
+cleanup_midi_devs:
+ kfree(midi_devs[devc->my_dev]);
+cleanup_unload_mididev:
+ sound_unload_mididev(devc->my_dev);
+cleanup_irq:
+ if (!devc->share_irq)
+ free_irq(devc->irq, devc);
+cleanup_devc:
+ kfree(devc);
+cleanup_region:
+ release_region(hw_config->io_base, 4);
+ return 0;
+}
+
+void unload_uart401(struct address_info *hw_config)
+{
+ uart401_devc *devc;
+ int n=hw_config->slots[4];
+
+ /* Not set up */
+ if(n==-1 || midi_devs[n]==NULL)
+ return;
+
+ /* Not allocated (erm ??) */
+
+ devc = midi_devs[hw_config->slots[4]]->devc;
+ if (devc == NULL)
+ return;
+
+ reset_uart401(devc);
+ release_region(hw_config->io_base, 4);
+
+ if (!devc->share_irq)
+ free_irq(devc->irq, devc);
+ if (devc)
+ {
+ kfree(midi_devs[devc->my_dev]->converter);
+ kfree(midi_devs[devc->my_dev]);
+ kfree(devc);
+ devc = NULL;
+ }
+ /* This kills midi_devs[x] */
+ sound_unload_mididev(hw_config->slots[4]);
+}
+
+EXPORT_SYMBOL(probe_uart401);
+EXPORT_SYMBOL(unload_uart401);
+EXPORT_SYMBOL(uart401intr);
+
+static struct address_info cfg_mpu;
+
+static int io = -1;
+static int irq = -1;
+
+module_param(io, int, 0444);
+module_param(irq, int, 0444);
+
+
+static int __init init_uart401(void)
+{
+ cfg_mpu.irq = irq;
+ cfg_mpu.io_base = io;
+
+ /* Can be loaded either for module use or to provide functions
+ to others */
+ if (cfg_mpu.io_base != -1 && cfg_mpu.irq != -1) {
+ printk(KERN_INFO "MPU-401 UART driver Copyright (C) Hannu Savolainen 1993-1997");
+ if (!probe_uart401(&cfg_mpu, THIS_MODULE))
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_uart401(void)
+{
+ if (cfg_mpu.io_base != -1 && cfg_mpu.irq != -1)
+ unload_uart401(&cfg_mpu);
+}
+
+module_init(init_uart401);
+module_exit(cleanup_uart401);
+
+#ifndef MODULE
+static int __init setup_uart401(char *str)
+{
+ /* io, irq */
+ int ints[3];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ io = ints[1];
+ irq = ints[2];
+
+ return 1;
+}
+
+__setup("uart401=", setup_uart401);
+#endif
+MODULE_LICENSE("GPL");