// SPDX-License-Identifier: GPL-2.0 /* * Serial driver for the amiga builtin port. * * This code was created by taking serial.c version 4.30 from kernel * release 2.3.22, replacing all hardware related stuff with the * corresponding amiga hardware actions, and removing all irrelevant * code. As a consequence, it uses many of the constants and names * associated with the registers and bits of 16550 compatible UARTS - * but only to keep track of status, etc in the state variables. It * was done this was to make it easier to keep the code in line with * (non hardware specific) changes to serial.c. * * The port is registered with the tty driver as minor device 64, and * therefore other ports should only use 65 upwards. * * Richard Lucock 28/12/99 * * Copyright (C) 1991, 1992 Linus Torvalds * Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, * 1998, 1999 Theodore Ts'o *
*/
unsignedlong port; int baud_base; int custom_divisor; int read_status_mask; int ignore_status_mask; int timeout; int quot; int IER; /* Interrupt Enable Register */ int MCR; /* Modem control register */
u8 x_char; /* xon/xoff character */
};
staticstruct tty_driver *serial_driver;
/* number of characters left in xmit buffer before we ask for more */ #define WAKEUP_CHARS 256
/* * ------------------------------------------------------------ * rs_stop() and rs_start() * * This routines are called before setting or resetting tty->flow.stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------
*/ staticvoid rs_stop(struct tty_struct *tty)
{ struct serial_state *info = tty->driver_data; unsignedlong flags;
local_irq_save(flags); if (info->IER & UART_IER_THRI) {
info->IER &= ~UART_IER_THRI; /* disable Tx interrupt and remove any pending interrupts */
amiga_custom.intena = IF_TBE;
mb();
amiga_custom.intreq = IF_TBE;
mb();
}
local_irq_restore(flags);
}
local_irq_save(flags); if (info->xmit.head != info->xmit.tail
&& info->xmit.buf
&& !(info->IER & UART_IER_THRI)) {
info->IER |= UART_IER_THRI;
amiga_custom.intena = IF_SETCLR | IF_TBE;
mb(); /* set a pending Tx Interrupt, transmitter should restart now */
amiga_custom.intreq = IF_SETCLR | IF_TBE;
mb();
}
local_irq_restore(flags);
}
/* * ---------------------------------------------------------------------- * * Here start the interrupt handling routines. * * -----------------------------------------------------------------------
*/
staticvoid receive_chars(struct serial_state *info)
{ int status; int serdatr;
u8 ch, flag; struct async_icount *icount; bool overrun = false;
icount = &info->icount;
status = UART_LSR_DR; /* We obviously have a character! */
serdatr = amiga_custom.serdatr;
mb();
amiga_custom.intreq = IF_RBF;
mb();
if((serdatr & 0x1ff) == 0)
status |= UART_LSR_BI; if(serdatr & SDR_OVRUN)
status |= UART_LSR_OE;
ch = serdatr & 0xff;
icount->rx++;
#ifdef SERIAL_DEBUG_INTR
printk("DR%02x:%02x...", ch, status); #endif
flag = TTY_NORMAL;
/* * We don't handle parity or frame errors - but I have left * the code in, since I'm not sure that the errors can't be * detected.
*/
if (status & (UART_LSR_BI | UART_LSR_PE |
UART_LSR_FE | UART_LSR_OE)) { /* * For statistics only
*/ if (status & UART_LSR_BI) {
status &= ~(UART_LSR_FE | UART_LSR_PE);
icount->brk++;
} elseif (status & UART_LSR_PE)
icount->parity++; elseif (status & UART_LSR_FE)
icount->frame++; if (status & UART_LSR_OE)
icount->overrun++;
/* * Now check to see if character should be * ignored, and mask off conditions which * should be ignored.
*/ if (status & info->ignore_status_mask) return;
status &= info->read_status_mask;
if (status & (UART_LSR_BI)) { #ifdef SERIAL_DEBUG_INTR
printk("handling break...."); #endif
flag = TTY_BREAK; if (info->tport.flags & ASYNC_SAK)
do_SAK(info->tport.tty);
} elseif (status & UART_LSR_PE)
flag = TTY_PARITY; elseif (status & UART_LSR_FE)
flag = TTY_FRAME; if (status & UART_LSR_OE) { /* * Overrun is special, since it's * reported immediately, and doesn't * affect the current character
*/
overrun = true;
}
}
tty_insert_flip_char(&info->tport, ch, flag); if (overrun)
tty_insert_flip_char(&info->tport, 0, TTY_OVERRUN);
tty_flip_buffer_push(&info->tport);
}
/* Determine bits that have changed */
dstatus = status ^ current_ctl_bits;
current_ctl_bits = status;
if (dstatus) {
icount = &info->icount; /* update input line counters */ if (dstatus & SER_DSR)
icount->dsr++; if (dstatus & SER_DCD) {
icount->dcd++;
} if (dstatus & SER_CTS)
icount->cts++;
wake_up_interruptible(&port->delta_msr_wait);
}
if (tty_port_check_carrier(port) && (dstatus & SER_DCD)) { #if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))
printk("ttyS%d CD now %s...", info->line,
(!(status & SER_DCD)) ? "on" : "off"); #endif if (!(status & SER_DCD))
wake_up_interruptible(&port->open_wait); else { #ifdef SERIAL_DEBUG_OPEN
printk("doing serial hangup..."); #endif if (port->tty)
tty_hangup(port->tty);
}
} if (tty_port_cts_enabled(port)) { if (port->tty->hw_stopped) { if (!(status & SER_CTS)) { #if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
printk("CTS tx start..."); #endif
port->tty->hw_stopped = false;
info->IER |= UART_IER_THRI;
amiga_custom.intena = IF_SETCLR | IF_TBE;
mb(); /* set a pending Tx Interrupt, transmitter should restart now */
amiga_custom.intreq = IF_SETCLR | IF_TBE;
mb();
tty_wakeup(port->tty); return;
}
} else { if ((status & SER_CTS)) { #if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
printk("CTS tx stop..."); #endif
port->tty->hw_stopped = true;
info->IER &= ~UART_IER_THRI; /* disable Tx interrupt and remove any pending interrupts */
amiga_custom.intena = IF_TBE;
mb();
amiga_custom.intreq = IF_TBE;
mb();
}
}
}
}
static irqreturn_t ser_vbl_int( int irq, void *data)
{ /* vbl is just a periodic interrupt we tie into to update modem status */ struct serial_state *info = data; /* * TBD - is it better to unregister from this interrupt or to * ignore it if MSI is clear ?
*/ if(info->IER & UART_IER_MSI)
check_modem_status(info); return IRQ_HANDLED;
}
/* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * -------------------------------------------------------------------
*/
/* * --------------------------------------------------------------- * Low level utility subroutines for the serial driver: routines to * figure out the appropriate timeout for an interrupt chain, routines * to initialize and startup a serial port, and routines to shutdown a * serial port. Useful stuff like that. * ---------------------------------------------------------------
*/
/* * This routine will shutdown a serial port; interrupts are disabled, and * DTR is dropped if the hangup on close termio flag is on.
*/ staticvoid shutdown(struct tty_struct *tty, struct serial_state *info)
{ unsignedlong flags;
if (!tty_port_initialized(&info->tport)) return;
#ifdef SERIAL_DEBUG_OPEN
printk("Shutting down serial port %d ....\n", info->line); #endif
local_irq_save(flags); /* Disable interrupts */
/* * clear delta_msr_wait queue to avoid mem leaks: we may free the irq * here so the queue might never be waken up
*/
wake_up_interruptible(&info->tport.delta_msr_wait);
/* * Free the IRQ, if necessary
*/
free_irq(IRQ_AMIGA_VERTB, info);
/* * This routine is called to set the UART divisor registers to match * the specified baud rate for a serial port.
*/ staticvoid change_speed(struct tty_struct *tty, struct serial_state *info, conststruct ktermios *old_termios)
{ struct tty_port *port = &info->tport; int quot = 0, baud_base, baud; unsigned cflag, cval = 0; int bits; unsignedlong flags;
cflag = tty->termios.c_cflag;
/* Byte size is always 8 bits plus parity bit if requested */
cval = 3; bits = 10; if (cflag & CSTOPB) {
cval |= 0x04;
bits++;
} if (cflag & PARENB) {
cval |= UART_LCR_PARITY;
bits++;
} if (!(cflag & PARODD))
cval |= UART_LCR_EPAR; if (cflag & CMSPAR)
cval |= UART_LCR_SPAR;
/* Determine divisor based on baud rate */
baud = tty_get_baud_rate(tty); if (!baud)
baud = 9600; /* B0 transition handled in rs_set_termios */
baud_base = info->baud_base; if (baud == 38400 && (port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
quot = info->custom_divisor; else { if (baud == 134) /* Special case since 134 is really 134.5 */
quot = (2*baud_base / 269); elseif (baud)
quot = baud_base / baud;
} /* If the quotient is zero refuse the change */ if (!quot && old_termios) { /* FIXME: Will need updating for new tty in the end */
tty->termios.c_cflag &= ~CBAUD;
tty->termios.c_cflag |= (old_termios->c_cflag & CBAUD);
baud = tty_get_baud_rate(tty); if (!baud)
baud = 9600; if (baud == 38400 &&
(port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
quot = info->custom_divisor; else { if (baud == 134) /* Special case since 134 is really 134.5 */
quot = (2*baud_base / 269); elseif (baud)
quot = baud_base / baud;
}
} /* As a last resort, if the quotient is zero, default to 9600 bps */ if (!quot)
quot = baud_base / 9600;
info->quot = quot;
info->timeout = (XMIT_FIFO_SIZE*HZ*bits*quot) / baud_base;
info->timeout += HZ/50; /* Add .02 seconds of slop */
/* CTS flow control flag and modem status interrupts */
info->IER &= ~UART_IER_MSI; if (port->flags & ASYNC_HARDPPS_CD)
info->IER |= UART_IER_MSI;
tty_port_set_cts_flow(port, cflag & CRTSCTS); if (cflag & CRTSCTS)
info->IER |= UART_IER_MSI;
tty_port_set_check_carrier(port, ~cflag & CLOCAL); if (~cflag & CLOCAL)
info->IER |= UART_IER_MSI; /* TBD: * Does clearing IER_MSI imply that we should disable the VBL interrupt ?
*/
/* * Set up parity check flag
*/
info->read_status_mask = UART_LSR_OE | UART_LSR_DR; if (I_INPCK(tty))
info->read_status_mask |= UART_LSR_FE | UART_LSR_PE; if (I_BRKINT(tty) || I_PARMRK(tty))
info->read_status_mask |= UART_LSR_BI;
/* * Characters to ignore
*/
info->ignore_status_mask = 0; if (I_IGNPAR(tty))
info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; if (I_IGNBRK(tty)) {
info->ignore_status_mask |= UART_LSR_BI; /* * If we're ignore parity and break indicators, ignore * overruns too. (For real raw support).
*/ if (I_IGNPAR(tty))
info->ignore_status_mask |= UART_LSR_OE;
} /* * !!! ignore all characters if CREAD is not set
*/ if ((cflag & CREAD) == 0)
info->ignore_status_mask |= UART_LSR_DR;
local_irq_save(flags);
/* * This function is used to send a high-priority XON/XOFF character to * the device
*/ staticvoid rs_send_xchar(struct tty_struct *tty, u8 ch)
{ struct serial_state *info = tty->driver_data; unsignedlong flags;
info->x_char = ch; if (ch) { /* Make sure transmit interrupts are on */
/* Check this ! */
local_irq_save(flags); if(!(amiga_custom.intenar & IF_TBE)) {
amiga_custom.intena = IF_SETCLR | IF_TBE;
mb(); /* set a pending Tx Interrupt, transmitter should restart now */
amiga_custom.intreq = IF_SETCLR | IF_TBE;
mb();
}
local_irq_restore(flags);
info->IER |= UART_IER_THRI;
}
}
/* * ------------------------------------------------------------ * rs_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------
*/ staticvoid rs_throttle(struct tty_struct * tty)
{ struct serial_state *info = tty->driver_data; unsignedlong flags; #ifdef SERIAL_DEBUG_THROTTLE
printk("throttle %s ....\n", tty_name(tty)); #endif
if (I_IXOFF(tty))
rs_send_xchar(tty, STOP_CHAR(tty));
check_and_exit: if (tty_port_initialized(port)) { if (change_spd) { /* warn about deprecation unless clearing */ if (ss->flags & ASYNC_SPD_MASK)
dev_warn_ratelimited(tty->dev, "use of SPD flags is deprecated\n");
change_speed(tty, state, NULL);
}
} else
retval = startup(tty, state);
tty_unlock(tty); return retval;
}
/* * get_lsr_info - get line status register info * * Purpose: Let user call ioctl() to get info when the UART physically * is emptied. On bus types like RS485, the transmitter must * release the bus after transmitting. This must be done when * the transmit shift register is empty, not be done when the * transmit holding register is empty. This functionality * allows an RS485 driver to be written in user space.
*/ staticint get_lsr_info(struct serial_state *info, unsignedint __user *value)
{ unsignedchar status; unsignedint result; unsignedlong flags;
local_irq_save(flags);
status = amiga_custom.serdatr;
mb();
local_irq_restore(flags);
result = ((status & SDR_TSRE) ? TIOCSER_TEMT : 0); if (copy_to_user(value, &result, sizeof(int))) return -EFAULT; return 0;
}
local_irq_save(flags); if (set & TIOCM_RTS)
info->MCR |= SER_RTS; if (set & TIOCM_DTR)
info->MCR |= SER_DTR; if (clear & TIOCM_RTS)
info->MCR &= ~SER_RTS; if (clear & TIOCM_DTR)
info->MCR &= ~SER_DTR;
rtsdtr_ctrl(info->MCR);
local_irq_restore(flags); return 0;
}
/* * rs_break() --- routine which turns the break handling on or off
*/ staticint rs_break(struct tty_struct *tty, int break_state)
{ unsignedlong flags;
/* * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) * Return: write counters to the user passed counter struct * NB: both 1->0 and 0->1 transitions are counted except for * RI where only 0->1 is counted.
*/ staticint rs_get_icount(struct tty_struct *tty, struct serial_icounter_struct *icount)
{ struct serial_state *info = tty->driver_data; struct async_icount cnow; unsignedlong flags;
if ((cmd != TIOCSERCONFIG) &&
(cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { if (tty_io_error(tty)) return -EIO;
}
switch (cmd) { case TIOCSERCONFIG: return 0;
case TIOCSERGETLSR: /* Get line status register */ return get_lsr_info(info, argp);
/* * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change * - mask passed in arg for lines of interest * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) * Caller should use TIOCGICOUNT to see which one it was
*/ case TIOCMIWAIT:
local_irq_save(flags); /* note the counters on entry */
cprev = info->icount;
local_irq_restore(flags); while (1) {
prepare_to_wait(&info->tport.delta_msr_wait,
&wait, TASK_INTERRUPTIBLE);
local_irq_save(flags);
cnow = info->icount; /* atomic copy */
local_irq_restore(flags); if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
ret = -EIO; /* no change => error */ break;
} if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
ret = 0; break;
}
schedule(); /* see if a signal did it */ if (signal_pending(current)) {
ret = -ERESTARTSYS; break;
}
cprev = cnow;
}
finish_wait(&info->tport.delta_msr_wait, &wait); return ret;
/* Handle transition to B0 status */ if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) {
info->MCR &= ~(SER_DTR|SER_RTS);
local_irq_save(flags);
rtsdtr_ctrl(info->MCR);
local_irq_restore(flags);
}
/* Handle transition away from B0 status */ if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
info->MCR |= SER_DTR; if (!C_CRTSCTS(tty) || !tty_throttled(tty))
info->MCR |= SER_RTS;
local_irq_save(flags);
rtsdtr_ctrl(info->MCR);
local_irq_restore(flags);
}
/* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !C_CRTSCTS(tty)) {
tty->hw_stopped = false;
rs_start(tty);
}
#if 0 /* * No need to wake up processes in open wait, since they * sample the CLOCAL flag once, and don't recheck it. * XXX It's not clear whether the current behavior is correct * or not. Hence, this may change.....
*/ if (!(old_termios->c_cflag & CLOCAL) && C_CLOCAL(tty))
wake_up_interruptible(&info->open_wait); #endif
}
/* * ------------------------------------------------------------ * rs_close() * * This routine is called when the serial port gets closed. First, we * wait for the last remaining data to be sent. Then, we unlink its * async structure from the interrupt chain if necessary, and we free * that IRQ if nothing is left in the chain. * ------------------------------------------------------------
*/ staticvoid rs_close(struct tty_struct *tty, struct file * filp)
{ struct serial_state *state = tty->driver_data; struct tty_port *port = &state->tport;
if (tty_port_close_start(port, tty, filp) == 0) return;
/* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register.
*/
state->read_status_mask &= ~UART_LSR_DR; if (tty_port_initialized(port)) { /* disable receive interrupts */
amiga_custom.intena = IF_RBF;
mb(); /* clear any pending receive interrupt */
amiga_custom.intreq = IF_RBF;
mb();
/* * Before we drop DTR, make sure the UART transmitter * has completely drained; this is especially * important if there is a transmit FIFO!
*/
rs_wait_until_sent(tty, state->timeout);
}
shutdown(tty, state);
rs_flush_buffer(tty);
tty_ldisc_flush(tty);
port->tty = NULL;
tty_port_close_end(port, tty);
}
/* * rs_wait_until_sent() --- wait until the transmitter is empty
*/ staticvoid rs_wait_until_sent(struct tty_struct *tty, int timeout)
{ struct serial_state *info = tty->driver_data; unsignedlong orig_jiffies, char_time; int lsr;
orig_jiffies = jiffies;
/* * Set the check interval to be 1/5 of the estimated time to * send a single character, and make it at least 1. The check * interval should also be less than the timeout. * * Note: we have to use pretty tight timings here to satisfy * the NIST-PCTS.
*/
char_time = (info->timeout - HZ/50) / XMIT_FIFO_SIZE;
char_time = char_time / 5; if (char_time == 0)
char_time = 1; if (timeout)
char_time = min_t(unsignedlong, char_time, timeout); /* * If the transmitter hasn't cleared in twice the approximate * amount of time to send the entire FIFO, it probably won't * ever clear. This assumes the UART isn't doing flow * control, which is currently the case. Hence, if it ever * takes longer than info->timeout, this is probably due to a * UART bug of some kind. So, we clamp the timeout parameter at * 2*info->timeout.
*/ if (!timeout || timeout > 2*info->timeout)
timeout = 2*info->timeout; #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
printk("In rs_wait_until_sent(%d) check=%lu...", timeout, char_time);
printk("jiff=%lu...", jiffies); #endif while(!((lsr = amiga_custom.serdatr) & SDR_TSRE)) { #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
printk("serdatr = %d (jiff=%lu)...", lsr, jiffies); #endif
msleep_interruptible(jiffies_to_msecs(char_time)); if (signal_pending(current)) break; if (timeout && time_after(jiffies, orig_jiffies + timeout)) break;
}
__set_current_state(TASK_RUNNING);
/* * rs_hangup() --- called by tty_hangup() when a hangup is signaled.
*/ staticvoid rs_hangup(struct tty_struct *tty)
{ struct serial_state *info = tty->driver_data;
/* * This routine is called whenever a serial port is opened. It * enables interrupts for a serial port, linking in its async structure into * the IRQ chain. It also performs the serial-specific * initialization for the tty structure.
*/ staticint rs_open(struct tty_struct *tty, struct file * filp)
{ struct tty_port *port = tty->port; struct serial_state *info = container_of(port, struct serial_state,
tport); int retval;
/* * --------------------------------------------------------------------- * rs_init() and friends * * rs_init() is called at boot-time to initialize the serial driver. * ---------------------------------------------------------------------
*/
memset(state, 0, sizeof(*state));
state->port = (int)&amiga_custom.serdatr; /* Just to give it a value */
tty_port_init(&state->tport);
state->tport.ops = &amiga_port_ops;
tty_port_link_device(&state->tport, driver, 0);
error = tty_register_driver(driver); if (error) goto fail_tty_driver_kref_put;
printk(KERN_INFO "ttyS0 is the amiga builtin serial port\n");
/* Hardware set up */
state->baud_base = amiga_colorclock;
/* set ISRs, and then disable the rx interrupts */
error = request_irq(IRQ_AMIGA_TBE, ser_tx_int, 0, "serial TX", state); if (error) goto fail_unregister;
/* * amiga_serial_remove() lives in .exit.text. For drivers registered via * module_platform_driver_probe() this is ok because they cannot get unbound at * runtime. So mark the driver struct with __refdata to prevent modpost * triggering a section mismatch warning.
*/ staticstruct platform_driver amiga_serial_driver __refdata = {
.remove = __exit_p(amiga_serial_remove),
.driver = {
.name = "amiga-serial",
},
};
/* * ------------------------------------------------------------ * Serial console driver * ------------------------------------------------------------
*/
staticvoid amiga_serial_putc(char c)
{
amiga_custom.serdat = (unsignedchar)c | 0x100; while (!(amiga_custom.serdatr & 0x2000))
barrier();
}
/* * Print a string to the serial port trying not to disturb * any possible real use of the port... * * The console must be locked when we get here.
*/ staticvoid serial_console_write(struct console *co, constchar *s, unsigned count)
{ unsignedshort intena = amiga_custom.intenar;
amiga_custom.intena = IF_TBE;
while (count--) { if (*s == '\n')
amiga_serial_putc('\r');
amiga_serial_putc(*s++);
}
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.