static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) { struct uart_ip22zilog_port *up = dev_id; while (up) { struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port); unsigned char r3; bool push = false; spin_lock(&up->port.lock); r3 = read_zsreg(channel, R3); /* Channel A */ if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (r3 & CHARxIP) push = ip22zilog_receive_chars(up, channel); if (r3 & CHAEXT) ip22zilog_status_handle(up, channel); if (r3 & CHATxIP) ip22zilog_transmit_chars(up, channel); } spin_unlock(&up->port.lock); if (push) tty_flip_buffer_push(&up->port.state->port); /* Channel B */ up = up->next; channel = ZILOG_CHANNEL_FROM_PORT(&up->port); push = false; spin_lock(&up->port.lock); if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); if (r3 & CHBRxIP) push = ip22zilog_receive_chars(up, channel); if (r3 & CHBEXT) ip22zilog_status_handle(up, channel); if (r3 & CHBTxIP) ip22zilog_transmit_chars(up, channel); } spin_unlock(&up->port.lock); if (push) tty_flip_buffer_push(&up->port.state->port); up = up->next; } return IRQ_HANDLED; }
static void ip22zilog_status_handle(struct uart_ip22zilog_port *up, struct zilog_channel *channel) { unsigned char status; status = readb(&channel->control); ZSDELAY(); writeb(RES_EXT_INT, &channel->control); ZSDELAY(); ZS_WSYNC(channel); 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 & DCD) ^ up->prev_status) uart_handle_dcd_change(&up->port, (status & DCD)); if ((status & CTS) ^ up->prev_status) uart_handle_cts_change(&up->port, (status & CTS)); wake_up_interruptible(&up->port.info->delta_msr_wait); } up->prev_status = status; }
/* The port lock is held and interrupts are disabled. */ static void ip22zilog_start_tx(struct uart_port *port) { struct uart_ip22zilog_port *up = (struct uart_ip22zilog_port *) port; struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(port); unsigned char status; up->flags |= IP22ZILOG_FLAG_TX_ACTIVE; up->flags &= ~IP22ZILOG_FLAG_TX_STOPPED; status = readb(&channel->control); ZSDELAY(); /* 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); port->icount.tx++; port->x_char = 0; } else { struct circ_buf *xmit = &port->state->xmit; writeb(xmit->buf[xmit->tail], &channel->data); ZSDELAY(); ZS_WSYNC(channel); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); } }
static void ip22zilog_put_char(struct zilog_channel *channel, unsigned char ch) { int loops = ZS_PUT_CHAR_MAX_DELAY; /* 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 { unsigned char val = readb(&channel->control); if (val & Tx_BUF_EMP) { ZSDELAY(); break; } udelay(5); } while (--loops); writeb(ch, &channel->data); ZSDELAY(); ZS_WSYNC(channel); }
static void ip22zilog_clear_fifo(struct zilog_channel *channel) { int i; for (i = 0; i < 32; i++) { unsigned char regval; regval = readb(&channel->control); ZSDELAY(); if (regval & Rx_CH_AV) break; regval = read_zsreg(channel, R1); readb(&channel->data); ZSDELAY(); if (regval & (PAR_ERR | Rx_OVR | CRC_ERR)) { writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } } }
static void ip22zilog_transmit_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel) { struct circ_buf *xmit; if (ZS_IS_CONS(up)) { unsigned char 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 &= ~IP22ZILOG_FLAG_TX_ACTIVE; if (ZS_REGS_HELD(up)) { __load_zsregs(channel, up->curregs); up->flags &= ~IP22ZILOG_FLAG_REGS_HELD; } if (ZS_TX_STOPPED(up)) { up->flags &= ~IP22ZILOG_FLAG_TX_STOPPED; goto ack_tx_int; } if (up->port.x_char) { up->flags |= IP22ZILOG_FLAG_TX_ACTIVE; writeb(up->port.x_char, &channel->data); ZSDELAY(); ZS_WSYNC(channel); up->port.icount.tx++; up->port.x_char = 0; return; } if (up->port.state == NULL) goto ack_tx_int; xmit = &up->port.state->xmit; if (uart_circ_empty(xmit)) goto ack_tx_int; if (uart_tx_stopped(&up->port)) goto ack_tx_int; up->flags |= IP22ZILOG_FLAG_TX_ACTIVE; writeb(xmit->buf[xmit->tail], &channel->data); ZSDELAY(); ZS_WSYNC(channel); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); up->port.icount.tx++; if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); return; ack_tx_int: writeb(RES_Tx_P, &channel->control); ZSDELAY(); ZS_WSYNC(channel); }
static bool ip22zilog_receive_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel) { unsigned char ch, flag; unsigned int r1; bool push = up->port.state != NULL; for (;;) { ch = readb(&channel->control); ZSDELAY(); if (!(ch & Rx_CH_AV)) break; r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } ch = readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; /* Handle the null char got when BREAK is removed. */ if (!ch) r1 |= up->tty_break; /* A real serial line, record the character and status. */ flag = TTY_NORMAL; up->port.icount.rx++; if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR | Rx_SYS | Rx_BRK)) { up->tty_break = 0; if (r1 & (Rx_SYS | Rx_BRK)) { up->port.icount.brk++; if (r1 & Rx_SYS) continue; r1 &= ~(PAR_ERR | CRC_ERR); } else if (r1 & PAR_ERR) up->port.icount.parity++; else if (r1 & CRC_ERR) up->port.icount.frame++; if (r1 & Rx_OVR) up->port.icount.overrun++; r1 &= up->port.read_status_mask; if (r1 & Rx_BRK) flag = TTY_BREAK; else if (r1 & PAR_ERR) flag = TTY_PARITY; else if (r1 & CRC_ERR) flag = TTY_FRAME; } if (uart_handle_sysrq_char(&up->port, ch)) continue; if (push) uart_insert_char(&up->port, r1, Rx_OVR, ch, flag); } return push; }
/* This function must only be called when the TX is not busy. The UART * port lock must be held and local interrupts disabled. */ static void __load_zsregs(struct zilog_channel *channel, unsigned char *regs) { int i; /* Let pending transmits finish. */ for (i = 0; i < 1000; i++) { unsigned char stat = read_zsreg(channel, R1); if (stat & ALL_SNT) break; udelay(100); } writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); ip22zilog_clear_fifo(channel); /* Disable all interrupts. */ write_zsreg(channel, R1, regs[R1] & ~(RxINT_MASK | TxINT_ENAB | EXT_INT_ENAB)); /* Set parity, sync config, stop bits, and clock divisor. */ write_zsreg(channel, R4, regs[R4]); /* Set misc. TX/RX control bits. */ write_zsreg(channel, R10, regs[R10]); /* Set TX/RX controls sans the enable bits. */ write_zsreg(channel, R3, regs[R3] & ~RxENAB); write_zsreg(channel, R5, regs[R5] & ~TxENAB); /* Synchronous mode config. */ write_zsreg(channel, R6, regs[R6]); write_zsreg(channel, R7, regs[R7]); /* 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. */ /* Disable baud generator. */ write_zsreg(channel, R14, regs[R14] & ~BRENAB); /* Clock mode control. */ write_zsreg(channel, R11, regs[R11]); /* Lower and upper byte of baud rate generator divisor. */ write_zsreg(channel, R12, regs[R12]); write_zsreg(channel, R13, regs[R13]); /* Now rewrite R14, with BRENAB (if set). */ write_zsreg(channel, R14, regs[R14]); /* External status interrupt control. */ write_zsreg(channel, R15, regs[R15]); /* Reset external status interrupts. */ write_zsreg(channel, R0, RES_EXT_INT); write_zsreg(channel, R0, RES_EXT_INT); /* 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]); }
static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel) { struct tty_struct *tty = up->port.info->tty; /* XXX info==NULL? */ while (1) { unsigned char ch, r1, flag; r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } ch = readb(&channel->control); ZSDELAY(); /* This funny hack depends upon BRK_ABRT not interfering * with the other bits we care about in R1. */ if (ch & BRK_ABRT) r1 |= BRK_ABRT; ch = readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; if (ZS_IS_CONS(up) && (r1 & BRK_ABRT)) { /* Wait for BREAK to deassert to avoid potentially * confusing the PROM. */ while (1) { ch = readb(&channel->control); ZSDELAY(); if (!(ch & BRK_ABRT)) break; } ip22_do_break(); return; } /* A real serial line, record the character and status. */ flag = TTY_NORMAL; up->port.icount.rx++; if (r1 & (BRK_ABRT | PAR_ERR | Rx_OVR | CRC_ERR)) { if (r1 & BRK_ABRT) { r1 &= ~(PAR_ERR | CRC_ERR); up->port.icount.brk++; if (uart_handle_break(&up->port)) goto next_char; } else if (r1 & PAR_ERR) up->port.icount.parity++; else if (r1 & CRC_ERR) up->port.icount.frame++; if (r1 & Rx_OVR) up->port.icount.overrun++; r1 &= up->port.read_status_mask; if (r1 & BRK_ABRT) flag = TTY_BREAK; else if (r1 & PAR_ERR) flag = TTY_PARITY; else if (r1 & CRC_ERR) flag = TTY_FRAME; } if (uart_handle_sysrq_char(&up->port, ch)) goto next_char; if (up->port.ignore_status_mask == 0xff || (r1 & up->port.ignore_status_mask) == 0) tty_insert_flip_char(tty, ch, flag); if (r1 & Rx_OVR) tty_insert_flip_char(tty, 0, TTY_OVERRUN); next_char: ch = readb(&channel->control); ZSDELAY(); if (!(ch & Rx_CH_AV)) break; } tty_flip_buffer_push(tty); }
static void ip22zilog_receive_chars(struct uart_ip22zilog_port *up, struct zilog_channel *channel, struct pt_regs *regs) { struct tty_struct *tty = up->port.info->tty; /* XXX info==NULL? */ while (1) { unsigned char ch, r1; if (unlikely(tty->flip.count >= TTY_FLIPBUF_SIZE)) { tty->flip.work.func((void *)tty); if (tty->flip.count >= TTY_FLIPBUF_SIZE) return; /* XXX Ignores SysRq when we need it most. Fix. */ } r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } ch = readb(&channel->control); ZSDELAY(); /* This funny hack depends upon BRK_ABRT not interfering * with the other bits we care about in R1. */ if (ch & BRK_ABRT) r1 |= BRK_ABRT; ch = readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; if (ZS_IS_CONS(up) && (r1 & BRK_ABRT)) { /* Wait for BREAK to deassert to avoid potentially * confusing the PROM. */ while (1) { ch = readb(&channel->control); ZSDELAY(); if (!(ch & BRK_ABRT)) break; } ip22_do_break(); return; } /* A real serial line, record the character and status. */ *tty->flip.char_buf_ptr = ch; *tty->flip.flag_buf_ptr = TTY_NORMAL; up->port.icount.rx++; if (r1 & (BRK_ABRT | PAR_ERR | Rx_OVR | CRC_ERR)) { if (r1 & BRK_ABRT) { r1 &= ~(PAR_ERR | CRC_ERR); up->port.icount.brk++; if (uart_handle_break(&up->port)) goto next_char; } else if (r1 & PAR_ERR) up->port.icount.parity++; else if (r1 & CRC_ERR) up->port.icount.frame++; if (r1 & Rx_OVR) up->port.icount.overrun++; r1 &= up->port.read_status_mask; if (r1 & BRK_ABRT) *tty->flip.flag_buf_ptr = TTY_BREAK; else if (r1 & PAR_ERR) *tty->flip.flag_buf_ptr = TTY_PARITY; else if (r1 & CRC_ERR) *tty->flip.flag_buf_ptr = TTY_FRAME; } if (uart_handle_sysrq_char(&up->port, ch, regs)) goto next_char; if (up->port.ignore_status_mask == 0xff || (r1 & up->port.ignore_status_mask) == 0) { tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } if ((r1 & Rx_OVR) && tty->flip.count < TTY_FLIPBUF_SIZE) { *tty->flip.flag_buf_ptr = TTY_OVERRUN; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } next_char: ch = readb(&channel->control); ZSDELAY(); if (!(ch & Rx_CH_AV)) break; } tty_flip_buffer_push(tty); }