summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_usb/ossusb_ymhmidi.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_usb/ossusb_ymhmidi.c')
-rw-r--r--kernel/drv/oss_usb/ossusb_ymhmidi.c653
1 files changed, 653 insertions, 0 deletions
diff --git a/kernel/drv/oss_usb/ossusb_ymhmidi.c b/kernel/drv/oss_usb/ossusb_ymhmidi.c
new file mode 100644
index 0000000..89f749b
--- /dev/null
+++ b/kernel/drv/oss_usb/ossusb_ymhmidi.c
@@ -0,0 +1,653 @@
+/*
+ * Purpose: Dedicated driver for Yamaha USB MIDI devices
+ */
+/*
+ *
+ * This file is part of Open Sound System.
+ *
+ * Copyright (C) 4Front Technologies 1996-2008.
+ *
+ * This this source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ */
+
+#include "oss_config.h"
+#include "midi_core.h"
+#include "ossusb.h"
+#include "midiparser.h"
+
+typedef struct ymhusb_devc ymhusb_devc;
+
+#define MAX_INDEVS 1
+#define MAX_OUTDEVS 1
+#define TMPBUF_SIZE 64
+#define TMPBUF_NSLOTS 24
+
+typedef unsigned char tmpbuf_slot_t[TMPBUF_SIZE];
+#define OUTBUF_SIZE (TMPBUF_NSLOTS*TMPBUF_SIZE)
+
+typedef struct
+{
+ ymhusb_devc *devc;
+ oss_device_t *osdev;
+ udi_usb_devc *usbdev;
+ oss_mutex_t mutex;
+
+ int portnum;
+ int midi_dev;
+ int open_mode;
+
+ void *endpoint_desc;
+ udi_endpoint_handle_t *endpoint_handle;
+ udi_usb_request_t *datapipe;
+
+ oss_midi_inputbyte_t midi_input_intr;
+ unsigned char *tmpbuf;
+ oss_dma_handle_t tmpbuf_dma_handle;
+ int tmp_len;
+ midiparser_common_p parser;
+
+ volatile int output_busy;
+
+ /* Output buffer */
+ tmpbuf_slot_t *outbuf;
+ oss_dma_handle_t outbuf_dma_handle;
+ int buf_t, buf_h;
+} ymhusb_midic;
+
+struct ymhusb_devc
+{
+ special_unload_t unload_func;
+
+ oss_device_t *osdev;
+ udi_usb_devc *usbdev;
+
+ oss_mutex_t mutex;
+
+ int n_inputs, n_outputs;
+ ymhusb_midic indevs[MAX_INDEVS], outdevs[MAX_OUTDEVS];
+};
+
+static void
+ymhusb_unload (void *d)
+{
+ ymhusb_devc *devc = (ymhusb_devc *) d;
+ int i;
+
+ for (i = 0; i < devc->n_inputs; i++)
+ {
+ MUTEX_CLEANUP (devc->indevs[i].mutex);
+ }
+ for (i = 0; i < devc->n_outputs; i++)
+ {
+ MUTEX_CLEANUP (devc->outdevs[i].mutex);
+ }
+ MUTEX_CLEANUP (devc->mutex);
+}
+
+static int ymhmidi_start_input (ymhusb_devc * devc, ymhusb_midic * midic);
+
+void
+ymhmidi_record_callback (udi_usb_request_t * request, void *arg)
+{
+ ymhusb_midic *midic = arg;
+ ymhusb_devc *devc = midic->devc;
+ unsigned char *data;
+ int l;
+
+ l = udi_usb_request_actlen (request);
+ data = udi_usb_request_actdata (request);
+
+ if (l == 0)
+ goto restart;
+
+ if (midi_devs[midic->midi_dev]->event_input != NULL)
+ midi_devs[midic->midi_dev]->event_input (midic->midi_dev, data, l);
+
+restart:
+ if (midic->open_mode & OPEN_READ)
+ ymhmidi_start_input (devc, midic);
+}
+
+static int ymhmidi_submit_output (ymhusb_devc * devc, ymhusb_midic * midic);
+
+ /*ARGSUSED*/ void
+ymhmidi_play_callback (udi_usb_request_t * request, void *arg)
+{
+ ymhusb_midic *midic = arg;
+ ymhusb_devc *devc = midic->devc;
+
+ midic->output_busy = 0;
+ ymhmidi_submit_output (devc, midic);
+}
+
+ /*ARGSUSED*/ static int
+ymhmidi_start_input (ymhusb_devc * devc, ymhusb_midic * midic)
+{
+ int err;
+
+ if ((err =
+ udi_usb_submit_request (midic->datapipe, ymhmidi_record_callback,
+ midic, midic->endpoint_handle,
+ UDI_USBXFER_BULK_READ, midic->tmpbuf,
+ TMPBUF_SIZE)) < 0)
+ {
+ return err;
+ }
+
+ return 0;
+}
+
+ /*ARGSUSED*/ static int
+ymhmidi_submit_output (ymhusb_devc * devc, ymhusb_midic * midic)
+{
+ int err;
+ int n = midic->buf_t;
+
+ if (midic->buf_h == midic->buf_t)
+ return 0;
+
+ if ((err =
+ udi_usb_submit_request (midic->datapipe, ymhmidi_play_callback, midic,
+ midic->endpoint_handle, UDI_USBXFER_BULK_WRITE,
+ midic->outbuf[n], TMPBUF_SIZE)) < 0)
+ {
+ cmn_err (CE_WARN, "Submit USB MIDI request failed\n");
+ return err;
+ }
+
+ midic->buf_t = (n + 1) % TMPBUF_NSLOTS;;
+ midic->output_busy = 1;
+
+ return 0;
+}
+
+static int
+ymhmidi_put_output (ymhusb_devc * devc, ymhusb_midic * midic)
+{
+ int n = midic->buf_h;
+
+ midic->buf_h = (n + 1) % TMPBUF_NSLOTS;
+
+ memcpy (midic->outbuf[n], midic->tmpbuf, TMPBUF_SIZE);
+ midic->tmp_len = 0;
+ memset (midic->tmpbuf, 0, TMPBUF_SIZE);
+
+ if (midic->output_busy)
+ return 0;
+
+ ymhmidi_submit_output (devc, midic);
+
+ return 0;
+}
+
+ /*ARGSUSED*/ static void
+ymhusb_close_input (int dev, int mode)
+{
+ oss_native_word flags;
+ ymhusb_midic *midic;
+
+ midic = midi_devs[dev]->devc;
+
+ MUTEX_ENTER_IRQDISABLE (midic->mutex, flags);
+ midic->open_mode = 0;
+ midic->midi_input_intr = NULL;
+ udi_usb_free_request (midic->datapipe);
+ udi_close_endpoint (midic->endpoint_handle);
+ if (midic->tmpbuf != NULL)
+ CONTIG_FREE (midic->osdev, midic->tmpbuf, TMPBUF_SIZE, midic->tmpbuf_dma_handle);
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+}
+
+ /*ARGSUSED*/ static int
+ymhusb_open_input (int dev, int mode, oss_midi_inputbyte_t inputbyte,
+ oss_midi_inputbuf_t inputbuf,
+ oss_midi_outputintr_t outputintr)
+{
+ oss_native_word flags, phaddr;
+ ymhusb_midic *midic;
+ ymhusb_devc *devc;
+
+ midic = midi_devs[dev]->devc;
+ devc = midic->devc;
+
+ MUTEX_ENTER_IRQDISABLE (midic->mutex, flags);
+ if (midic->open_mode)
+ {
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+ return OSS_EBUSY;
+ }
+
+ midic->open_mode = mode;
+ midic->midi_input_intr = inputbyte;
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+
+ midic->tmpbuf =
+ CONTIG_MALLOC (midic->osdev, TMPBUF_SIZE, MEMLIMIT_32BITS, &phaddr, midic->tmpbuf_dma_handle);
+ memset (midic->tmpbuf, 0, TMPBUF_SIZE);
+
+ if ((midic->endpoint_handle =
+ udi_open_endpoint (midic->usbdev, midic->endpoint_desc)) == NULL)
+ {
+ midic->open_mode = 0;
+ midic->midi_input_intr = NULL;
+ cmn_err (CE_WARN, "Cannot open audio pipe\n");
+ return OSS_ENOMEM;
+ }
+ if ((midic->datapipe =
+ udi_usb_alloc_request (midic->usbdev, midic->endpoint_handle, 1,
+ UDI_USBXFER_BULK_READ)) == NULL)
+ {
+ return OSS_EIO;
+ }
+
+ return ymhmidi_start_input (devc, midic);
+}
+
+static void
+ymhusb_flush_output (int dev)
+{
+ oss_native_word flags;
+ ymhusb_midic *midic;
+ ymhusb_devc *devc;
+ int next;
+
+ midic = midi_devs[dev]->devc;
+ devc = midic->devc;
+
+ if (midic->tmp_len == 0)
+ return;
+ next = (midic->buf_t + 1) % TMPBUF_NSLOTS;
+ if (next == midic->buf_h) /* Buffer full */
+ return;
+
+ MUTEX_ENTER_IRQDISABLE (midic->mutex, flags);
+ ymhmidi_put_output (devc, midic);
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+}
+
+static int
+ymhusb_wait_output (int dev)
+{
+ ymhusb_midic *midic;
+
+ ymhusb_flush_output (dev);
+
+ midic = midi_devs[dev]->devc;
+
+ if (midic->output_busy)
+ return 1;
+
+ if (midic->buf_t == midic->buf_h)
+ return 0; /* Not busy */
+
+ return 1;
+}
+
+ /*ARGSUSED*/ static void
+ymhusb_close_output (int dev, int mode)
+{
+ oss_native_word flags;
+ ymhusb_midic *midic;
+
+ midic = midi_devs[dev]->devc;
+
+ ymhusb_flush_output (dev);
+
+ MUTEX_ENTER_IRQDISABLE (midic->mutex, flags);
+ midic->open_mode = 0;
+ midic->midi_input_intr = NULL;
+ udi_usb_free_request (midic->datapipe);
+ udi_close_endpoint (midic->endpoint_handle);
+ if (midic->outbuf != NULL)
+ CONTIG_FREE (midic->osdev, midic->outbuf, OUTBUF_SIZE, midic->outbuf_dma_handle);
+ if (midic->tmpbuf != NULL)
+ KERNEL_FREE (midic->tmpbuf);
+ midiparser_unalloc (midic->parser);
+ midic->parser = NULL;
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+}
+
+static void
+out_event (ymhusb_midic * midic, unsigned char a, unsigned char b,
+ unsigned char c, unsigned char d)
+{
+ unsigned char *e;
+ ymhusb_devc *devc = midic->devc;
+
+ if (midic->tmp_len > TMPBUF_SIZE - 4)
+ ymhmidi_put_output (devc, midic);
+
+ e = midic->tmpbuf + midic->tmp_len;
+ *e++ = a;
+ *e++ = b;
+ *e++ = c;
+ *e++ = d;
+
+ midic->tmp_len += 4;
+
+ if (midic->tmp_len >= TMPBUF_SIZE)
+ ymhmidi_put_output (devc, midic);
+}
+
+void
+parser_cb (void *context, int category, unsigned char msg, unsigned char ch,
+ unsigned char *parms, int len)
+{
+ ymhusb_midic *midic = context;
+
+ if (category == CAT_VOICE)
+ {
+ switch (msg)
+ {
+ case MIDI_NOTEON:
+ out_event (midic, 0x9, msg | ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_NOTEOFF:
+ out_event (midic, 0x8, msg | ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_KEY_PRESSURE:
+ out_event (midic, 0xa, msg | ch, parms[0], parms[1]);
+ break;
+ }
+
+ return;
+ }
+
+ if (category == CAT_CHN)
+ {
+ switch (msg)
+ {
+ case MIDI_CTL_CHANGE:
+ out_event (midic, 0xb, msg | ch, parms[0], parms[1]);
+ break;
+
+ case MIDI_PGM_CHANGE:
+ out_event (midic, 0xc, msg | ch, parms[0], 0);
+ break;
+
+ case MIDI_CHN_PRESSURE:
+ out_event (midic, 0xd, msg | ch, parms[0], 0);
+ break;
+
+ case MIDI_PITCH_BEND:
+ out_event (midic, 0xe, msg | ch, parms[0], parms[1]);
+ break;
+ }
+ return;
+ }
+
+ if (category == CAT_REALTIME)
+ {
+ out_event (midic, 0xf, msg | ch, 0, 0);
+ return;
+ }
+
+ if (category == CAT_MTC)
+ {
+ out_event (midic, 0x2, 0xf1, parms[0], 0);
+ return;
+ }
+
+ if (category == CAT_SYSEX)
+ {
+ int l = len, n;
+ unsigned char *d = parms;
+
+ while (l > 0)
+ {
+ n = l;
+ if (n > 3)
+ n = 3;
+
+ switch (n)
+ {
+ case 3:
+ out_event (midic, 0x4, d[0], d[1], d[2]);
+ break;
+ case 2:
+ out_event (midic, 0x6, d[0], d[1], 0);
+ break;
+ case 1:
+ out_event (midic, 0x5, d[0], 0, 0);
+ break;
+ }
+
+ l -= n;
+ d += n;
+ }
+ return;
+ }
+}
+
+ /*ARGSUSED*/ static int
+ymhusb_open_output (int dev, int mode, oss_midi_inputbyte_t inputbyte,
+ oss_midi_inputbuf_t inputbuf,
+ oss_midi_outputintr_t outputintr)
+{
+ oss_native_word flags, phaddr;
+ ymhusb_midic *midic;
+
+ midic = midi_devs[dev]->devc;
+
+ MUTEX_ENTER_IRQDISABLE (midic->mutex, flags);
+ if (midic->open_mode)
+ {
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+ return OSS_EBUSY;
+ }
+
+ midic->open_mode = mode;
+ midic->output_busy = 0;
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+
+ midic->tmpbuf = KERNEL_MALLOC (TMPBUF_SIZE);
+ memset (midic->tmpbuf, 0, TMPBUF_SIZE);
+ midic->outbuf =
+ CONTIG_MALLOC (midic->osdev, OUTBUF_SIZE, MEMLIMIT_32BITS, &phaddr, midic->outbuf_dma_handle);
+ midic->tmp_len = 0;
+ midic->buf_h = midic->buf_t = 0; /* Empty buffer */
+ memset (midic->outbuf, 0, OUTBUF_SIZE);
+
+ if ((midic->parser = midiparser_create (parser_cb, midic)) == NULL)
+ {
+ midic->open_mode = 0;
+ midic->midi_input_intr = NULL;
+ cmn_err (CE_WARN, "Cannot create MIDI parser\n");
+ return OSS_ENOMEM;
+ }
+
+ if ((midic->endpoint_handle =
+ udi_open_endpoint (midic->usbdev, midic->endpoint_desc)) == NULL)
+ {
+ midic->open_mode = 0;
+ midic->midi_input_intr = NULL;
+ cmn_err (CE_WARN, "Cannot open audio pipe\n");
+ return OSS_ENOMEM;
+ }
+ if ((midic->datapipe =
+ udi_usb_alloc_request (midic->usbdev, midic->endpoint_handle, 1,
+ UDI_USBXFER_BULK_WRITE)) == NULL)
+ {
+ return OSS_EIO;
+ }
+
+ return 0;
+}
+
+static int
+ymhusb_out (int dev, unsigned char midi_byte)
+{
+ ymhusb_midic *midic = midi_devs[dev]->devc;
+ oss_native_word flags;
+ int next;
+
+ next = (midic->buf_t + 1) % TMPBUF_NSLOTS;
+ if (next == midic->buf_h) /* Buffer full */
+ return 0; /* Try again later */
+
+ MUTEX_ENTER_IRQDISABLE (midic->mutex, flags);
+ midiparser_input (midic->parser, midi_byte);
+ MUTEX_EXIT_IRQRESTORE (midic->mutex, flags);
+
+ return 1;
+}
+
+ /*ARGSUSED*/ static int
+ymhusb_ioctl (int dev, unsigned cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static midi_driver_t ymhusb_input_driver = {
+ ymhusb_open_input,
+ ymhusb_close_input,
+ ymhusb_ioctl,
+ ymhusb_out,
+};
+
+static void
+add_input_device (ymhusb_devc * devc, char *name, void *desc, int caps)
+{
+ int n;
+ ymhusb_midic *midic;
+ char tmp[128];
+
+ if (devc->n_inputs >= MAX_INDEVS)
+ {
+ cmn_err (CE_WARN, "Yamaha MIDI: Too many inputs\n");
+ return;
+ }
+
+ n = devc->n_inputs++;
+
+ midic = &devc->indevs[n];
+
+ midic->devc = devc;
+ midic->osdev = devc->osdev;
+ MUTEX_INIT (midic->osdev, midic->mutex, MH_DRV + 1);
+ midic->endpoint_desc = desc;
+ midic->portnum = n;
+ midic->usbdev = devc->usbdev;
+
+ sprintf (tmp, "%s input %d", name, n);
+ if ((midic->midi_dev =
+ oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "YMHMIDI", tmp,
+ &ymhusb_input_driver,
+ sizeof (ymhusb_input_driver),
+ MFLAG_INPUT, midic, midic->osdev)) < 0)
+ {
+ cmn_err (CE_CONT, "Failed to install MIDI device\n");
+ return;
+ }
+ midi_devs[midic->midi_dev]->caps |= caps;
+}
+
+static midi_driver_t ymhusb_output_driver = {
+ ymhusb_open_output,
+ ymhusb_close_output,
+ ymhusb_ioctl,
+ ymhusb_out,
+ NULL,
+ 0,
+ ymhusb_flush_output,
+ ymhusb_wait_output
+};
+
+static void
+add_output_device (ymhusb_devc * devc, char *name, void *desc, int caps)
+{
+ int n;
+ ymhusb_midic *midic;
+ char tmp[128];
+
+ if (devc->n_outputs >= MAX_OUTDEVS)
+ {
+ cmn_err (CE_WARN, "Yamaha MIDI: Too many outputs\n");
+ return;
+ }
+
+ n = devc->n_outputs++;
+
+ midic = &devc->outdevs[n];
+
+ midic->devc = devc;
+ midic->osdev = devc->osdev;
+ MUTEX_INIT (midic->osdev, midic->mutex, MH_DRV + 1);
+ midic->endpoint_desc = desc;
+ midic->portnum = n;
+ midic->usbdev = devc->usbdev;
+
+ sprintf (tmp, "%s output %d", name, n);
+ if ((midic->midi_dev =
+ oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "YMHMIDI", tmp,
+ &ymhusb_output_driver,
+ sizeof (ymhusb_output_driver),
+ MFLAG_OUTPUT, midic, midic->osdev)) < 0)
+ {
+ cmn_err (CE_CONT, "Failed to install MIDI device\n");
+ return;
+ }
+
+ midi_devs[midic->midi_dev]->caps |= caps;
+}
+
+void *
+yamaha_usb_midi_driver (ossusb_devc * usb_devc)
+{
+ ymhusb_devc *devc;
+ int i;
+ unsigned char *desc;
+ int desc_len;
+ int caps = MIDI_CAP_EXTERNAL;
+ unsigned int devid;
+
+ char *name = "Yamaha USB MIDI";
+
+ if (usb_devc->dev_name != NULL)
+ name = usb_devc->dev_name;
+
+ if ((devc = PMALLOC (usb_devc->osdev, sizeof (*devc))) == NULL)
+ {
+ cmn_err (CE_WARN, "Yamaha MIDI: Out of memory\n");
+ return NULL;
+ }
+
+ memset (devc, 0, sizeof (*devc));
+
+ devc->unload_func = ymhusb_unload;
+ devc->osdev = usb_devc->osdev;
+ devc->usbdev = usb_devc->last_usbdev;
+ MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);
+ devid = (usb_devc->vendor << 16) | usb_devc->product;
+
+ DDB (cmn_err
+ (CE_CONT, "Attaching Yamaha MIDI device %08x (%s)\n", devid, name));
+
+ switch (devid)
+ {
+ case 0x0499101e: /* PSR-K1 keyboard */
+ caps |= MIDI_CAP_PTOP;
+ break;
+ }
+
+ for (i = 0; i < 32; i++)
+ if ((desc =
+ udi_usbdev_get_endpoint (devc->usbdev, 0, i, &desc_len)) != NULL)
+ {
+ if (desc[2] & 0x80)
+ {
+ add_input_device (devc, name, desc, caps);
+ }
+ else
+ {
+ add_output_device (devc, name, desc, caps);
+ }
+ }
+
+ return devc;
+}