diff options
Diffstat (limited to 'kernel/drv/oss_usb/ossusb_ymhmidi.c')
-rw-r--r-- | kernel/drv/oss_usb/ossusb_ymhmidi.c | 653 |
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; +} |