diff options
Diffstat (limited to 'drivers/sbus/char/sab82532.c')
-rw-r--r-- | drivers/sbus/char/sab82532.c | 2173 |
1 files changed, 2173 insertions, 0 deletions
diff --git a/drivers/sbus/char/sab82532.c b/drivers/sbus/char/sab82532.c new file mode 100644 index 000000000..775fbbe5a --- /dev/null +++ b/drivers/sbus/char/sab82532.c @@ -0,0 +1,2173 @@ +/* $Id: sab82532.c,v 1.4 1997/09/03 17:04:21 ecd Exp $ + * sab82532.c: ASYNC Driver for the SIEMENS SAB82532 DUSCC. + * + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/fcntl.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/malloc.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <asm/sab82532.h> +#include <asm/uaccess.h> +#include <asm/ebus.h> + +#include "sunserial.h" + +static DECLARE_TASK_QUEUE(tq_serial); + +static struct tty_driver serial_driver, callout_driver; +static int sab82532_refcount; + +/* number of characters left in xmit buffer before we ask for more */ +#define WAKEUP_CHARS 256 + +/* Set of debugging defines */ +#undef SERIAL_DEBUG_OPEN +#undef SERIAL_DEBUG_INTR +#undef SERIAL_DEBUG_FLOW +#undef SERIAL_DEBUG_WAIT_UNTIL_SENT + +static void change_speed(struct sab82532 *info); +static void sab82532_wait_until_sent(struct tty_struct *tty, int timeout); + +/* + * This assumes you have a 29.4912 MHz clock for your UART. + */ +#define BASE_BAUD ( 29491200 / 16 ) + +static struct sab82532 *sab82532_chain = 0; +static struct tty_struct *sab82532_table[NR_PORTS]; +static struct termios *sab82532_termios[NR_PORTS]; +static struct termios *sab82532_termios_locked[NR_PORTS]; + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +/* + * tmp_buf is used as a temporary buffer by sab82532_write. We need to + * lock it in case the copy_from_user blocks while swapping in a page, + * and some other program tries to do a serial write at the same time. + * Since the lock will only come under contention when the system is + * swapping and available memory is low, it makes sense to share one + * buffer across all the serial ports, since it significantly saves + * memory if large numbers of serial ports are open. + */ +static unsigned char *tmp_buf = 0; +static struct semaphore tmp_buf_sem = MUTEX; + +static inline int serial_paranoia_check(struct sab82532 *info, + kdev_t device, const char *routine) +{ +#ifdef SERIAL_PARANOIA_CHECK + static const char *badmagic = + "Warning: bad magic number for serial struct (%s) in %s\n"; + static const char *badinfo = + "Warning: null sab82532 for (%s) in %s\n"; + + if (!info) { + printk(badinfo, kdevname(device), routine); + return 1; + } + if (info->magic != SERIAL_MAGIC) { + printk(badmagic, kdevname(device), routine); + return 1; + } +#endif + return 0; +} + +/* + * This is used to figure out the divisor speeds. + * + * The formula is: Baud = BASE_BAUD / ((N + 1) * (1 << M)), + * + * with 0 <= N < 64 and 0 <= M < 16 + * + * XXX: Speeds with M = 0 might not work properly for XTAL frequencies + * above 10 MHz. + */ +struct ebrg_struct { + int baud; + int n; + int m; +}; + +static struct ebrg_struct ebrg_table[] = { + { 0, 0, 0 }, + { 50, 35, 10 }, + { 75, 47, 9 }, + { 110, 32, 9 }, + { 134, 53, 8 }, + { 150, 47, 8 }, + { 200, 35, 8 }, + { 300, 47, 7 }, + { 600, 47, 6 }, + { 1200, 47, 5 }, + { 1800, 31, 5 }, + { 2400, 47, 4 }, + { 4800, 47, 3 }, + { 9600, 47, 2 }, + { 19200, 47, 1 }, + { 38400, 23, 1 }, + { 57600, 15, 1 }, + { 115200, 7, 1 }, + { 230400, 3, 1 }, + { 460800, 1, 1 }, + { 76800, 11, 1 }, + { 153600, 5, 1 }, + { 307200, 3, 1 }, + { 614400, 3, 0 }, + { 921600, 0, 1 }, +}; + +#define NR_EBRG_VALUES (sizeof(ebrg_table)/sizeof(struct ebrg_struct)) + +/* + * ------------------------------------------------------------ + * sab82532_stop() and sab82532_start() + * + * This routines are called before setting or resetting tty->stopped. + * They enable or disable transmitter interrupts, as necessary. + * ------------------------------------------------------------ + */ +static void sab82532_stop(struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->device, "sab82532_stop")) + return; + + save_flags(flags); cli(); + info->interrupt_mask1 |= SAB82532_IMR1_XPR; + info->regs->w.imr1 = info->interrupt_mask1; + restore_flags(flags); +} + +static void sab82532_start(struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->device, "sab82532_start")) + return; + + save_flags(flags); cli(); + info->interrupt_mask1 &= ~(SAB82532_IMR1_XPR); + info->regs->w.imr1 = info->interrupt_mask1; + restore_flags(flags); +} + +static void batten_down_hatches(void) +{ + /* If we are doing kadb, we call the debugger + * else we just drop into the boot monitor. + * Note that we must flush the user windows + * first before giving up control. + */ + printk("\n"); + flush_user_windows(); +#ifndef __sparc_v9__ + if ((((unsigned long)linux_dbvec) >= DEBUG_FIRSTVADDR) && + (((unsigned long)linux_dbvec) <= DEBUG_LASTVADDR)) + sp_enter_debugger(); + else +#endif + prom_cmdline(); +} + +/* + * ---------------------------------------------------------------------- + * + * Here starts the interrupt handling routines. All of the following + * subroutines are declared as inline and are folded into + * sab82532_interrupt(). They were separated out for readability's sake. + * + * Note: sab82532_interrupt() is a "fast" interrupt, which means that it + * runs with interrupts turned off. People who may want to modify + * sab82532_interrupt() should try to keep the interrupt handler as fast as + * possible. After you are done making modifications, it is not a bad + * idea to do: + * + * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c + * + * and look at the resulting assemble code in serial.s. + * + * - Ted Ts'o (tytso@mit.edu), 7-Mar-93 + * ----------------------------------------------------------------------- + */ + +/* + * This routine is used by the interrupt handler to schedule + * processing in the software interrupt portion of the driver. + */ +static inline void sab82532_sched_event(struct sab82532 *info, int event) +{ + info->event |= 1 << event; + queue_task(&info->tqueue, &tq_serial); + mark_bh(SERIAL_BH); +} + +static inline void receive_chars(struct sab82532 *info, + union sab82532_irq_status *stat) +{ + struct tty_struct *tty = info->tty; + unsigned char buf[32]; + unsigned char status; + int free_fifo = 0; + int i, count = 0; + + /* Read number of BYTES (Character + Status) available. */ + if (stat->sreg.isr0 & SAB82532_ISR0_RPF) { + count = info->recv_fifo_size; + free_fifo++; + } + if (stat->sreg.isr0 & SAB82532_ISR0_TCD) { + count = info->regs->r.rbcl & (info->recv_fifo_size - 1); + free_fifo++; + } + if (stat->sreg.isr0 & SAB82532_ISR0_RFO) { +#if 1 + printk("sab82532: receive_chars: RFO"); +#endif + free_fifo++; + } + + /* Issue a FIFO read command in case we where idle. */ + if (stat->sreg.isr0 & SAB82532_ISR0_TIME) { + if (info->regs->r.star & SAB82532_STAR_CEC) + udelay(1); + info->regs->w.cmdr = SAB82532_CMDR_RFRD; + } + + /* Read the FIFO. */ + for (i = 0; i < (count << 1); i++) + buf[i] = info->regs->r.rfifo[i]; + + /* Issue Receive Message Complete command. */ + if (free_fifo) { + if (info->regs->r.star & SAB82532_STAR_CEC) + udelay(1); + info->regs->w.cmdr = SAB82532_CMDR_RMC; + } + + for (i = 0; i < count; ) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { +#if 1 + printk("sab82532: receive_chars: tty overrun\n"); +#endif + info->icount.buf_overrun++; + break; + } + + tty->flip.count++; + *tty->flip.char_buf_ptr++ = buf[i++]; + status = buf[i++]; + info->icount.rx++; + +#ifdef SERIAL_DEBUG_INTR + printk("DR%02x:%02x...", (unsigned char)*(tty->flip.char_buf_ptr - 1), status); +#endif + + if (status & SAB82532_RSTAT_PE) { + *tty->flip.flag_buf_ptr++ = TTY_PARITY; + info->icount.parity++; + } else if (status & SAB82532_RSTAT_FE) { + *tty->flip.flag_buf_ptr++ = TTY_FRAME; + info->icount.frame++; + } +#ifdef CMSPAR + else if (status & SAB82532_RSTAT_PARITY) + *tty->flip.flag_buf_ptr++ = TTY_PARITY; +#endif + else + *tty->flip.flag_buf_ptr++ = TTY_NORMAL; + } + + queue_task(&tty->flip.tqueue, &tq_timer); +} + +static inline void transmit_chars(struct sab82532 *info, + union sab82532_irq_status *stat) +{ + int i; + + if ((info->xmit_cnt <= 0) || info->tty->stopped || + info->tty->hw_stopped) { + if (stat->sreg.isr1 & SAB82532_ISR1_ALLS) + info->all_sent = 1; + info->interrupt_mask1 |= SAB82532_IMR1_XPR; + info->regs->w.imr1 = info->interrupt_mask1; + return; + } + + /* Stuff 32 bytes into Transmit FIFO. */ + info->all_sent = 0; + for (i = 0; i < info->xmit_fifo_size; i++) { + info->regs->w.xfifo[i] = info->xmit_buf[info->xmit_tail++]; + info->xmit_tail &= (SERIAL_XMIT_SIZE - 1); + info->icount.tx++; + if (--info->xmit_cnt <= 0) + break; + } + + /* Issue a Transmit Frame command. */ + if (info->regs->r.star & SAB82532_STAR_CEC) + udelay(1); + info->regs->w.cmdr = SAB82532_CMDR_XF; + + if (info->xmit_cnt < WAKEUP_CHARS) + sab82532_sched_event(info, RS_EVENT_WRITE_WAKEUP); + +#ifdef SERIAL_DEBUG_INTR + printk("THRE..."); +#endif + if (info->xmit_cnt <= 0) { + info->interrupt_mask1 |= SAB82532_IMR1_XPR; + info->regs->w.imr1 = info->interrupt_mask1; + } +} + +static inline void check_status(struct sab82532 *info, + union sab82532_irq_status *stat) +{ + struct tty_struct *tty = info->tty; + int modem_change = 0; + + if (stat->sreg.isr1 & SAB82532_ISR1_BRK) { + if (info->is_console) { + batten_down_hatches(); + return; + } + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + info->icount.buf_overrun++; + goto check_modem; + } + tty->flip.count++; + *tty->flip.flag_buf_ptr++ = TTY_PARITY; + *tty->flip.char_buf_ptr++ = 0; + info->icount.brk++; + } + + if (stat->sreg.isr0 & SAB82532_ISR0_RFO) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + info->icount.buf_overrun++; + goto check_modem; + } + tty->flip.count++; + *tty->flip.flag_buf_ptr++ = TTY_PARITY; + *tty->flip.char_buf_ptr++ = 0; + info->icount.overrun++; + } + + if (info->is_console) + return; + +check_modem: + if (stat->sreg.isr0 & SAB82532_ISR0_CDSC) { + info->dcd = (info->regs->r.vstr & SAB82532_VSTR_CD) ? 0 : 1; + info->icount.dcd++; + modem_change++; +#if 0 + printk("DCD change: %d\n", info->icount.dcd); +#endif + } + if (stat->sreg.isr1 & SAB82532_ISR1_CSC) { + info->cts = info->regs->r.star & SAB82532_STAR_CTS; + info->icount.cts++; + modem_change++; +#if 0 + printk("CTS change: %d, CTS %s\n", info->icount.cts, info->cts ? "on" : "off"); +#endif + } + if ((info->regs->r.pvr & info->pvr_dsr_bit) ^ info->dsr) { + info->dsr = info->regs->r.pvr & info->pvr_dsr_bit; + info->icount.dsr++; + modem_change++; +#if 0 + printk("DSR change: %d\n", info->icount.dsr); +#endif + } + if (modem_change) + wake_up_interruptible(&info->delta_msr_wait); + + if ((info->flags & ASYNC_CHECK_CD) && + (stat->sreg.isr0 & SAB82532_ISR0_CDSC)) { + +#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR)) + printk("ttys%d CD now %s...", info->line, + (info->dcd) ? "on" : "off"); +#endif + + if (info->dcd) + wake_up_interruptible(&info->open_wait); + else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) && + (info->flags & ASYNC_CALLOUT_NOHUP))) { + +#ifdef SERIAL_DEBUG_OPEN + printk("scheduling hangup..."); +#endif + + queue_task(&info->tqueue_hangup, &tq_scheduler); + } + } + + if (info->flags & ASYNC_CTS_FLOW) { + if (info->tty->hw_stopped) { + if (info->cts) { + +#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) + printk("CTS tx start..."); +#endif + info->tty->hw_stopped = 0; + sab82532_sched_event(info, + RS_EVENT_WRITE_WAKEUP); + info->interrupt_mask1 &= ~(SAB82532_IMR1_XPR); + info->regs->w.imr1 = info->interrupt_mask1; + } + } else { + if (!(info->cts)) { + +#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) + printk("CTS tx stop..."); +#endif + info->tty->hw_stopped = 1; + } + } + } +} + +/* + * This is the serial driver's generic interrupt routine + */ +static void sab82532_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct sab82532 *info = dev_id; + union sab82532_irq_status status; + +#ifdef SERIAL_DEBUG_INTR + printk("sab82532_interrupt(%d)...", irq); +#endif + + status.stat = 0; + if (info->regs->r.gis & SAB82532_GIS_ISA0) + status.sreg.isr0 = info->regs->r.isr0; + if (info->regs->r.gis & SAB82532_GIS_ISA1) + status.sreg.isr1 = info->regs->r.isr1; + +#ifdef SERIAL_DEBUG_INTR + printk("%d<%02x.%02x>", info->line, + status.sreg.isr0, status.sreg.isr1); +#endif + + if (!status.stat) + goto next; + + if (status.sreg.isr0 & (SAB82532_ISR0_TCD | SAB82532_ISR0_TIME | + SAB82532_ISR0_RFO | SAB82532_ISR0_RPF)) + receive_chars(info, &status); + if ((status.sreg.isr0 & SAB82532_ISR0_CDSC) || + (status.sreg.isr1 & (SAB82532_ISR1_BRK | SAB82532_ISR1_CSC))) + check_status(info, &status); + if (status.sreg.isr1 & (SAB82532_ISR1_ALLS | SAB82532_ISR1_XPR)) + transmit_chars(info, &status); + +next: + info = info->next; + status.stat = 0; + if (info->regs->r.gis & SAB82532_GIS_ISB0) + status.sreg.isr0 = info->regs->r.isr0; + if (info->regs->r.gis & SAB82532_GIS_ISB1) + status.sreg.isr1 = info->regs->r.isr1; + +#ifdef SERIAL_DEBUG_INTR + printk("%d<%02x.%02x>", info->line, + status.sreg.isr0, status.sreg.isr1); +#endif + + if (!status.stat) + goto done; + + if (status.sreg.isr0 & (SAB82532_ISR0_TCD | SAB82532_ISR0_TIME | + SAB82532_ISR0_RFO | SAB82532_ISR0_RPF)) + receive_chars(info, &status); + if ((status.sreg.isr0 & SAB82532_ISR0_CDSC) || + (status.sreg.isr1 & (SAB82532_ISR1_BRK | SAB82532_ISR1_CSC))) + check_status(info, &status); + if (status.sreg.isr1 & (SAB82532_ISR1_ALLS | SAB82532_ISR1_XPR)) + transmit_chars(info, &status); + +done: +#ifdef SERIAL_DEBUG_INTR + printk("end.\n"); +#endif +} + +/* + * ------------------------------------------------------------------- + * Here ends the serial interrupt routines. + * ------------------------------------------------------------------- + */ + +/* + * This routine is used to handle the "bottom half" processing for the + * serial driver, known also the "software interrupt" processing. + * This processing is done at the kernel interrupt level, after the + * sab82532_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This + * is where time-consuming activities which can not be done in the + * interrupt driver proper are done; the interrupt driver schedules + * them using sab82532_sched_event(), and they get done here. + */ +static void do_serial_bh(void) +{ + run_task_queue(&tq_serial); +} + +static void do_softint(void *private_) +{ + struct sab82532 *info = (struct sab82532 *)private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + wake_up_interruptible(&tty->write_wait); + } +} + +/* + * This routine is called from the scheduler tqueue when the interrupt + * routine has signalled that a hangup has occurred. The path of + * hangup processing is: + * + * serial interrupt routine -> (scheduler tqueue) -> + * do_serial_hangup() -> tty->hangup() -> sab82532_hangup() + * + */ +static void do_serial_hangup(void *private_) +{ + struct sab82532 *info = (struct sab82532 *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + tty_hangup(tty); +} + + +static int startup(struct sab82532 *info) +{ + unsigned long flags; + unsigned long page; + unsigned char stat; + + page = get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + save_flags(flags); cli(); + + if (info->flags & ASYNC_INITIALIZED) { + free_page(page); + goto errout; + } + + if (!info->regs) { + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + free_page(page); + goto errout; + } + if (info->xmit_buf) + free_page(page); + else + info->xmit_buf = (unsigned char *)page; + +#ifdef SERIAL_DEBUG_OPEN + printk("starting up serial port %d...", info->line); +#endif + + /* + * Clear the FIFO buffers. + */ + if (info->regs->r.star & SAB82532_STAR_CEC) + udelay(1); + info->regs->w.cmdr = SAB82532_CMDR_RRES; + if (info->regs->r.star & SAB82532_STAR_CEC) + udelay(1); + info->regs->w.cmdr = SAB82532_CMDR_XRES; + + /* + * Clear the interrupt registers. + */ + stat = info->regs->r.isr0; + stat = info->regs->r.isr1; + + /* + * Now, initialize the UART + */ + info->regs->w.ccr0 = 0; /* power-down */ + info->regs->w.ccr0 = SAB82532_CCR0_MCE | SAB82532_CCR0_SC_NRZ | + SAB82532_CCR0_SM_ASYNC; + info->regs->w.ccr1 = SAB82532_CCR1_ODS | SAB82532_CCR1_BCR | 7; + info->regs->w.ccr2 = SAB82532_CCR2_BDF | SAB82532_CCR2_SSEL | + SAB82532_CCR2_TOE; + info->regs->w.ccr3 = 0; + info->regs->w.ccr4 = SAB82532_CCR4_MCK4 | SAB82532_CCR4_EBRG; + info->regs->w.mode = SAB82532_MODE_RTS | SAB82532_MODE_FCTS | + SAB82532_MODE_RAC; + info->regs->w.rfc = SAB82532_RFC_DPS | SAB82532_RFC_RFDF; + switch (info->recv_fifo_size) { + case 1: + info->regs->w.rfc |= SAB82532_RFC_RFTH_1; + break; + case 4: + info->regs->w.rfc |= SAB82532_RFC_RFTH_4; + break; + case 16: + info->regs->w.rfc |= SAB82532_RFC_RFTH_16; + break; + default: + info->recv_fifo_size = 32; + /* fall through */ + case 32: + info->regs->w.rfc |= SAB82532_RFC_RFTH_32; + break; + } + info->regs->rw.ccr0 |= SAB82532_CCR0_PU; /* power-up */ + + /* + * Finally, enable interrupts + */ + info->interrupt_mask0 = SAB82532_IMR0_PERR | SAB82532_IMR0_FERR | + SAB82532_IMR0_PLLA; + info->regs->w.imr0 = info->interrupt_mask0; + info->interrupt_mask1 = SAB82532_IMR1_BRKT | SAB82532_IMR1_XOFF | + SAB82532_IMR1_TIN | SAB82532_IMR1_XON | + SAB82532_IMR1_XPR; + info->regs->w.imr1 = info->interrupt_mask1; + + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; + + /* + * and set the speed of the serial port + */ + change_speed(info); + + info->flags |= ASYNC_INITIALIZED; + restore_flags(flags); + return 0; + +errout: + restore_flags(flags); + return -ENODEV; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void shutdown(struct sab82532 *info) +{ + unsigned long flags; + + if (!(info->flags & ASYNC_INITIALIZED)) + return; + +#ifdef SERIAL_DEBUG_OPEN + printk("Shutting down serial port %d...", info->line); +#endif + + save_flags(flags); cli(); /* 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->delta_msr_wait); + + if (info->xmit_buf) { + free_page((unsigned long)info->xmit_buf); + info->xmit_buf = 0; + } + + /* Disable Interrupts */ + info->interrupt_mask0 = 0xff; + info->regs->w.imr0 = info->interrupt_mask0; + info->interrupt_mask1 = 0xff; + info->regs->w.imr1 = info->interrupt_mask1; + + if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { + info->regs->rw.mode |= SAB82532_MODE_FRTS; + info->regs->rw.mode |= SAB82532_MODE_RTS; + info->regs->rw.pvr |= info->pvr_dtr_bit; + } + + /* Disable break condition */ + info->regs->rw.dafo &= ~(SAB82532_DAFO_XBRK); + + /* Disable Receiver */ + info->regs->rw.mode &= ~(SAB82532_MODE_RAC); + + /* Power Down */ + info->regs->rw.ccr0 &= ~(SAB82532_CCR0_PU); + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + info->flags &= ~ASYNC_INITIALIZED; + restore_flags(flags); +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void change_speed(struct sab82532 *info) +{ + unsigned long flags; + unsigned int ebrg; + tcflag_t cflag; + unsigned char dafo; + int i; + + if (!info->tty || !info->tty->termios) + return; + cflag = info->tty->termios->c_cflag; + + /* Byte size and parity */ + switch (cflag & CSIZE) { + case CS5: dafo = SAB82532_DAFO_CHL5; break; + case CS6: dafo = SAB82532_DAFO_CHL6; break; + case CS7: dafo = SAB82532_DAFO_CHL7; break; + case CS8: dafo = SAB82532_DAFO_CHL8; break; + /* Never happens, but GCC is too dumb to figure it out */ + default: dafo = SAB82532_DAFO_CHL5; break; + } + + if (cflag & CSTOPB) + dafo |= SAB82532_DAFO_STOP; + + if (cflag & PARENB) + dafo |= SAB82532_DAFO_PARE; + + if (cflag & PARODD) { +#ifdef CMSPAR + if (cflag & CMSPAR) + dafo |= SAB82532_DAFO_PAR_MARK; + else +#endif + dafo |= SAB82532_DAFO_PAR_ODD; + } else { +#ifdef CMSPAR + if (cflag & CMSPAR) + dafo |= SAB82532_DAFO_PAR_SPACE; + else +#endif + dafo |= SAB82532_DAFO_PAR_EVEN; + } + + /* Determine EBRG values based on baud rate */ + i = cflag & CBAUD; + if (i & CBAUDEX) { + i &= ~(CBAUDEX); + if ((i < 1) || ((i + 15) >= NR_EBRG_VALUES)) + info->tty->termios->c_cflag &= ~CBAUDEX; + else + i += 15; + } + ebrg = ebrg_table[i].n; + ebrg |= (ebrg_table[i].m << 6); + + /* CTS flow control flags */ + if (cflag & CRTSCTS) + info->flags |= ASYNC_CTS_FLOW; + else + info->flags &= ~(ASYNC_CTS_FLOW); + + if (cflag & CLOCAL) + info->flags &= ~(ASYNC_CHECK_CD); + else + info->flags |= ASYNC_CHECK_CD; + if (info->tty) + info->tty->hw_stopped = 0; + + /* + * Set up parity check flag + * XXX: not implemented, yet. + */ +#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + + /* + * Characters to ignore + * XXX: not implemented, yet. + */ + + /* + * !!! ignore all characters if CREAD is not set + * XXX: not implemented, yet. + */ + if ((cflag & CREAD) == 0) + info->ignore_status_mask |= SAB82532_ISR0_RPF | + SAB82532_ISR0_TCD | + SAB82532_ISR0_TIME; + + save_flags(flags); cli(); + info->regs->w.dafo = dafo; + info->regs->w.bgr = ebrg & 0xff; + info->regs->rw.ccr2 &= ~(0xc0); + info->regs->rw.ccr2 |= (ebrg >> 2) & 0xc0; + if (info->flags & ASYNC_CTS_FLOW) { + info->regs->rw.mode &= ~(SAB82532_MODE_RTS); + info->regs->rw.mode |= SAB82532_MODE_FRTS; + info->regs->rw.mode &= ~(SAB82532_MODE_FCTS); + } else { + info->regs->rw.mode |= SAB82532_MODE_RTS; + info->regs->rw.mode &= ~(SAB82532_MODE_FRTS); + info->regs->rw.mode |= SAB82532_MODE_FCTS; + } + info->regs->rw.mode |= SAB82532_MODE_RAC; + restore_flags(flags); +} + +static void sab82532_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->device, "sab82532_put_char")) + return; + + if (!tty || !info->xmit_buf) + return; + + save_flags(flags); cli(); + if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1) { + restore_flags(flags); + return; + } + + info->xmit_buf[info->xmit_head++] = ch; + info->xmit_head &= SERIAL_XMIT_SIZE-1; + info->xmit_cnt++; + restore_flags(flags); +} + +static void sab82532_flush_chars(struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->device, "sab82532_flush_chars")) + return; + + if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || + !info->xmit_buf) + return; + + save_flags(flags); cli(); + info->interrupt_mask1 &= ~(SAB82532_IMR1_XPR); + info->regs->w.imr1 = info->interrupt_mask1; + restore_flags(flags); +} + +static int sab82532_write(struct tty_struct * tty, int from_user, + const unsigned char *buf, int count) +{ + int c, ret = 0; + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->device, "sab82532_write")) + return 0; + + if (!tty || !info->xmit_buf || !tmp_buf) + return 0; + + if (from_user) + down(&tmp_buf_sem); + save_flags(flags); + while (1) { + cli(); + c = MIN(count, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, + SERIAL_XMIT_SIZE - info->xmit_head)); + if (c <= 0) + break; + + if (from_user) { + c -= copy_from_user(tmp_buf, buf, c); + if (!c) { + if (!ret) + ret = -EFAULT; + break; + } + c = MIN(c, MIN(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, + SERIAL_XMIT_SIZE - info->xmit_head)); + memcpy(info->xmit_buf + info->xmit_head, tmp_buf, c); + } else + memcpy(info->xmit_buf + info->xmit_head, buf, c); + info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1); + info->xmit_cnt += c; + restore_flags(flags); + buf += c; + count -= c; + ret += c; + } + if (from_user) + up(&tmp_buf_sem); + + if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped && + (info->interrupt_mask1 & SAB82532_IMR1_XPR)) { + info->interrupt_mask1 &= ~(SAB82532_IMR1_XPR); + info->regs->w.imr1 = info->interrupt_mask1; + } + + restore_flags(flags); + return ret; +} + +static int sab82532_write_room(struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + int ret; + + if (serial_paranoia_check(info, tty->device, "sab82532_write_room")) + return 0; + ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1; + if (ret < 0) + ret = 0; + return ret; +} + +static int sab82532_chars_in_buffer(struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + + if (serial_paranoia_check(info, tty->device, "sab82532_chars_in_buffer")) + return 0; + return info->xmit_cnt; +} + +static void sab82532_flush_buffer(struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + + if (serial_paranoia_check(info, tty->device, "sab82532_flush_buffer")) + return; + cli(); + info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; + sti(); + wake_up_interruptible(&tty->write_wait); + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); +} + +/* + * This function is used to send a high-priority XON/XOFF character to + * the device + */ +static void sab82532_send_xchar(struct tty_struct *tty, char ch) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + + if (serial_paranoia_check(info, tty->device, "sab82532_send_xchar")) + return; + + if (info->regs->r.star & SAB82532_STAR_TEC) + udelay(1); + info->regs->w.tic = ch; +} + +/* + * ------------------------------------------------------------ + * sab82532_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void sab82532_throttle(struct tty_struct * tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; +#ifdef SERIAL_DEBUG_THROTTLE + char buf[64]; + + printk("throttle %s: %d....\n", _tty_name(tty, buf), + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (serial_paranoia_check(info, tty->device, "sab82532_throttle")) + return; + + if (I_IXOFF(tty)) + sab82532_send_xchar(tty, STOP_CHAR(tty)); +#if 0 + if (tty->termios->c_cflag & CRTSCTS) + info->regs->rw.mode |= SAB82532_MODE_RTS; +#endif +} + +static void sab82532_unthrottle(struct tty_struct * tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; +#ifdef SERIAL_DEBUG_THROTTLE + char buf[64]; + + printk("unthrottle %s: %d....\n", _tty_name(tty, buf), + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (serial_paranoia_check(info, tty->device, "sab82532_unthrottle")) + return; + + if (I_IXOFF(tty)) { + if (info->x_char) + info->x_char = 0; + else + sab82532_send_xchar(tty, START_CHAR(tty)); + } + +#if 0 + if (tty->termios->c_cflag & CRTSCTS) + info->regs->rw.mode &= ~(SAB82532_MODE_RTS); +#endif +} + +/* + * ------------------------------------------------------------ + * sab82532_ioctl() and friends + * ------------------------------------------------------------ + */ + +static int get_serial_info(struct sab82532 *info, + struct serial_struct *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + memset(&tmp, 0, sizeof(tmp)); + tmp.type = info->type; + tmp.line = info->line; + tmp.port = (unsigned long)info->regs; + tmp.irq = info->irq; + tmp.flags = info->flags; + tmp.xmit_fifo_size = info->xmit_fifo_size; + tmp.baud_base = info->baud_base; + tmp.close_delay = info->close_delay; + tmp.closing_wait = info->closing_wait; + tmp.custom_divisor = info->custom_divisor; + tmp.hub6 = 0; + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct sab82532 *info, + struct serial_struct *new_info) +{ + return 0; +} + + +/* + * 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. + */ +static int get_lsr_info(struct sab82532 * info, unsigned int *value) +{ + unsigned int result; + + result = info->all_sent ? TIOCSER_TEMT : 0; + return put_user(result, value); +} + + +static int get_modem_info(struct sab82532 * info, unsigned int *value) +{ + unsigned int result; + + result = ((info->regs->r.mode & SAB82532_MODE_FRTS) ? 0 : TIOCM_RTS) + | ((info->regs->r.pvr & info->pvr_dtr_bit) ? 0 : TIOCM_DTR) + | ((info->regs->r.vstr & SAB82532_VSTR_CD) ? 0 : TIOCM_CAR) + | ((info->regs->r.pvr & info->pvr_dsr_bit) ? 0 : TIOCM_DSR) + | ((info->regs->r.star & SAB82532_STAR_CTS) ? TIOCM_CTS : 0); + return put_user(result,value); +} + +static int set_modem_info(struct sab82532 * info, unsigned int cmd, + unsigned int *value) +{ + int error; + unsigned int arg; + + error = get_user(arg, value); + if (error) + return error; + switch (cmd) { + case TIOCMBIS: + if (arg & TIOCM_RTS) { + info->regs->rw.mode &= ~(SAB82532_MODE_FRTS); + info->regs->rw.mode |= SAB82532_MODE_RTS; + } + if (arg & TIOCM_DTR) { + info->regs->rw.pvr &= ~(info->pvr_dtr_bit); + } + break; + case TIOCMBIC: + if (arg & TIOCM_RTS) { + info->regs->rw.mode |= SAB82532_MODE_FRTS; + info->regs->rw.mode |= SAB82532_MODE_RTS; + } + if (arg & TIOCM_DTR) { + info->regs->rw.pvr |= info->pvr_dtr_bit; + } + break; + case TIOCMSET: + if (arg & TIOCM_RTS) { + info->regs->rw.mode &= ~(SAB82532_MODE_FRTS); + info->regs->rw.mode |= SAB82532_MODE_RTS; + } else { + info->regs->rw.mode |= SAB82532_MODE_FRTS; + info->regs->rw.mode |= SAB82532_MODE_RTS; + } + if (arg & TIOCM_DTR) { + info->regs->rw.pvr &= ~(info->pvr_dtr_bit); + } else { + info->regs->rw.pvr |= info->pvr_dtr_bit; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * This routine sends a break character out the serial port. + */ +static void send_break( struct sab82532 * info, int duration) +{ + if (!info->regs) + return; + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + duration; +#ifdef SERIAL_DEBUG_SEND_BREAK + printk("sab82532_send_break(%d) jiff=%lu...", duration, jiffies); +#endif + cli(); + info->regs->rw.dafo |= SAB82532_DAFO_XBRK; + schedule(); + info->regs->rw.dafo &= ~(SAB82532_DAFO_XBRK); + sti(); +#ifdef SERIAL_DEBUG_SEND_BREAK + printk("done jiffies=%lu\n", jiffies); +#endif +} + +/* + * This routine sets the break condition on the serial port. + */ +static void begin_break(struct sab82532 * info) +{ + if (!info->regs) + return; + info->regs->rw.dafo |= SAB82532_DAFO_XBRK; +} + +/* + * This routine clears the break condition on the serial port. + */ +static void end_break(struct sab82532 * info) +{ + if (!info->regs) + return; + info->regs->rw.dafo &= ~(SAB82532_DAFO_XBRK); +} + +static int sab82532_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + int error; + struct sab82532 * info = (struct sab82532 *)tty->driver_data; + int retval; + struct async_icount cprev, cnow; /* kernel counter temps */ + struct serial_icounter_struct *p_cuser; /* user space */ + + if (serial_paranoia_check(info, tty->device, "sab82532_ioctl")) + return -ENODEV; + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) && + (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT) && + (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } + + switch (cmd) { + case TCSBRK: /* SVID version: non-zero arg --> no break */ + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + if (current->signal & ~current->blocked) + return -EINTR; + if (!arg) { + send_break(info, HZ/4); /* 1/4 second */ + if (current->signal & ~current->blocked) + return -EINTR; + } + return 0; + case TCSBRKP: /* support for POSIX tcsendbreak() */ + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + if (current->signal & ~current->blocked) + return -EINTR; + send_break(info, arg ? arg*(HZ/10) : HZ/4); + if (current->signal & ~current->blocked) + return -EINTR; + return 0; + case TIOCSBRK: + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + begin_break(info); + return 0; + case TIOCCBRK: + retval = tty_check_change(tty); + if (retval) + return retval; + end_break(info); + return 0; + case TIOCGSOFTCAR: + return put_user(C_CLOCAL(tty) ? 1 : 0, (int *) arg); + case TIOCSSOFTCAR: + error = get_user(arg, (unsigned int *) arg); + if (error) + return error; + tty->termios->c_cflag = + ((tty->termios->c_cflag & ~CLOCAL) | + (arg ? CLOCAL : 0)); + return 0; + case TIOCMGET: + return get_modem_info(info, (unsigned int *) arg); + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + return set_modem_info(info, cmd, (unsigned int *) arg); + case TIOCGSERIAL: + return get_serial_info(info, + (struct serial_struct *) arg); + case TIOCSSERIAL: + return set_serial_info(info, + (struct serial_struct *) arg); + + case TIOCSERGETLSR: /* Get line status register */ + return get_lsr_info(info, (unsigned int *) arg); + + case TIOCSERGSTRUCT: + if (copy_to_user((struct sab82532 *) arg, + info, sizeof(struct sab82532))) + return -EFAULT; + return 0; + + /* + * 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: + cli(); + /* note the counters on entry */ + cprev = info->icount; + sti(); + while (1) { + interruptible_sleep_on(&info->delta_msr_wait); + /* see if a signal did it */ + if (current->signal & ~current->blocked) + return -ERESTARTSYS; + cli(); + cnow = info->icount; /* atomic copy */ + sti(); + if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && + cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) + return -EIO; /* no change => error */ + 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)) ) { + return 0; + } + cprev = cnow; + } + /* NOTREACHED */ + + /* + * 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. + */ + case TIOCGICOUNT: + cli(); + cnow = info->icount; + sti(); + p_cuser = (struct serial_icounter_struct *) arg; + error = put_user(cnow.cts, &p_cuser->cts); + if (error) return error; + error = put_user(cnow.dsr, &p_cuser->dsr); + if (error) return error; + error = put_user(cnow.rng, &p_cuser->rng); + if (error) return error; + error = put_user(cnow.dcd, &p_cuser->dcd); + if (error) return error; + return 0; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static void sab82532_set_termios(struct tty_struct *tty, + struct termios *old_termios) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + + if ( (tty->termios->c_cflag == old_termios->c_cflag) + && ( RELEVANT_IFLAG(tty->termios->c_iflag) + == RELEVANT_IFLAG(old_termios->c_iflag))) + return; + + change_speed(info); + + /* Handle transition to B0 status */ + if ((old_termios->c_cflag & CBAUD) && + !(tty->termios->c_cflag & CBAUD)) { + info->regs->w.mode |= SAB82532_MODE_FRTS; + info->regs->w.mode |= SAB82532_MODE_RTS; + info->regs->w.pvr |= info->pvr_dtr_bit; + } + + /* Handle transition away from B0 status */ + if (!(old_termios->c_cflag & CBAUD) && + (tty->termios->c_cflag & CBAUD)) { + info->regs->w.pvr &= ~(info->pvr_dtr_bit); + if (!tty->hw_stopped || + !(tty->termios->c_cflag & CRTSCTS)) { + info->regs->w.mode &= ~(SAB82532_MODE_FRTS); + info->regs->w.mode |= SAB82532_MODE_RTS; + } + } + + /* Handle turning off CRTSCTS */ + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) { + tty->hw_stopped = 0; + sab82532_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) && + (tty->termios->c_cflag & CLOCAL)) + wake_up_interruptible(&info->open_wait); +#endif +} + +/* + * ------------------------------------------------------------ + * sab82532_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. + * ------------------------------------------------------------ + */ +static void sab82532_close(struct tty_struct *tty, struct file * filp) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long flags; + + if (!info || serial_paranoia_check(info, tty->device, "sab82532_close")) + return; + + save_flags(flags); cli(); + + if (tty_hung_up_p(filp)) { + MOD_DEC_USE_COUNT; + restore_flags(flags); + return; + } + +#ifdef SERIAL_DEBUG_OPEN + printk("sab82532_close ttys%d, count = %d\n", info->line, info->count); +#endif + if ((tty->count == 1) && (info->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. info->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk("sab82532_close: bad serial port count; tty->count is 1," + " info->count is %d\n", info->count); + info->count = 1; + } + if (--info->count < 0) { + printk("sab82532_close: bad serial port count for ttys%d: %d\n", + info->line, info->count); + info->count = 0; + } + if (info->count) { + MOD_DEC_USE_COUNT; + restore_flags(flags); + return; + } + info->flags |= ASYNC_CLOSING; + /* + * Save the termios structure, since this port may have + * separate termios for callout and dialin. + */ + if (info->flags & ASYNC_NORMAL_ACTIVE) + info->normal_termios = *tty->termios; + if (info->flags & ASYNC_CALLOUT_ACTIVE) + info->callout_termios = *tty->termios; + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, info->closing_wait); + + /* + * At this point we stop accepting input. To do this, we + * disable the receive line status interrupts, and turn off + * the receiver. + */ + info->interrupt_mask0 |= SAB82532_IMR0_TCD; + info->regs->w.imr0 = info->interrupt_mask0; + info->regs->rw.mode &= ~(SAB82532_MODE_RAC); + if (info->flags & ASYNC_INITIALIZED) { + /* + * Before we drop DTR, make sure the UART transmitter + * has completely drained; this is especially + * important if there is a transmit FIFO! + */ + sab82532_wait_until_sent(tty, info->timeout); + } + shutdown(info); + if (tty->driver.flush_buffer) + tty->driver.flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + tty->closing = 0; + info->event = 0; + info->tty = 0; + if (info->blocked_open) { + if (info->close_delay) { + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + info->close_delay; + schedule(); + } + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE| + ASYNC_CLOSING); + wake_up_interruptible(&info->close_wait); + MOD_DEC_USE_COUNT; + restore_flags(flags); +} + +/* + * sab82532_wait_until_sent() --- wait until the transmitter is empty + */ +static void sab82532_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + unsigned long orig_jiffies, char_time; + + if (serial_paranoia_check(info,tty->device,"sab82532_wait_until_sent")) + return; + + 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) / info->xmit_fifo_size; + char_time = char_time / 5; + if (char_time == 0) + char_time = 1; + if (timeout) + char_time = MIN(char_time, timeout); +#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT + printk("In sab82532_wait_until_sent(%d) check=%lu...", timeout, char_time); + printk("jiff=%lu...", jiffies); +#endif + + /* XXX: Implement this... */ + + current->state = TASK_RUNNING; +#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT + printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies); +#endif +} + +/* + * sab82532_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +static void sab82532_hangup(struct tty_struct *tty) +{ + struct sab82532 * info = (struct sab82532 *)tty->driver_data; + + if (serial_paranoia_check(info, tty->device, "sab82532_hangup")) + return; + + sab82532_flush_buffer(tty); + shutdown(info); + info->event = 0; + info->count = 0; + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE); + info->tty = 0; + wake_up_interruptible(&info->open_wait); +} + +/* + * ------------------------------------------------------------ + * sab82532_open() and friends + * ------------------------------------------------------------ + */ +static int block_til_ready(struct tty_struct *tty, struct file * filp, + struct sab82532 *info) +{ + struct wait_queue wait = { current, NULL }; + int retval; + int do_clocal = 0; + + /* + * If the device is in the middle of being closed, then block + * until it's done, and then try again. + */ + if (tty_hung_up_p(filp) || + (info->flags & ASYNC_CLOSING)) { + if (info->flags & ASYNC_CLOSING) + interruptible_sleep_on(&info->close_wait); +#ifdef SERIAL_DO_RESTART + if (info->flags & ASYNC_HUP_NOTIFY) + return -EAGAIN; + else + return -ERESTARTSYS; +#else + return -EAGAIN; +#endif + } + + /* + * If this is a callout device, then just make sure the normal + * device isn't being used. + */ + if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) { + if (info->flags & ASYNC_NORMAL_ACTIVE) + return -EBUSY; + if ((info->flags & ASYNC_CALLOUT_ACTIVE) && + (info->flags & ASYNC_SESSION_LOCKOUT) && + (info->session != current->session)) + return -EBUSY; + if ((info->flags & ASYNC_CALLOUT_ACTIVE) && + (info->flags & ASYNC_PGRP_LOCKOUT) && + (info->pgrp != current->pgrp)) + return -EBUSY; + info->flags |= ASYNC_CALLOUT_ACTIVE; + return 0; + } + + /* + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. + */ + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { + if (info->flags & ASYNC_CALLOUT_ACTIVE) + return -EBUSY; + info->flags |= ASYNC_NORMAL_ACTIVE; + return 0; + } + + if (info->flags & ASYNC_CALLOUT_ACTIVE) { + if (info->normal_termios.c_cflag & CLOCAL) + do_clocal = 1; + } else { + if (tty->termios->c_cflag & CLOCAL) + do_clocal = 1; + } + + /* + * Block waiting for the carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, info->count is dropped by one, so that + * sab82532_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + retval = 0; + add_wait_queue(&info->open_wait, &wait); +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready before block: ttyS%d, count = %d\n", + info->line, info->count); +#endif + cli(); + if (!tty_hung_up_p(filp)) + info->count--; + sti(); + info->blocked_open++; + while (1) { + cli(); + if (!(info->flags & ASYNC_CALLOUT_ACTIVE) && + (tty->termios->c_cflag & CBAUD)) { + info->regs->rw.pvr &= ~(info->pvr_dtr_bit); + info->regs->rw.mode |= SAB82532_MODE_FRTS; + info->regs->rw.mode &= ~(SAB82532_MODE_RTS); + } + sti(); + current->state = TASK_INTERRUPTIBLE; + if (tty_hung_up_p(filp) || + !(info->flags & ASYNC_INITIALIZED)) { +#ifdef SERIAL_DO_RESTART + if (info->flags & ASYNC_HUP_NOTIFY) + retval = -EAGAIN; + else + retval = -ERESTARTSYS; +#else + retval = -EAGAIN; +#endif + break; + } + if (!(info->flags & ASYNC_CALLOUT_ACTIVE) && + !(info->flags & ASYNC_CLOSING) && + (do_clocal || !(info->regs->r.vstr & SAB82532_VSTR_CD))) + break; + if (current->signal & ~current->blocked) { + retval = -ERESTARTSYS; + break; + } +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready blocking: ttyS%d, count = %d, flags = %x, clocal = %d, vstr = %02x\n", + info->line, info->count, info->flags, do_clocal, info->regs->r.vstr); +#endif + schedule(); + } + current->state = TASK_RUNNING; + remove_wait_queue(&info->open_wait, &wait); + if (!tty_hung_up_p(filp)) + info->count++; + info->blocked_open--; +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready after blocking: ttys%d, count = %d\n", + info->line, info->count); +#endif + if (retval) + return retval; + info->flags |= ASYNC_NORMAL_ACTIVE; + return 0; +} + +/* + * 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. + */ +static int sab82532_open(struct tty_struct *tty, struct file * filp) +{ + struct sab82532 *info = sab82532_chain; + int retval, line; + unsigned long page; + +#ifdef SERIAL_DEBUG_OPEN + printk("sab82532_open: count = %d\n", info->count); +#endif + line = MINOR(tty->device) - tty->driver.minor_start; + if ((line < 0) || (line >= NR_PORTS)) + return -ENODEV; + + while (info) { + if (info->line == line) + break; + info = info->next; + } + if (!info) { + printk("sab82532_open: can't find info for line %d\n", + line); + return -ENODEV; + } + + info->count++; + if (serial_paranoia_check(info, tty->device, "sab82532_open")) + return -ENODEV; + +#ifdef SERIAL_DEBUG_OPEN + printk("sab82532_open %s%d, count = %d\n", tty->driver.name, info->line, + info->count); +#endif + tty->driver_data = info; + info->tty = tty; + + if (!tmp_buf) { + page = get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + if (tmp_buf) + free_page(page); + else + tmp_buf = (unsigned char *) page; + } + + /* + * Start up serial port + */ + retval = startup(info); + if (retval) + return retval; + + MOD_INC_USE_COUNT; + retval = block_til_ready(tty, filp, info); + if (retval) { +#ifdef SERIAL_DEBUG_OPEN + printk("sab82532_open returning after block_til_ready with %d\n", + retval); +#endif + return retval; + } + + if ((info->count == 1) && + (info->flags & ASYNC_SPLIT_TERMIOS)) { + if (tty->driver.subtype == SERIAL_TYPE_NORMAL) + *tty->termios = info->normal_termios; + else + *tty->termios = info->callout_termios; + change_speed(info); + } + + info->session = current->session; + info->pgrp = current->pgrp; + +#ifdef SERIAL_DEBUG_OPEN + printk("sab82532_open ttys%d successful... count %d", info->line, info->count); +#endif + return 0; +} + +/* + * /proc fs routines.... + */ + +static int inline line_info(char *buf, struct tty_struct *tty) +{ + struct sab82532 *info = (struct sab82532 *)tty->driver_data; + int ret; + + ret = sprintf(buf, "%d: uart:SAB82532 ", info->line); + switch (info->type) { + case 0: + ret += sprintf(buf+ret, "V1.0 "); + break; + case 1: + ret += sprintf(buf+ret, "V2.0 "); + break; + case 2: + ret += sprintf(buf+ret, "V3.2 "); + break; + default: + ret += sprintf(buf+ret, "V?.? "); + break; + } + ret += sprintf(buf+ret, "port:%lX irq:%d", + (unsigned long)info->regs, info->irq); + + if (!info->regs) { + ret += sprintf(buf+ret, "\n"); + return ret; + } + + /* + * Figure out the current RS-232 lines + */ + + ret += sprintf(buf+ret, "\n"); + return ret; +} + +int sab82532_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int i, len = 0; + off_t begin = 0; + + len += sprintf(page, "serinfo:1.0 driver:%s\n", "$Revision: 1.4 $"); + for (i = 0; i < NR_PORTS && len < 4000; i++) { + len += line_info(page + len, sab82532_table[i]); + if (len+begin > off+count) + goto done; + if (len+begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; +done: + if (off >= len+begin) + return 0; + *start = page + (begin-off); + return ((count < begin+len-off) ? count : begin+len-off); +} + +/* + * --------------------------------------------------------------------- + * sab82532_init() and friends + * + * sab82532_init() is called at boot-time to initialize the serial driver. + * --------------------------------------------------------------------- + */ +__initfunc(static int get_sab82532(void)) +{ + struct linux_ebus *ebus; + struct linux_ebus_device *edev; + struct sab82532 *sab; + unsigned long regs, offset; + int i; + + for_all_ebusdev(edev, ebus) + if (!strcmp(edev->prom_name, "se")) + break; + if (!edev) + return -ENODEV; + + printk("%s: SAB82532 at 0x%lx IRQ %x\n", __FUNCTION__, + edev->base_address[0], edev->irqs[0]); + + regs = edev->base_address[0]; + offset = sizeof(union sab82532_async_regs); + + for (i = 0; i < 2; i++) { + sab = (struct sab82532 *)kmalloc(sizeof(struct sab82532), + GFP_KERNEL); + if (!sab) { + printk("sab82532: can't alloc sab struct\n"); + break; + } + memset(sab, 0, sizeof(struct sab82532)); + + sab->regs = (union sab82532_async_regs *)(regs + offset); + sab->irq = edev->irqs[0]; + + if (check_region((unsigned long)sab->regs, + sizeof(union sab82532_async_regs))) { + kfree(sab); + continue; + } + request_region((unsigned long)sab->regs, + sizeof(union sab82532_async_regs), + "serial(sab82532)"); + + sab->regs->w.ipc = SAB82532_IPC_IC_ACT_LOW; + + sab->next = sab82532_chain; + sab82532_chain = sab; + + offset -= sizeof(union sab82532_async_regs); + } + return 0; +} + +/* Hooks for running a serial console. con_init() calls this if the + * console is run over one of the ttya/ttyb serial ports. + * 'chip' should be zero, as for now we only have one chip on board. + * 'line' is decoded as 0=ttya, 1=ttyb. + */ +void +sab82532_cons_hook(int chip, int out, int line) +{ + prom_printf("sab82532: serial console is not implemented, yet\n"); + prom_halt(); +} + +void +sab82532_kgdb_hook(int line) +{ + prom_printf("sab82532: kgdb support is not implemented, yet\n"); + prom_halt(); +} + +__initfunc(static inline void show_serial_version(void)) +{ + char *revision = "$Revision: 1.4 $"; + char *version, *p; + + version = strchr(revision, ' '); + p = strchr(++version, ' '); + *p = '\0'; + printk("SAB82532 serial driver version %s\n", version); +} + +/* + * The serial driver boot-time initialization code! + */ +__initfunc(int sab82532_init(void)) +{ + struct sab82532 *info; + int i; + + if (!sab82532_chain) + get_sab82532(); + if (!sab82532_chain) + return -ENODEV; + + init_bh(SERIAL_BH, do_serial_bh); + + show_serial_version(); + + /* Initialize the tty_driver structure */ + memset(&serial_driver, 0, sizeof(struct tty_driver)); + serial_driver.magic = TTY_DRIVER_MAGIC; + serial_driver.driver_name = "serial"; + serial_driver.name = "ttyS"; + serial_driver.major = TTY_MAJOR; + serial_driver.minor_start = 64; + serial_driver.num = NR_PORTS; + serial_driver.type = TTY_DRIVER_TYPE_SERIAL; + serial_driver.subtype = SERIAL_TYPE_NORMAL; + serial_driver.init_termios = tty_std_termios; + serial_driver.init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + serial_driver.flags = TTY_DRIVER_REAL_RAW; + serial_driver.refcount = &sab82532_refcount; + serial_driver.table = sab82532_table; + serial_driver.termios = sab82532_termios; + serial_driver.termios_locked = sab82532_termios_locked; + + serial_driver.open = sab82532_open; + serial_driver.close = sab82532_close; + serial_driver.write = sab82532_write; + serial_driver.put_char = sab82532_put_char; + serial_driver.flush_chars = sab82532_flush_chars; + serial_driver.write_room = sab82532_write_room; + serial_driver.chars_in_buffer = sab82532_chars_in_buffer; + serial_driver.flush_buffer = sab82532_flush_buffer; + serial_driver.ioctl = sab82532_ioctl; + serial_driver.throttle = sab82532_throttle; + serial_driver.unthrottle = sab82532_unthrottle; + serial_driver.send_xchar = sab82532_send_xchar; + serial_driver.set_termios = sab82532_set_termios; + serial_driver.stop = sab82532_stop; + serial_driver.start = sab82532_start; + serial_driver.hangup = sab82532_hangup; + serial_driver.wait_until_sent = sab82532_wait_until_sent; + serial_driver.read_proc = sab82532_read_proc; + + /* + * The callout device is just like normal device except for + * major number and the subtype code. + */ + callout_driver = serial_driver; + callout_driver.name = "cua"; + callout_driver.major = TTYAUX_MAJOR; + callout_driver.subtype = SERIAL_TYPE_CALLOUT; + callout_driver.read_proc = 0; + callout_driver.proc_entry = 0; + + if (tty_register_driver(&serial_driver)) + panic("Couldn't register serial driver\n"); + if (tty_register_driver(&callout_driver)) + panic("Couldn't register callout driver\n"); + + for (info = sab82532_chain, i = 0; info; info = info->next, i++) { + info->magic = SERIAL_MAGIC; + info->line = i; + info->tty = 0; + info->count = 0; + + info->type = info->regs->r.vstr & 0x0f; + info->regs->w.pcr = ~((1 << 1) | (1 << 2) | (1 << 4)); + info->regs->w.pim = 0xff; + if (info->line == 0) { + info->pvr_dsr_bit = (1 << 0); + info->pvr_dtr_bit = (1 << 1); + } else { + info->pvr_dsr_bit = (1 << 3); + info->pvr_dtr_bit = (1 << 2); + } + info->regs->w.pvr = (1 << 1) | (1 << 2) | (1 << 4); + info->regs->rw.mode |= SAB82532_MODE_FRTS; + info->regs->rw.mode |= SAB82532_MODE_RTS; + + info->xmit_fifo_size = 32; + info->recv_fifo_size = 32; + info->custom_divisor = 16; + info->close_delay = 5*HZ/10; + info->closing_wait = 30*HZ; + info->x_char = 0; + info->event = 0; + info->count = 0; + info->blocked_open = 0; + info->tqueue.routine = do_softint; + info->tqueue.data = info; + info->tqueue_hangup.routine = do_serial_hangup; + info->tqueue_hangup.data = info; + info->callout_termios = callout_driver.init_termios; + info->normal_termios = serial_driver.init_termios; + info->open_wait = 0; + info->close_wait = 0; + info->delta_msr_wait = 0; + info->icount.cts = info->icount.dsr = + info->icount.rng = info->icount.dcd = 0; + info->icount.rx = info->icount.tx = 0; + info->icount.frame = info->icount.parity = 0; + info->icount.overrun = info->icount.brk = 0; + + if (!(info->line & 0x01)) { + if (request_irq(info->irq, sab82532_interrupt, SA_SHIRQ, + "serial(sab82532)", info)) { + printk("sab82532: can't get IRQ %x\n", + info->irq); + panic("sab82532 initialization failed"); + } + } + + printk(KERN_INFO "ttyS%02d at 0x%lx (irq = %x) is a %s\n", + info->line, (unsigned long)info->regs, info->irq, + "SAB82532"); + } + return 0; +} + +__initfunc(int sab82532_probe(unsigned long *memory_start)) +{ + int node, enode, snode; + + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(node, "pci"); + + /* + * For each PCI bus... + */ + while (node) { + enode = prom_getchild(node); + enode = prom_searchsiblings(enode, "ebus"); + + /* + * For each EBus on this PCI... + */ + while (enode) { + snode = prom_getchild(enode); + snode = prom_searchsiblings(snode, "se"); + if (snode) + goto found; + + enode = prom_getsibling(enode); + enode = prom_searchsiblings(enode, "ebus"); + } + node = prom_getsibling(node); + node = prom_searchsiblings(node, "pci"); + } + return -ENODEV; + +found: + sunserial_setinitfunc(memory_start, sab82532_init); + rs_ops.rs_cons_hook = sab82532_cons_hook; + rs_ops.rs_kgdb_hook = sab82532_kgdb_hook; + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + if (get_sab82532()) + return -ENODEV; + + return sab82532_init(); +} + +void cleanup_module(void) +{ + unsigned long flags; + int e1, e2; + int i; + + /* printk("Unloading %s: version %s\n", serial_name, serial_version); */ + save_flags(flags); + cli(); + timer_active &= ~(1 << RS_TIMER); + timer_table[RS_TIMER].fn = NULL; + timer_table[RS_TIMER].expires = 0; + remove_bh(SERIAL_BH); + if ((e1 = tty_unregister_driver(&serial_driver))) + printk("SERIAL: failed to unregister serial driver (%d)\n", + e1); + if ((e2 = tty_unregister_driver(&callout_driver))) + printk("SERIAL: failed to unregister callout driver (%d)\n", + e2); + restore_flags(flags); + + for (i = 0; i < NR_PORTS; i++) { + if (sab82532_table[i].type != PORT_UNKNOWN) + release_region(sab82532_table[i].port, 8); + } + if (tmp_buf) { + free_page((unsigned long) tmp_buf); + tmp_buf = NULL; + } +} +#endif /* MODULE */ |