blob: def5a20eb9b864879103940c8d71b090029e3d8e [file] [log] [blame]
/*
* Generic driver for serial MIDI adapters
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* The adaptor module parameter allows you to select:
* 0 - Roland SoundCanvas (use outs paratemer to specify count of output ports)
*/
#include "adriver.h"
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>
#include <linux/delay.h>
#define SNDRV_SERIAL_MAX_OUTS 16 /* min 16 */
#define TX_BUF_SIZE 256
#define SERIAL_ADAPTOR_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */
#define SERIAL_ADAPTOR_MS124T 1 /* Midiator MS-124T */
#define SERIAL_ADAPTOR_MS124W_SA 2 /* Midiator MS-124W in S/A mode */
#define SERIAL_ADAPTOR_MS124W_MB 3 /* Midiator MS-124W in M/B mode */
#define SERIAL_ADAPTOR_MAX SERIAL_ADAPTOR_MS124W_MB
EXPORT_NO_SYMBOLS;
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Serial MIDI");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
static char *sdev[SNDRV_CARDS] = {"/dev/ttyS0", [1 ... (SNDRV_CARDS - 1)] = ""}; /* serial device */
static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */
static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SERIAL_ADAPTOR_SOUNDCANVAS};
static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */
static int devices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 8 */
static int handshake[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* bool */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for serial device.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for serial device.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable serial device.");
module_param_array(sdev, charp, NULL, 0444);
MODULE_PARM_DESC(sdev, "Device file string for serial device.");
module_param_array(speed, int, NULL, 0444);
MODULE_PARM_DESC(speed, "Speed in bauds.");
module_param_array(adaptor, int, NULL, 0444);
MODULE_PARM_DESC(adaptor, "Type of adaptor.");
module_param_array(outs, int, NULL, 0444);
MODULE_PARM_DESC(outs, "Number of MIDI outputs.");
module_param_array(devices, int, NULL, 0444);
MODULE_PARM_DESC(devices, "Number of devices to attach to the card.");
module_param_array(handshake, int, NULL, 0444);
MODULE_PARM_DESC(handshake, "Do handshaking.");
#define SERIAL_MODE_NOT_OPENED (0)
#define SERIAL_MODE_BIT_INPUT (0)
#define SERIAL_MODE_BIT_OUTPUT (1)
#define SERIAL_MODE_BIT_INPUT_TRIGGERED (2)
#define SERIAL_MODE_BIT_OUTPUT_TRIGGERED (3)
typedef struct _snd_serialmidi {
struct snd_card *card;
char *sdev; /* serial device name (e.g. /dev/ttyS0) */
int dev_idx;
unsigned int speed; /* speed in bauds */
unsigned int adaptor; /* see SERIAL_ADAPTOR_ */
unsigned long mode; /* see SERIAL_MODE_* */
unsigned int outs; /* count of outputs */
unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS];
struct snd_rawmidi *rmidi; /* rawmidi device */
struct snd_rawmidi_substream *substream_input;
struct snd_rawmidi_substream *substream_output;
struct file *file;
struct tty_struct *tty;
struct mutex open_lock;
void (*old_receive_buf)(struct tty_struct *, const unsigned char *cp, char *fp, int count);
void (*old_write_wakeup)(struct tty_struct *);
int old_exclusive;
int old_low_latency;
int handshake;
unsigned char tx_buf[TX_BUF_SIZE];
} serialmidi_t;
static struct platform_device *devptrs[SNDRV_CARDS];
static void ldisc_receive_buf(struct tty_struct *, const unsigned char *cp, char *fp, int count);
static void ldisc_write_wakeup(struct tty_struct *);
static int tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode * inode = file->f_dentry->d_inode;
mm_segment_t fs;
int retval;
if (file->f_op == NULL)
return -ENXIO;
fs = get_fs();
set_fs(get_ds());
retval = file->f_op->ioctl(inode, file, cmd, arg);
set_fs(fs);
return retval;
}
static int open_tty(serialmidi_t *serial, unsigned int mode)
{
int retval = 0;
struct tty_struct *tty;
struct termios old_termios, *ntermios;
struct tty_driver *driver;
int ldisc, speed, cflag;
mutex_lock(&serial->open_lock);
if (serial->tty) {
set_bit(mode, &serial->mode);
goto __end;
}
if (IS_ERR(serial->file = filp_open(serial->sdev, O_RDWR|O_NONBLOCK, 0))) {
retval = PTR_ERR(serial->file);
serial->file = NULL;
goto __end;
}
tty = (struct tty_struct *)serial->file->private_data;
if (tty == NULL || tty->magic != TTY_MAGIC) {
snd_printk(KERN_ERR "device %s has not valid tty", serial->sdev);
retval = -EIO;
goto __end;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) /* correct version? */
driver = tty->driver;
#else
driver = &tty->driver;
#endif
if (driver == NULL || driver->set_termios == NULL) {
snd_printk(KERN_ERR "tty %s has not set_termios", serial->sdev);
retval = -EIO;
goto __end;
}
#ifdef CONFIG_HAVE_TTY_COUNT_ATOMIC
if (atomic_read(&tty->count) > 1) {
#else
if (tty->count > 1) {
#endif
snd_printk(KERN_ERR "tty %s is already used", serial->sdev);
retval = -EBUSY;
goto __end;
}
/* select N_TTY line discipline (for sure) */
ldisc = N_TTY;
if ((retval = tty_ioctl(serial->file, TIOCSETD, (unsigned long)&ldisc)) < 0) {
snd_printk(KERN_ERR "TIOCSETD (N_TTY) failed for tty %s", serial->sdev);
goto __end;
}
/* sanity check, we use disc_data for own purposes */
if (tty->disc_data != NULL) {
snd_printk(KERN_ERR "disc_data are used for tty %s", serial->sdev);
goto __end;
}
switch (serial->speed) {
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
default:
speed = B115200;
break;
}
if (serial->handshake)
cflag = speed | CREAD | CSIZE | CS8 | CRTSCTS | HUPCL;
else
cflag = speed | CREAD | CSIZE | CS8 | HUPCL;
switch (serial->adaptor) {
case SERIAL_ADAPTOR_MS124W_SA:
case SERIAL_ADAPTOR_MS124W_MB:
case SERIAL_ADAPTOR_MS124T:
/* TODO */
break;
}
old_termios = *tty->termios;
ntermios = tty->termios;
ntermios->c_lflag = NOFLSH;
ntermios->c_iflag = IGNBRK | IGNPAR;
ntermios->c_oflag = 0;
ntermios->c_cflag = cflag;
ntermios->c_cc[VEOL] = 0; /* '\r'; */
ntermios->c_cc[VERASE] = 0;
ntermios->c_cc[VKILL] = 0;
ntermios->c_cc[VMIN] = 0;
ntermios->c_cc[VTIME] = 0;
(*driver->set_termios)(tty, &old_termios);
serial->tty = tty;
/* some magic here, we need own receive_buf */
/* it would be probably better to create own line discipline */
/* but this solution is sufficient at the time */
tty->disc_data = serial;
serial->old_receive_buf = tty->ldisc.receive_buf;
tty->ldisc.receive_buf = ldisc_receive_buf;
serial->old_write_wakeup = tty->ldisc.write_wakeup;
tty->ldisc.write_wakeup = ldisc_write_wakeup;
serial->old_low_latency = tty->low_latency;
tty->low_latency = 1;
serial->old_exclusive = test_bit(TTY_EXCLUSIVE, &tty->flags);
set_bit(TTY_EXCLUSIVE, &tty->flags);
set_bit(mode, &serial->mode);
retval = 0;
__end:
if (retval < 0) {
if (serial->file) {
filp_close(serial->file, NULL);
serial->file = NULL;
}
}
mutex_unlock(&serial->open_lock);
return retval;
}
static int close_tty(serialmidi_t *serial, unsigned int mode)
{
unsigned int imode = mode == SERIAL_MODE_BIT_INPUT ?
SERIAL_MODE_BIT_OUTPUT : SERIAL_MODE_BIT_INPUT;
struct tty_struct *tty;
mutex_lock(&serial->open_lock);
clear_bit(mode, &serial->mode);
if (test_bit(imode, &serial->mode))
goto __end;
tty = serial->tty;
if (tty->disc_data == serial)
tty->disc_data = NULL;
if (tty && tty->ldisc.receive_buf == ldisc_receive_buf) {
tty->low_latency = serial->old_low_latency;
tty->ldisc.receive_buf = serial->old_receive_buf;
if (serial->old_exclusive)
set_bit(TTY_EXCLUSIVE, &tty->flags);
else
clear_bit(TTY_EXCLUSIVE, &tty->flags);
}
filp_close(serial->file, NULL);
serial->tty = NULL;
serial->file = NULL;
__end:
mutex_unlock(&serial->open_lock);
return 0;
}
static void ldisc_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{
serialmidi_t *serial = tty->disc_data;
if (serial == NULL)
return;
if (!test_bit(SERIAL_MODE_BIT_INPUT_TRIGGERED, &serial->mode))
return;
snd_rawmidi_receive(serial->substream_input, cp, count);
}
static void tx_loop(serialmidi_t *serial)
{
struct tty_struct *tty;
char *buf = serial->tx_buf;
int count;
struct tty_driver *driver;
tty = serial->tty;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) /* correct version? */
driver = tty->driver;
#else
driver = &tty->driver;
#endif
if (driver == NULL)
return;
if (down_trylock(&tty->atomic_write))
return;
while (1) {
count = driver->write_room(tty);
if (count <= 0) {
up(&tty->atomic_write);
return;
}
count = count > TX_BUF_SIZE ? TX_BUF_SIZE : count;
count = snd_rawmidi_transmit_peek(serial->substream_output, buf, count);
if (count > 0) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 10)
driver->write(tty, buf, count);
#else
count = driver->write(tty, 0, buf, count);
#endif
snd_rawmidi_transmit_ack(serial->substream_output, count);
} else {
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
break;
}
}
up(&tty->atomic_write);
}
static void ldisc_write_wakeup(struct tty_struct *tty)
{
serialmidi_t *serial = tty->disc_data;
tx_loop(serial);
}
static void snd_serialmidi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
serialmidi_t *serial = substream->rmidi->private_data;
if (up) {
set_bit(SERIAL_MODE_BIT_OUTPUT_TRIGGERED, &serial->mode);
set_bit(TTY_DO_WRITE_WAKEUP, &serial->tty->flags);
tx_loop(serial);
} else {
clear_bit(TTY_DO_WRITE_WAKEUP, &serial->tty->flags);
clear_bit(SERIAL_MODE_BIT_OUTPUT_TRIGGERED, &serial->mode);
}
}
static void snd_serialmidi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
serialmidi_t *serial = substream->rmidi->private_data;
if (up) {
set_bit(SERIAL_MODE_BIT_INPUT_TRIGGERED, &serial->mode);
} else {
clear_bit(SERIAL_MODE_BIT_INPUT_TRIGGERED, &serial->mode);
}
}
static int snd_serialmidi_output_open(struct snd_rawmidi_substream *substream)
{
serialmidi_t *serial = substream->rmidi->private_data;
int err;
if ((err = open_tty(serial, SERIAL_MODE_BIT_OUTPUT)) < 0)
return err;
serial->substream_output = substream;
return 0;
}
static int snd_serialmidi_output_close(struct snd_rawmidi_substream *substream)
{
serialmidi_t *serial = substream->rmidi->private_data;
serial->substream_output = NULL;
return close_tty(serial, SERIAL_MODE_BIT_OUTPUT);
}
static int snd_serialmidi_input_open(struct snd_rawmidi_substream *substream)
{
serialmidi_t *serial = substream->rmidi->private_data;
int err;
if ((err = open_tty(serial, SERIAL_MODE_BIT_INPUT)) < 0)
return err;
serial->substream_input = substream;
return 0;
}
static int snd_serialmidi_input_close(struct snd_rawmidi_substream *substream)
{
serialmidi_t *serial = substream->rmidi->private_data;
serial->substream_input = NULL;
return close_tty(serial, SERIAL_MODE_BIT_INPUT);
}
static struct snd_rawmidi_ops snd_serialmidi_output =
{
.open = snd_serialmidi_output_open,
.close = snd_serialmidi_output_close,
.trigger = snd_serialmidi_output_trigger,
};
static struct snd_rawmidi_ops snd_serialmidi_input =
{
.open = snd_serialmidi_input_open,
.close = snd_serialmidi_input_close,
.trigger = snd_serialmidi_input_trigger,
};
static int snd_serialmidi_free(serialmidi_t *serial)
{
if (serial->sdev);
kfree(serial->sdev);
kfree(serial);
return 0;
}
static int snd_serialmidi_dev_free(struct snd_device *device)
{
serialmidi_t *serial = device->device_data;
return snd_serialmidi_free(serial);
}
static int __init snd_serialmidi_rmidi(serialmidi_t *serial)
{
struct snd_rawmidi *rrawmidi;
int err;
if ((err = snd_rawmidi_new(serial->card, "UART Serial MIDI", serial->dev_idx, serial->outs, 1, &rrawmidi)) < 0)
return err;
snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_serialmidi_input);
snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_serialmidi_output);
snprintf(rrawmidi->name, sizeof(rrawmidi->name), "%s %d", serial->card->shortname, serial->dev_idx);
rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
rrawmidi->private_data = serial;
serial->rmidi = rrawmidi;
return 0;
}
static int __init snd_serialmidi_create(struct snd_card *card, const char *sdev,
unsigned int speed, unsigned int adaptor,
unsigned int outs, int idx, int hshake)
{
static struct snd_device_ops ops = {
.dev_free = snd_serialmidi_dev_free,
};
serialmidi_t *serial;
int err;
switch (adaptor) {
case SERIAL_ADAPTOR_SOUNDCANVAS:
break;
case SERIAL_ADAPTOR_MS124T:
case SERIAL_ADAPTOR_MS124W_SA:
outs = 1;
break;
case SERIAL_ADAPTOR_MS124W_MB:
outs = 16;
break;
default:
snd_printk(KERN_ERR "Adaptor type is out of range 0-%d (%d)\n",
SERIAL_ADAPTOR_MAX, adaptor);
return -ENODEV;
}
if (outs < 1)
outs = 1;
else if (outs > 16)
outs = 16;
if ((serial = kzalloc(sizeof(*serial), GFP_KERNEL)) == NULL)
return -ENOMEM;
mutex_init(&serial->open_lock);
serial->card = card;
serial->dev_idx = idx;
serial->sdev = kstrdup(sdev, GFP_KERNEL);
if (serial->sdev == NULL) {
snd_serialmidi_free(serial);
return -ENOMEM;
}
serial->adaptor = adaptor;
serial->speed = speed;
serial->outs = outs;
serial->handshake = hshake;
memset(serial->prev_status, 0x80, sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS);
/* Register device */
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, serial, &ops)) < 0) {
snd_serialmidi_free(serial);
return err;
}
if ((err = snd_serialmidi_rmidi(serial)) < 0) {
snd_device_free(card, serial);
return err;
}
return 0;
}
static int __init snd_serial_probe(struct platform_device *devptr)
{
struct snd_card *card;
int err;
int dev = devptr->id;
err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
if (err < 0)
return err;
strcpy(card->driver, "Serial MIDI");
if (id[dev] && *id[dev])
snprintf(card->shortname, sizeof(card->shortname), "%s", id[dev]);
else
strcpy(card->shortname, card->driver);
if (devices[dev] > 1) {
int i, start_dev;
char devname[32];
/* assign multiple devices to a single card */
if (devices[dev] > 8) {
printk(KERN_ERR "serialmidi: invalid devices %d\n", devices[dev]);
snd_card_free(card);
return -EINVAL;
}
/* device name mangling */
strncpy(devname, sdev[dev], sizeof(devname));
devname[31] = 0;
i = strlen(devname);
if (i > 0) i--;
if (devname[i] >= '0' && devname[i] <= '9') {
for (; i > 0; i--)
if (devname[i] < '0' || devname[i] > '9') {
i++;
break;
}
start_dev = simple_strtoul(devname + i, NULL, 0);
devname[i] = 0;
} else
start_dev = 0;
for (i = 0; i < devices[dev]; i++) {
char devname2[33];
sprintf(devname2, "%s%d", devname, start_dev + i);
if ((err = snd_serialmidi_create(card, devname2, speed[dev],
adaptor[dev], outs[dev], i,
handshake[dev])) < 0) {
snd_card_free(card);
return err;
}
}
} else {
if ((err = snd_serialmidi_create(card, sdev[dev], speed[dev],
adaptor[dev], outs[dev], 0,
handshake[dev])) < 0) {
snd_card_free(card);
return err;
}
}
sprintf(card->longname, "%s at %s", card->shortname, sdev[dev]);
snd_card_set_dev(card, &devptr->dev);
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
platform_set_drvdata(devptr, card);
return 0;
}
static int snd_serial_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
platform_set_drvdata(devptr, NULL);
return 0;
}
#define SND_SERIAL_DRIVER "snd_serial"
static struct platform_driver snd_serial_driver = {
.probe = snd_serial_probe,
.remove = snd_serial_remove,
.driver = {
.name = SND_SERIAL_DRIVER
},
};
static void __init_or_module snd_serial_unregister_all(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(devices); ++i)
platform_device_unregister(devptrs[i]);
platform_driver_unregister(&snd_serial_driver);
}
static int __init alsa_card_serialmidi_init(void)
{
int i, cards, err;
if ((err = platform_driver_register(&snd_serial_driver)) < 0)
return err;
cards = 0;
for (i = 0; i < SNDRV_CARDS; i++) {
struct platform_device *device;
if (! enable[i])
continue;
device = platform_device_register_simple(SND_SERIAL_DRIVER,
i, NULL, 0);
if (IS_ERR(device)) {
err = PTR_ERR(device);
goto errout;
}
devptrs[i] = device;
cards++;
}
if (!cards) {
#ifdef MODULE
printk(KERN_ERR "serial MIDI device not found or device busy\n");
#endif
err = -ENODEV;
goto errout;
}
return 0;
errout:
snd_serial_unregister_all();
return err;
}
static void __exit alsa_card_serialmidi_exit(void)
{
snd_serial_unregister_all();
}
module_init(alsa_card_serialmidi_init)
module_exit(alsa_card_serialmidi_exit)