// SPDX-License-Identifier: GPL-2.0-or-later /* * 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. * * 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.
*/
/* parameter for using of write loop */ shortint fifo_limit; /* used in uart16550 */ shortint fifo_count; /* used in uart16550 */
/* type of adaptor */ int adaptor;
/* inputs */ int prev_in; unsignedchar rstatus;
/* outputs */ int prev_out; unsignedchar prev_status[SNDRV_SERIAL_MAX_OUTS];
/* write buffer and its writing/reading position */ unsignedchar tx_buff[TX_BUFF_SIZE]; int buff_in_count; int buff_in; int buff_out; int drop_on_full;
/* This macro is only used in snd_uart16550_io_loop */ staticinlinevoid snd_uart16550_buffer_output(struct snd_uart16550 *uart)
{ unsignedshort 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
*/ staticvoid snd_uart16550_io_loop(struct snd_uart16550 * uart)
{ unsignedchar c, status; int substream;
/* 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;
} elseif ((uart->filemode & SERIAL_MODE_INPUT_OPEN)
&& uart->midi_input[substream])
snd_rawmidi_receive(uart->midi_input[substream],
&c, 1);
} elseif ((uart->filemode & SERIAL_MODE_INPUT_OPEN) &&
uart->midi_input[substream])
snd_rawmidi_receive(uart->midi_input[substream], &c, 1);
if (status & UART_LSR_OE)
dev_warn(uart->card->dev, "%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. */ staticvoid snd_uart16550_buffer_timer(struct timer_list *t)
{ unsignedlong flags; struct snd_uart16550 *uart;
/* * this method probes, if an uart sits on given port * return 0 if found * return negative error if not found
*/ staticint snd_uart16550_detect(struct snd_uart16550 *uart)
{ unsignedlong io_base = uart->base; int ok; unsignedchar c;
/* Do some vague tests for the presence of the uart */ if (io_base == 0 || io_base == SNDRV_AUTO_PORT) { return -ENODEV; /* Not configured */
}
if (!devm_request_region(uart->card->dev, io_base, 8, "Serial MIDI")) {
dev_err(uart->card->dev, "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)
ok = 0; /* failed */
outb(0xaa, io_base + UART_SCR); /* Write arbitrary data into the scratch reg */
c = inb(io_base + UART_SCR); /* If it comes back, it's OK */ if (c != 0xaa)
ok = 0; /* failed */
outb(0x55, io_base + UART_SCR); /* Write arbitrary data into the scratch reg */
c = inb(io_base + UART_SCR); /* If it comes back, it's OK */ if (c != 0x55)
ok = 0; /* failed */
outb(UART_FCR_ENABLE_FIFO /* Enable FIFO's (if available) */
| UART_FCR_CLEAR_RCVR /* Clear receiver FIFO */
| UART_FCR_CLEAR_XMIT /* Clear transmitter FIFO */
| UART_FCR_TRIGGER_4 /* Set FIFO trigger at 4-bytes */ /* NOTE: interrupt generated after T=(time)4-bytes * if less than UART_FCR_TRIGGER bytes received
*/
,uart->base + UART_FCR); /* FIFO Control Register */
if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0)
uart->fifo_limit = 16; if (uart->divisor != 0) {
uart->old_line_ctrl_reg = inb(uart->base + UART_LCR);
outb(UART_LCR_DLAB /* Divisor latch access bit */
,uart->base + UART_LCR); /* Line Control Register */
uart->old_divisor_lsb = inb(uart->base + UART_DLL);
uart->old_divisor_msb = inb(uart->base + UART_DLM);
outb(uart->divisor
,uart->base + UART_DLL); /* Divisor Latch Low */
outb(0
,uart->base + UART_DLM); /* Divisor Latch High */ /* DLAB is reset to 0 in next outb() */
} /* Set serial parameters (parity off, etc) */
outb(UART_LCR_WLEN8 /* 8 data-bits */
| 0 /* 1 stop-bit */
| 0 /* parity off */
| 0 /* DLAB = 0 */
,uart->base + UART_LCR); /* Line Control Register */
switch (uart->adaptor) { default:
outb(UART_MCR_RTS /* Set Request-To-Send line active */
| UART_MCR_DTR /* Set Data-Terminal-Ready line active */
| UART_MCR_OUT2 /* Set OUT2 - not always required, but when * it is, it is ESSENTIAL for enabling interrupts
*/
,uart->base + UART_MCR); /* Modem Control Register */ break; case SNDRV_SERIAL_MS124W_SA: case SNDRV_SERIAL_MS124W_MB: /* MS-124W can draw power from RTS and DTR if they
are in opposite states. */
outb(UART_MCR_RTS | (0&UART_MCR_DTR) | UART_MCR_OUT2,
uart->base + UART_MCR); break; case SNDRV_SERIAL_MS124T: /* MS-124T can draw power from RTS and/or DTR (preferably
both) if they are both asserted. */
outb(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2,
uart->base + UART_MCR); break;
}
switch (uart->adaptor) { default:
outb((0 & UART_MCR_RTS) /* Deactivate Request-To-Send line */
|(0 & UART_MCR_DTR) /* Deactivate Data-Terminal-Ready line */
|(0 & UART_MCR_OUT2) /* Deactivate OUT2 */
,uart->base + UART_MCR); /* Modem Control Register */ break; case SNDRV_SERIAL_MS124W_SA: case SNDRV_SERIAL_MS124W_MB: /* MS-124W can draw power from RTS and DTR if they
are in opposite states; leave it powered. */
outb(UART_MCR_RTS | (0&UART_MCR_DTR) | (0&UART_MCR_OUT2),
uart->base + UART_MCR); break; case SNDRV_SERIAL_MS124T: /* MS-124T can draw power from RTS and/or DTR (preferably
both) if they are both asserted; leave it powered. */
outb(UART_MCR_RTS | UART_MCR_DTR | (0&UART_MCR_OUT2),
uart->base + UART_MCR); break;
}
inb(uart->base + UART_IIR); /* Clear any outstanding interrupts */
/* Restore old divisor */ if (uart->divisor != 0) {
outb(UART_LCR_DLAB /* Divisor latch access bit */
,uart->base + UART_LCR); /* Line Control Register */
outb(uart->old_divisor_lsb
,uart->base + UART_DLL); /* Divisor Latch Low */
outb(uart->old_divisor_msb
,uart->base + UART_DLM); /* Divisor Latch High */ /* Restore old LCR (data bits, stop bits, parity, DLAB) */
outb(uart->old_line_ctrl_reg
,uart->base + UART_LCR); /* Line Control Register */
}
}
/* Interrupts are disabled during the updating of the tx_buff, * since it is 'bad' to have two processes updating the same * variables (ie buff_in & buff_out)
*/
spin_lock_irqsave(&uart->open_lock, flags);
if (uart->irq < 0) /* polling */
snd_uart16550_io_loop(uart);
if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) { while (1) { /* buffer full? */ /* in this mode we need two bytes of space */ if (uart->buff_in_count > TX_BUFF_SIZE - 2) break; if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1) break; #ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO /* select exactly one of the four ports */
addr_byte = (1 << (substream->number + 4)) | 0x08; #else /* select any combination of the four ports */
addr_byte = (substream->number << 4) | 0x08; /* ...except none */ if (addr_byte == 0x08)
addr_byte = 0xf8; #endif
snd_uart16550_output_byte(uart, substream, addr_byte); /* send midi byte */
snd_uart16550_output_byte(uart, substream, midi_byte);
}
} else {
first = 0; while (snd_rawmidi_transmit_peek(substream, &midi_byte, 1) == 1) { /* Also send F5 after 3 seconds with no data
* to handle device disconnect */ if (first == 0 &&
(uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS ||
uart->adaptor == SNDRV_SERIAL_GENERIC) &&
(uart->prev_out != substream->number ||
time_after(jiffies, lasttime + 3*HZ))) {
if (snd_uart16550_buffer_can_write(uart, 3)) { /* Roland Soundcanvas part selection */ /* If this substream of the data is * different previous substream * in this uart, send the change part * event
*/
uart->prev_out = substream->number; /* change part */
snd_uart16550_output_byte(uart, substream,
0xf5); /* data */
snd_uart16550_output_byte(uart, substream,
uart->prev_out + 1); /* If midi_byte is a data byte,
* send the previous status byte */ if (midi_byte < 0x80 &&
uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS)
snd_uart16550_output_byte(uart, substream, uart->prev_status[uart->prev_out]);
} elseif (!uart->drop_on_full) break;
switch (uart->adaptor) { case SNDRV_SERIAL_MS124W_SA: case SNDRV_SERIAL_MS124W_MB: /* MS-124W can draw power from RTS and DTR if they
are in opposite states. */
outb(UART_MCR_RTS | (0&UART_MCR_DTR), uart->base + UART_MCR); break; case SNDRV_SERIAL_MS124T: /* MS-124T can draw power from RTS and/or DTR (preferably
both) if they are asserted. */
outb(UART_MCR_RTS | UART_MCR_DTR, uart->base + UART_MCR); break; default: break;
}
staticint snd_uart16550_rmidi(struct snd_uart16550 *uart, int device, int outs, int ins, struct snd_rawmidi **rmidi)
{ struct snd_rawmidi *rrawmidi; int err;
staticint snd_serial_probe(struct platform_device *devptr)
{ struct snd_card *card; struct snd_uart16550 *uart; int err; int dev = devptr->id;
switch (adaptor[dev]) { case SNDRV_SERIAL_SOUNDCANVAS:
ins[dev] = 1; break; case SNDRV_SERIAL_MS124T: case SNDRV_SERIAL_MS124W_SA:
outs[dev] = 1;
ins[dev] = 1; break; case SNDRV_SERIAL_MS124W_MB:
outs[dev] = 16;
ins[dev] = 1; break; case SNDRV_SERIAL_GENERIC: break; default:
dev_err(&devptr->dev, "Adaptor type is out of range 0-%d (%d)\n",
SNDRV_SERIAL_MAX_ADAPTOR, adaptor[dev]); return -ENODEV;
}
if (outs[dev] < 1 || outs[dev] > SNDRV_SERIAL_MAX_OUTS) {
dev_err(&devptr->dev, "Count of outputs is out of range 1-%d (%d)\n",
SNDRV_SERIAL_MAX_OUTS, outs[dev]); return -ENODEV;
}
if (ins[dev] < 1 || ins[dev] > SNDRV_SERIAL_MAX_INS) {
dev_err(&devptr->dev, "Count of inputs is out of range 1-%d (%d)\n",
SNDRV_SERIAL_MAX_INS, ins[dev]); return -ENODEV;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.