/*
* serial.c
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
* Isaku Yamahata <yamahata@private.email.ne.jp>,
* George Hansper <ghansper@apana.org.au>,
* Hannu Savolainen
*
* This code is based on the code from ALSA 0.5.9, but heavily rewritten.
*
* 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
*
* Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com
* Added support for the Midiator MS-124T and for the MS-124W in
* Single Addressed (S/A) or Multiple Burst (M/B) mode, with
* power derived either parasitically from the serial port or
* from a separate power supply.
*
* More documentation can be found in serial-u16550.txt.
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>
#include <linux/serial_reg.h>
#include <linux/jiffies.h>
#include <asm/io.h>
MODULE_DESCRIPTION("MIDI serial u16550");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{ALSA, MIDI serial u16550}}");
#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */
#define SNDRV_SERIAL_MS124T 1 /* Midiator MS-124T */
#define SNDRV_SERIAL_MS124W_SA 2 /* Midiator MS-124W in S/A mode */
#define SNDRV_SERIAL_MS124W_MB 3 /* Midiator MS-124W in M/B mode */
#define SNDRV_SERIAL_GENERIC 4 /* Generic Interface */
#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC
static char *adaptor_names[] = {
"Soundcanvas",
"MS-124T",
"MS-124W S/A",
"MS-124W M/B",
"Generic"
};
#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */
#define SNDRV_SERIAL_DROPBUFF 1 /* Non-blocking discard operation */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x3f8,0x2f8,0x3e8,0x2e8 */
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,4,5,7,9,10,11,14,15 */
static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */
static int base[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 115200}; /* baud base */
static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */
static int ins[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */
static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SNDRV_SERIAL_SOUNDCANVAS};
static bool droponfull[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS -1)] = SNDRV_SERIAL_NORMALBUFF };
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Serial MIDI.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for Serial MIDI.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable UART16550A chip.");
module_param_array(port, long, NULL, 0444);
MODULE_PARM_DESC(port, "Port # for UART16550A chip.");
module_param_array(irq, int, NULL, 0444);
MODULE_PARM_DESC(irq, "IRQ # for UART16550A chip.");
module_param_array(speed, int, NULL, 0444);
MODULE_PARM_DESC(speed, "Speed in bauds.");
module_param_array(base, int, NULL, 0444);
MODULE_PARM_DESC(base, "Base for divisor in bauds.");
module_param_array(outs, int, NULL, 0444);
MODULE_PARM_DESC(outs, "Number of MIDI outputs.");
module_param_array(ins, int, NULL, 0444);
MODULE_PARM_DESC(ins, "Number of MIDI inputs.");
module_param_array(droponfull, bool, NULL, 0444);
MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode");
module_param_array(adaptor, int, NULL, 0444);
MODULE_PARM_DESC(adaptor, "Type of adaptor.");
/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/ /* Address outs as 0-3 instead of bitmap */
#define SNDRV_SERIAL_MAX_OUTS 16 /* max 64, min 16 */
#define SNDRV_SERIAL_MAX_INS 16 /* max 64, min 16 */
#define TX_BUFF_SIZE (1<<15) /* Must be 2^n */
#define TX_BUFF_MASK (TX_BUFF_SIZE - 1)
#define SERIAL_MODE_NOT_OPENED (0)
#define SERIAL_MODE_INPUT_OPEN (1 << 0)
#define SERIAL_MODE_OUTPUT_OPEN (1 << 1)
#define SERIAL_MODE_INPUT_TRIGGERED (1 << 2)
#define SERIAL_MODE_OUTPUT_TRIGGERED (1 << 3)
struct snd_uart16550 {
struct snd_card *card;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *midi_output[SNDRV_SERIAL_MAX_OUTS];
struct snd_rawmidi_substream *midi_input[SNDRV_SERIAL_MAX_INS];
int filemode; /* open status of file */
spinlock_t open_lock;
int irq;
unsigned long base;
struct resource *res_base;
unsigned int speed;
unsigned int speed_base;
unsigned char divisor;
unsigned char old_divisor_lsb;
unsigned char old_divisor_msb;
unsigned char old_line_ctrl_reg;
/* parameter for using of write loop */
short int fifo_limit; /* used in uart16550 */
short int fifo_count; /* used in uart16550 */
/* type of adaptor */
int adaptor;
/* inputs */
int prev_in;
unsigned char rstatus;
/* outputs */
int prev_out;
unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS];
/* write buffer and its writing/reading position */
unsigned char tx_buff[TX_BUFF_SIZE];
int buff_in_count;
int buff_in;
int buff_out;
int drop_on_full;
/* wait timer */
unsigned int timer_running:1;
struct timer_list buffer_timer;
};
static struct platform_device *devices[SNDRV_CARDS];
static inline void snd_uart16550_add_timer(struct snd_uart16550 *uart)
{
if (!uart->timer_running) {
/* timer 38600bps * 10bit * 16byte */
uart->buffer_timer.expires = jiffies + (HZ+255)/256;
uart->timer_running = 1;
add_timer(&uart->buffer_timer);
}
}
static inline void snd_uart16550_del_timer(struct snd_uart16550 *uart)
{
if (uart->timer_running) {
del_timer(&uart->buffer_timer);
uart->timer_running = 0;
}
}
/* This macro is only used in snd_uart16550_io_loop */
static inline void snd_uart16550_buffer_output(struct snd_uart16550 *uart)
{
unsigned short buff_out = uart->buff_out;
if (uart->buff_in_count > 0) {
outb(uart->tx_buff[buff_out], uart->base + UART_TX);
uart->fifo_count++;
buff_out++;
buff_out &= TX_BUFF_MASK;
uart->buff_out = buff_out;
uart->buff_in_count--;
}
}
/* This loop should be called with interrupts disabled
* We don't want to interrupt this,
* as we're already handling an interrupt
*/
static void snd_uart16550_io_loop(struct snd_uart16550 * uart)
{
unsigned char c, status;
int substream;
/* recall previous stream */
substream = uart->prev_in;
/* Read Loop */
while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) {
/* while receive data ready */
c = inb(uart->base + UART_RX);
/* keep track of last status byte */
if (c & 0x80)
uart->rstatus = c;
/* handle stream switch */
if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
if (uart->rstatus == 0xf5) {
if (c <= SNDRV_SERIAL_MAX_INS && c > 0)
substream = c - 1;
if (c != 0xf5)
/* prevent future bytes from being
interpreted as streams */
uart->rstatus = 0;
} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN)
&& uart->midi_input[substream])
snd_rawmidi_receive(uart->midi_input[substream],
&c, 1);
} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) &&
uart->midi_input[substream])
snd_rawmidi_receive(uart->midi_input[substream], &c, 1);
if (status & UART_LSR_OE)
snd_printk(KERN_WARNING
"%s: Overrun on device at 0x%lx\n",
uart->rmidi->name, uart->base);
}
/* remember the last stream */
uart->prev_in = substream;
/* no need of check SERIAL_MODE_OUTPUT_OPEN because if not,
buffer is never filled. */
/* Check write status */
if (status & UART_LSR_THRE)
uart->fifo_count = 0;
if (uart->adaptor == SNDRV_SERIAL_MS124W_SA
|| uart->adaptor == SNDRV_SERIAL_GENERIC) {
/* Can't use FIFO, must send only when CTS is true */
status = inb(uart->base + UART_MSR);
while (uart->fifo_count == 0 && (status & UART_MSR_CTS) &&
uart->buff_in_count > 0) {
snd_uart16550_buffer_output(uart);
status = inb(uart->base + UART_MSR);
}
} else {
/* Write loop */
while (uart->fifo_count < uart->fifo_limit /* Can we write ? */
&& uart->buff_in_count > 0) /* Do we want to? */
snd_uart16550_buffer_output(uart);
}
if (uart->irq < 0 && uart->buff_in_count > 0)
snd_uart16550_add_timer(uart);
}
/* NOTES ON SERVICING INTERUPTS
* ---------------------------
* After receiving a interrupt, it is important to indicate to the UART that
* this has been done.
* For a Rx interrupt, this is done by reading the received byte.
* For a Tx interrupt this is done by either:
* a) Writing a byte
* b) Reading the IIR
* It is particularly important to read the IIR if a Tx interrupt is received
* when there is no data in tx_buff[], as in this case there no other
* indication that the interrupt has been serviced, and it remains outstanding
* indefinitely. This has the curious side effect that and no further interrupts
* will be generated from this device AT ALL!!.
* It is also desirable to clear outstanding interrupts when the device is
* opened/closed.
*
*
* Note that some devices need OUT2 to be set before they will generate
* interrupts at all. (Possibly tied to an internal pull-up on CTS?)
*/
static irqreturn_t snd_uart16550_interrupt(int irq, void *dev_id)
{
struct snd_uart16550 *uart;
uart = dev_id;
spin_lock(&uart->open_lock);
if (uart->filemode == SERIAL_MODE_NOT_OPENED) {
spin_unlock(&uart->open_lock);
return IRQ_NONE;
}
/* indicate to the UART that the interrupt has been serviced */
inb(uart->base + UART_IIR);
snd_uart16550_io_loop(uart);
spin_unlock(&uart->open_lock);
return IRQ_HANDLED;
}
/* When the polling mode, this function calls snd_uart16550_io_loop. */
static void snd_uart16550_buffer_timer(unsigned long data)
{
unsigned long flags;
struct snd_uart16550 *uart;
uart = (struct snd_uart16550 *)data;
spin_lock_irqsave(&uart->open_lock, flags);
snd_uart16550_del_timer(uart);
snd_uart16550_io_loop(uart);
spin_unlock_irqrestore(&uart->open_lock, flags);
}
/*
* this method probes, if an uart sits on given port
* return 0 if found
* return negative error if not found
*/
static int __devinit snd_uart16550_detect(struct snd_uart16550 *uart)
{
unsigned long io_base = uart->base;
int ok;
unsigned char c;
/* Do some vague tests for the presence of the uart */
if (io_base == 0 || io_base == SNDRV_AUTO_PORT) {
return -ENODEV; /* Not configured */
}
uart->res_base = request_region(io_base, 8, "Serial MIDI");
if (uart->res_base == NULL) {
snd_printk(KERN_ERR "u16550: can't grab port 0x%lx\n", io_base);
return -EBUSY;
}
/* uart detected unless one of the following tests should fail */
ok = 1;
/* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */
outb(UART_LCR_WLEN8, io_base + UART_LCR); /* Line Control Register */
c = inb(io_base + UART_IER);
/* The top four bits of the IER should always == 0 */
if ((c & 0xf0) != 0)