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; }
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 write_zsreg(struct zilog_channel *channel, unsigned char reg, unsigned char value) { writeb(reg, &channel->control); ZSDELAY(); writeb(value, &channel->control); ZSDELAY(); }
/* Reading and writing Zilog8530 registers. The delays are to make this * driver work on the IP22 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. */ static unsigned char read_zsreg(struct zilog_channel *channel, unsigned char reg) { unsigned char retval; writeb(reg, &channel->control); ZSDELAY(); retval = readb(&channel->control); ZSDELAY(); return retval; }
/* A convenient way to quickly get R0 status. The caller must _not_ hold the * port lock, it is acquired here. */ static __inline__ unsigned char ip22zilog_read_channel_status(struct uart_port *port) { struct zilog_channel *channel; unsigned char status; channel = ZILOG_CHANNEL_FROM_PORT(port); status = readb(&channel->control); ZSDELAY(); return status; }
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); }
/* 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_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); } } }
/* * take external status interrupt (only CD interests us) */ static int zs_xsisr( struct zscom *zs ) { register struct zsaline *za = (struct zsaline *)(void *)zs->zs_priv; register struct zscc_device *zsaddr = zs->zs_addr; register queue_t *q; register unsigned char zsstatus; register int loopcheck; register char *dname; #ifdef PPS_SYNC register unsigned int s; register long usec; #endif /* * pick up current state */ zsstatus = zsaddr->zscc_control; if ((za->za_rr0 ^ zsstatus) & (cdmask)) { timestamp_t cdevent; register int status; za->za_rr0 = (za->za_rr0 & ~(cdmask)) | (zsstatus & (cdmask)); #ifdef PPS_SYNC s = splclock(); #ifdef PPS_NEW usec = timestamp.tv_usec; #else usec = pps_time.tv_usec; #endif #endif /* * time stamp */ uniqtime(&cdevent.tv); #ifdef PPS_SYNC (void)splx(s); #endif /* * logical state */ status = cd_invert ? (zsstatus & cdmask) == 0 : (zsstatus & cdmask) != 0; #ifdef PPS_SYNC if (status) { usec = cdevent.tv.tv_usec - usec; if (usec < 0) usec += 1000000; hardpps(&cdevent.tv, usec); } #endif q = za->za_ttycommon.t_readq; /* * ok - now the hard part - find ourself */ loopcheck = MAXDEPTH; while (q) { if (q->q_qinfo && q->q_qinfo->qi_minfo) { dname = q->q_qinfo->qi_minfo->mi_idname; if (!Strcmp(dname, parseinfo.st_rdinit->qi_minfo->mi_idname)) { /* * back home - phew (hopping along stream queues might * prove dangerous to your health) */ if ((((parsestream_t *)(void *)q->q_ptr)->parse_status & PARSE_ENABLE) && parse_iopps(&((parsestream_t *)(void *)q->q_ptr)->parse_io, (int)(status ? SYNC_ONE : SYNC_ZERO), &cdevent)) { /* * XXX - currently we do not pass up the message, as * we should. * for a correct behaviour wee need to block out * processing until parse_iodone has been posted via * a softcall-ed routine which does the message pass-up * right now PPS information relies on input being * received */ parse_iodone(&((parsestream_t *)(void *)q->q_ptr)->parse_io); } if (status) { ((parsestream_t *)(void *)q->q_ptr)->parse_ppsclockev.tv = cdevent.tv; ++(((parsestream_t *)(void *)q->q_ptr)->parse_ppsclockev.serial); } parseprintf(DD_ISR, ("zs_xsisr: CD event %s has been posted for \"%s\"\n", status ? "ONE" : "ZERO", dname)); break; } } q = q->q_next; if (!loopcheck--) { panic("zs_xsisr: STREAMS Queue corrupted - CD event"); } } /* * only pretend that CD has been handled */ ZSDELAY(2); if (!((za->za_rr0 ^ zsstatus) & ~(cdmask))) { /* * all done - kill status indication and return */ zsaddr->zscc_control = ZSWR0_RESET_STATUS; /* might kill other conditions here */ return 0; } } if (zsstatus & cdmask) /* fake CARRIER status */ za->za_flags |= ZAS_CARR_ON; else za->za_flags &= ~ZAS_CARR_ON; /* * we are now gathered here to process some unusual external status * interrupts. * any CD events have also been handled and shouldn't be processed * by the original routine (unless we have a VERY busy port pin) * some initializations are done here, which could have been done before for * both code paths but have been avoided for minimum path length to * the uniq_time routine */ dname = (char *) 0; q = za->za_ttycommon.t_readq; loopcheck = MAXDEPTH; /* * the real thing for everything else ... */ while (q) { if (q->q_qinfo && q->q_qinfo->qi_minfo) { dname = q->q_qinfo->qi_minfo->mi_idname; if (!Strcmp(dname, parseinfo.st_rdinit->qi_minfo->mi_idname)) { register int (*zsisr) (struct zscom *); /* * back home - phew (hopping along stream queues might * prove dangerous to your health) */ if ((zsisr = ((struct savedzsops *)((parsestream_t *)(void *)q->q_ptr)->parse_data)->oldzsops->zsop_xsint)) return zsisr(zs); else panic("zs_xsisr: unable to locate original ISR"); parseprintf(DD_ISR, ("zs_xsisr: non CD event was processed for \"%s\"\n", dname)); /* * now back to our program ... */ return 0; } } q = q->q_next; if (!loopcheck--) { panic("zs_xsisr: STREAMS Queue corrupted - non CD event"); } } /* * last resort - shouldn't even come here as it indicates * corrupted TTY structures */ printf("zs_zsisr: looking for \"%s\" - found \"%s\" - taking EMERGENCY path\n", parseinfo.st_rdinit->qi_minfo->mi_idname, dname ? dname : "-NIL-"); if (emergencyzs && emergencyzs->zsop_xsint) emergencyzs->zsop_xsint(zs); else panic("zs_xsisr: no emergency ISR handler"); return 0; }
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); }