// SPDX-License-Identifier: GPL-2.0 /* sunzilog.c: Zilog serial driver for Sparc systems. * * Driver for Zilog serial chips found on Sun workstations and * servers. This driver could actually be made more generic. * * This is based on the old drivers/sbus/char/zs.c code. A lot * of code has been simply moved over directly from there but * much has been rewritten. Credits therefore go out to Eddie * C. Dost, Pete Zaitcev, Ted Ts'o and Alex Buell for their * work there. * * Copyright (C) 2002, 2006, 2007 David S. Miller (davem@davemloft.net)
*/
/* On 32-bit sparcs we need to delay after register accesses * to accommodate sun4 systems, but we do not need to flush writes. * On 64-bit sparc we only need to flush single writes to ensure * completion.
*/ #ifndef CONFIG_SPARC64 #define ZSDELAY() udelay(5) #define ZSDELAY_LONG() udelay(20) #define ZS_WSYNC(channel) do { } while (0) #else #define ZSDELAY() #define ZSDELAY_LONG() #define ZS_WSYNC(__channel) \
readb(&((__channel)->control)) #endif
/* Reading and writing Zilog8530 registers. The delays are to make this * driver work on the Sun4 which needs a settling delay after each chip * register access, other machines handle this in hardware via auxiliary * flip-flops which implement the settle time we do in software. * * The port lock must be held and local IRQs must be disabled * when {read,write}_zsreg is invoked.
*/ staticunsignedchar read_zsreg(struct zilog_channel __iomem *channel, unsignedchar reg)
{ unsignedchar retval;
/* This function must only be called when the TX is not busy. The UART * port lock must be held and local interrupts disabled.
*/ staticint __load_zsregs(struct zilog_channel __iomem *channel, unsignedchar *regs)
{ int i; int escc; unsignedchar r15;
/* Let pending transmits finish. */ for (i = 0; i < 1000; i++) { unsignedchar stat = read_zsreg(channel, R1); if (stat & ALL_SNT) break;
udelay(100);
}
/* Don't mess with the interrupt vector (R2, unused by us) and * master interrupt control (R9). We make sure this is setup * properly at probe time then never touch it again.
*/
/* External status interrupt and FIFO control. */
write_zsreg(channel, R15, regs[R15] & ~WR7pEN);
escc = 1;
} else { /* Clear FIFO bit case it is an issue */
regs[R15] &= ~FIFOEN;
escc = 0;
}
/* Reset external status interrupts. */
write_zsreg(channel, R0, RES_EXT_INT); /* First Latch */
write_zsreg(channel, R0, RES_EXT_INT); /* Second Latch */
/* Rewrite R3/R5, this time without enables masked. */
write_zsreg(channel, R3, regs[R3]);
write_zsreg(channel, R5, regs[R5]);
/* Rewrite R1, this time without IRQ enabled masked. */
write_zsreg(channel, R1, regs[R1]);
return escc;
}
/* Reprogram the Zilog channel HW registers with the copies found in the * software state struct. If the transmitter is busy, we defer this update * until the next TX complete interrupt. Else, we do it right now. * * The UART port lock must be held and local interrupts disabled.
*/ staticvoid sunzilog_maybe_update_regs(struct uart_sunzilog_port *up, struct zilog_channel __iomem *channel)
{ if (!ZS_REGS_HELD(up)) { if (ZS_TX_ACTIVE(up)) {
up->flags |= SUNZILOG_FLAG_REGS_HELD;
} else {
__load_zsregs(channel, up->curregs);
}
}
}
if (status & BRK_ABRT) { if (ZS_IS_MOUSE(up))
sunzilog_kbdms_receive_chars(up, 0, 1); if (ZS_IS_CONS(up)) { /* Wait for BREAK to deassert to avoid potentially * confusing the PROM.
*/ while (1) {
status = readb(&channel->control);
ZSDELAY(); if (!(status & BRK_ABRT)) break;
}
sun_do_break(); return;
}
}
if (ZS_WANTS_MODEM_STATUS(up)) { if (status & SYNC)
up->port.icount.dsr++;
/* The Zilog just gives us an interrupt when DCD/CTS/etc. change. * But it does not tell us which bit has changed, we have to keep * track of this ourselves.
*/ if ((status ^ up->prev_status) ^ DCD)
uart_handle_dcd_change(&up->port,
(status & DCD)); if ((status ^ up->prev_status) ^ CTS)
uart_handle_cts_change(&up->port,
(status & CTS));
if (ZS_IS_CONS(up)) { unsignedchar status = readb(&channel->control);
ZSDELAY();
/* TX still busy? Just wait for the next TX done interrupt. * * It can occur because of how we do serial console writes. It would * be nice to transmit console writes just like we normally would for * a TTY line. (ie. buffered and TX interrupt driven). That is not * easy because console writes cannot sleep. One solution might be * to poll on enough port->xmit space becoming free. -DaveM
*/ if (!(status & Tx_BUF_EMP)) return;
}
up->flags &= ~SUNZILOG_FLAG_TX_ACTIVE;
if (ZS_REGS_HELD(up)) {
__load_zsregs(channel, up->curregs);
up->flags &= ~SUNZILOG_FLAG_REGS_HELD;
}
if (ZS_TX_STOPPED(up)) {
up->flags &= ~SUNZILOG_FLAG_TX_STOPPED; goto ack_tx_int;
}
if (up->port.x_char) {
up->flags |= SUNZILOG_FLAG_TX_ACTIVE;
writeb(up->port.x_char, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
/* Channel A */
port = NULL; if (r3 & (CHAEXT | CHATxIP | CHARxIP)) {
writeb(RES_H_IUS, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
if (r3 & CHARxIP)
port = sunzilog_receive_chars(up, channel); if (r3 & CHAEXT)
sunzilog_status_handle(up, channel); if (r3 & CHATxIP)
sunzilog_transmit_chars(up, channel);
}
uart_port_unlock(&up->port);
if (port)
tty_flip_buffer_push(port);
/* Channel B */
up = up->next;
channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
uart_port_lock(&up->port);
port = NULL; if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) {
writeb(RES_H_IUS, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
if (r3 & CHBRxIP)
port = sunzilog_receive_chars(up, channel); if (r3 & CHBEXT)
sunzilog_status_handle(up, channel); if (r3 & CHBTxIP)
sunzilog_transmit_chars(up, channel);
}
uart_port_unlock(&up->port);
if (port)
tty_flip_buffer_push(port);
up = up->next;
}
return IRQ_HANDLED;
}
/* A convenient way to quickly get R0 status. The caller must _not_ hold the * port lock, it is acquired here.
*/ static __inline__ unsignedchar sunzilog_read_channel_status(struct uart_port *port)
{ struct zilog_channel __iomem *channel; unsignedchar status;
channel = ZILOG_CHANNEL_FROM_PORT(port);
status = readb(&channel->control);
ZSDELAY();
return status;
}
/* The port lock is not held. */ staticunsignedint sunzilog_tx_empty(struct uart_port *port)
{ unsignedlong flags; unsignedchar status; unsignedint ret;
uart_port_lock_irqsave(port, &flags);
status = sunzilog_read_channel_status(port);
uart_port_unlock_irqrestore(port, flags);
if (status & Tx_BUF_EMP)
ret = TIOCSER_TEMT; else
ret = 0;
return ret;
}
/* The port lock is held and interrupts are disabled. */ staticunsignedint sunzilog_get_mctrl(struct uart_port *port)
{ unsignedchar status; unsignedint ret;
status = sunzilog_read_channel_status(port);
ret = 0; if (status & DCD)
ret |= TIOCM_CAR; if (status & SYNC)
ret |= TIOCM_DSR; if (status & CTS)
ret |= TIOCM_CTS;
return ret;
}
/* The port lock is held and interrupts are disabled. */ staticvoid sunzilog_set_mctrl(struct uart_port *port, unsignedint mctrl)
{ struct uart_sunzilog_port *up =
container_of(port, struct uart_sunzilog_port, port); struct zilog_channel __iomem *channel = ZILOG_CHANNEL_FROM_PORT(port); unsignedchar set_bits, clear_bits;
/* NOTE: Not subject to 'transmitter active' rule. */
up->curregs[R5] |= set_bits;
up->curregs[R5] &= ~clear_bits;
write_zsreg(channel, R5, up->curregs[R5]);
}
/* The port lock is held and interrupts are disabled. */ staticvoid sunzilog_stop_tx(struct uart_port *port)
{ struct uart_sunzilog_port *up =
container_of(port, struct uart_sunzilog_port, port);
up->flags |= SUNZILOG_FLAG_TX_STOPPED;
}
/* The port lock is held and interrupts are disabled. */ staticvoid sunzilog_start_tx(struct uart_port *port)
{ struct uart_sunzilog_port *up =
container_of(port, struct uart_sunzilog_port, port); struct zilog_channel __iomem *channel = ZILOG_CHANNEL_FROM_PORT(port); unsignedchar status;
/* TX busy? Just wait for the TX done interrupt. */ if (!(status & Tx_BUF_EMP)) return;
/* Send the first character to jump-start the TX done * IRQ sending engine.
*/ if (port->x_char) {
writeb(port->x_char, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
/* * The test for ZS_IS_CONS is explained by the following e-mail: ***** * From: Russell King <rmk@arm.linux.org.uk> * Date: Sun, 8 Dec 2002 10:18:38 +0000 * * On Sun, Dec 08, 2002 at 02:43:36AM -0500, Pete Zaitcev wrote: * > I boot my 2.5 boxes using "console=ttyS0,9600" argument, * > and I noticed that something is not right with reference * > counting in this case. It seems that when the console * > is open by kernel initially, this is not accounted * > as an open, and uart_startup is not called. * * That is correct. We are unable to call uart_startup when the serial * console is initialised because it may need to allocate memory (as * request_irq does) and the memory allocators may not have been * initialised. * * 1. initialise the port into a state where it can send characters in the * console write method. * * 2. don't do the actual hardware shutdown in your shutdown() method (but * do the normal software shutdown - ie, free irqs etc) *****
*/ staticvoid sunzilog_shutdown(struct uart_port *port)
{ struct uart_sunzilog_port *up = UART_ZILOG(port); struct zilog_channel __iomem *channel; unsignedlong flags;
/* Disable all interrupts and BRK assertion. */
up->curregs[R1] &= ~(EXT_INT_ENAB | TxINT_ENAB | RxINT_MASK);
up->curregs[R5] &= ~SND_BRK;
sunzilog_maybe_update_regs(up, channel);
uart_port_unlock_irqrestore(port, flags);
}
/* Shared by TTY driver and serial console setup. The port lock is held * and local interrupts are disabled.
*/ staticvoid
sunzilog_convert_to_zs(struct uart_sunzilog_port *up, unsignedint cflag, unsignedint iflag, int brg)
{
/* We do not request/release mappings of the registers here, this * happens at early serial probe time.
*/ staticvoid sunzilog_release_port(struct uart_port *port)
{
}
/* These do not need to do anything interesting either. */ staticvoid sunzilog_config_port(struct uart_port *port, int flags)
{
}
/* We do not support letting the user mess with the divisor, IRQ, etc. */ staticint sunzilog_verify_port(struct uart_port *port, struct serial_struct *ser)
{ return -EINVAL;
}
/* This is a timed polling loop so do not switch the explicit * udelay with ZSDELAY as that is a NOP on some platforms. -DaveM
*/ do { unsignedchar val = readb(&channel->control); if (val & Tx_BUF_EMP) {
ZSDELAY(); break;
}
udelay(5);
} while (--loops);
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.