// SPDX-License-Identifier: GPL-2.0 /* * Serial driver for the amiga builtin port. * * This code was created by taking serial.c version 4.30 from kernel * release 2.3.22, replacing all hardware related stuff with the * corresponding amiga hardware actions, and removing all irrelevant * code. As a consequence, it uses many of the constants and names * associated with the registers and bits of 16550 compatible UARTS - * but only to keep track of status, etc in the state variables. It * was done this was to make it easier to keep the code in line with * (non hardware specific) changes to serial.c. * * The port is registered with the tty driver as minor device 64, and * therefore other ports should only use 65 upwards. * * Richard Lucock 28/12/99 * * Copyright (C) 1991, 1992 Linus Torvalds * Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, * 1998, 1999 Theodore Ts'o * */ /* Set of debugging defines */ #undef SERIAL_DEBUG_INTR #undef SERIAL_DEBUG_OPEN #undef SERIAL_DEBUG_FLOW #undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT /* * End of serial driver configuration section. */ #include <linux/bitops.h> #include <linux/circ_buf.h> #include <linux/console.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/fcntl.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/ioport.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/ptrace.h> #include <linux/seq_file.h> #include <linux/serial.h> #include <linux/serial_reg.h> #include <linux/serial_core.h> #include <linux/sched.h> #include <linux/signal.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/timer.h> #include <linux/tty_flip.h> #include <linux/tty.h> #include <linux/types.h> #include <linux/uaccess.h> #include <asm/amigahw.h> #include <asm/amigaints.h> #include <asm/irq.h> #include <asm/setup.h> struct serial_state { struct tty_port tport; struct circ_buf xmit; struct async_icount icount; unsigned long port; int baud_base; int custom_divisor; int read_status_mask; int ignore_status_mask; int timeout; int quot; int IER; /* Interrupt Enable Register */ int MCR; /* Modem control register */ int x_char; /* xon/xoff character */ }; static struct tty_driver *serial_driver; /* number of characters left in xmit buffer before we ask for more */ #define WAKEUP_CHARS 256 #define XMIT_FIFO_SIZE 1 static unsigned char current_ctl_bits; static void change_speed(struct tty_struct *tty, struct serial_state *info, const struct ktermios *old); static void rs_wait_until_sent(struct tty_struct *tty, int timeout); static struct serial_state serial_state; /* some serial hardware definitions */ #define SDR_OVRUN (1<<15) #define SDR_RBF (1<<14) #define SDR_TBE (1<<13) #define SDR_TSRE (1<<12) #define SERPER_PARENB (1<<15) #define AC_SETCLR (1<<15) #define AC_UARTBRK (1<<11) #define SER_DTR (1<<7) #define SER_RTS (1<<6) #define SER_DCD (1<<5) #define SER_CTS (1<<4) #define SER_DSR (1<<3) static __inline__ void rtsdtr_ctrl(int bits) { ciab.pra = ((bits & (SER_RTS | SER_DTR)) ^ (SER_RTS | SER_DTR)) | (ciab.pra & ~(SER_RTS | SER_DTR)); } /* * ------------------------------------------------------------ * rs_stop() and rs_start() * * This routines are called before setting or resetting tty->flow.stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */ static void rs_stop(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; unsigned long flags; local_irq_save(flags); if (info->IER & UART_IER_THRI) { info->IER &= ~UART_IER_THRI; /* disable Tx interrupt and remove any pending interrupts */ amiga_custom.intena = IF_TBE; mb(); amiga_custom.intreq = IF_TBE; mb(); } local_irq_restore(flags); } static void rs_start(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; unsigned long flags; local_irq_save(flags); if (info->xmit.head != info->xmit.tail && info->xmit.buf && !(info->IER & UART_IER_THRI)) { info->IER |= UART_IER_THRI; amiga_custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ amiga_custom.intreq = IF_SETCLR | IF_TBE; mb(); } local_irq_restore(flags); } /* * ---------------------------------------------------------------------- * * Here start the interrupt handling routines. * * ----------------------------------------------------------------------- */ static void receive_chars(struct serial_state *info) { int status; int serdatr; unsigned char ch, flag; struct async_icount *icount; int oe = 0; icount = &info->icount; status = UART_LSR_DR; /* We obviously have a character! */ serdatr = amiga_custom.serdatr; mb(); amiga_custom.intreq = IF_RBF; mb(); if((serdatr & 0x1ff) == 0) status |= UART_LSR_BI; if(serdatr & SDR_OVRUN) status |= UART_LSR_OE; ch = serdatr & 0xff; icount->rx++; #ifdef SERIAL_DEBUG_INTR printk("DR%02x:%02x...", ch, status); #endif flag = TTY_NORMAL; /* * We don't handle parity or frame errors - but I have left * the code in, since I'm not sure that the errors can't be * detected. */ if (status & (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE | UART_LSR_OE)) { /* * For statistics only */ if (status & UART_LSR_BI) { status &= ~(UART_LSR_FE | UART_LSR_PE); icount->brk++; } else if (status & UART_LSR_PE) icount->parity++; else if (status & UART_LSR_FE) icount->frame++; if (status & UART_LSR_OE) icount->overrun++; /* * Now check to see if character should be * ignored, and mask off conditions which * should be ignored. */ if (status & info->ignore_status_mask) goto out; status &= info->read_status_mask; if (status & (UART_LSR_BI)) { #ifdef SERIAL_DEBUG_INTR printk("handling break...."); #endif flag = TTY_BREAK; if (info->tport.flags & ASYNC_SAK) do_SAK(info->tport.tty); } else if (status & UART_LSR_PE) flag = TTY_PARITY; else if (status & UART_LSR_FE) flag = TTY_FRAME; if (status & UART_LSR_OE) { /* * Overrun is special, since it's * reported immediately, and doesn't * affect the current character */ oe = 1; } } tty_insert_flip_char(&info->tport, ch, flag); if (oe == 1) tty_insert_flip_char(&info->tport, 0, TTY_OVERRUN); tty_flip_buffer_push(&info->tport); out: return; } static void transmit_chars(struct serial_state *info) { amiga_custom.intreq = IF_TBE; mb(); if (info->x_char) { amiga_custom.serdat = info->x_char | 0x100; mb(); info->icount.tx++; info->x_char = 0; return; } if (info->xmit.head == info->xmit.tail || info->tport.tty->flow.stopped || info->tport.tty->hw_stopped) { info->IER &= ~UART_IER_THRI; amiga_custom.intena = IF_TBE; mb(); return; } amiga_custom.serdat = info->xmit.buf[info->xmit.tail++] | 0x100; mb(); info->xmit.tail = info->xmit.tail & (UART_XMIT_SIZE - 1); info->icount.tx++; if (CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) < WAKEUP_CHARS) tty_wakeup(info->tport.tty); #ifdef SERIAL_DEBUG_INTR printk("THRE..."); #endif if (info->xmit.head == info->xmit.tail) { amiga_custom.intena = IF_TBE; mb(); info->IER &= ~UART_IER_THRI; } } static void check_modem_status(struct serial_state *info) { struct tty_port *port = &info->tport; unsigned char status = ciab.pra & (SER_DCD | SER_CTS | SER_DSR); unsigned char dstatus; struct async_icount *icount; /* Determine bits that have changed */ dstatus = status ^ current_ctl_bits; current_ctl_bits = status; if (dstatus) { icount = &info->icount; /* update input line counters */ if (dstatus & SER_DSR) icount->dsr++; if (dstatus & SER_DCD) { icount->dcd++; } if (dstatus & SER_CTS) icount->cts++; wake_up_interruptible(&port->delta_msr_wait); } if (tty_port_check_carrier(port) && (dstatus & SER_DCD)) { #if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR)) printk("ttyS%d CD now %s...", info->line, (!(status & SER_DCD)) ? "on" : "off"); #endif if (!(status & SER_DCD)) wake_up_interruptible(&port->open_wait); else { #ifdef SERIAL_DEBUG_OPEN printk("doing serial hangup..."); #endif if (port->tty) tty_hangup(port->tty); } } if (tty_port_cts_enabled(port)) { if (port->tty->hw_stopped) { if (!(status & SER_CTS)) { #if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) printk("CTS tx start..."); #endif port->tty->hw_stopped = false; info->IER |= UART_IER_THRI; amiga_custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ amiga_custom.intreq = IF_SETCLR | IF_TBE; mb(); tty_wakeup(port->tty); return; } } else { if ((status & SER_CTS)) { #if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) printk("CTS tx stop..."); #endif port->tty->hw_stopped = true; info->IER &= ~UART_IER_THRI; /* disable Tx interrupt and remove any pending interrupts */ amiga_custom.intena = IF_TBE; mb(); amiga_custom.intreq = IF_TBE; mb(); } } } } static irqreturn_t ser_vbl_int( int irq, void *data) { /* vbl is just a periodic interrupt we tie into to update modem status */ struct serial_state *info = data; /* * TBD - is it better to unregister from this interrupt or to * ignore it if MSI is clear ? */ if(info->IER & UART_IER_MSI) check_modem_status(info); return IRQ_HANDLED; } static irqreturn_t ser_rx_int(int irq, void *dev_id) { struct serial_state *info = dev_id; #ifdef SERIAL_DEBUG_INTR printk("ser_rx_int..."); #endif if (!info->tport.tty) return IRQ_NONE; receive_chars(info); #ifdef SERIAL_DEBUG_INTR printk("end.\n"); #endif return IRQ_HANDLED; } static irqreturn_t ser_tx_int(int irq, void *dev_id) { struct serial_state *info = dev_id; if (amiga_custom.serdatr & SDR_TBE) { #ifdef SERIAL_DEBUG_INTR printk("ser_tx_int..."); #endif if (!info->tport.tty) return IRQ_NONE; transmit_chars(info); #ifdef SERIAL_DEBUG_INTR printk("end.\n"); #endif } return IRQ_HANDLED; } /* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- */ /* * --------------------------------------------------------------- * Low level utility subroutines for the serial driver: routines to * figure out the appropriate timeout for an interrupt chain, routines * to initialize and startup a serial port, and routines to shutdown a * serial port. Useful stuff like that. * --------------------------------------------------------------- */ static int startup(struct tty_struct *tty, struct serial_state *info) { struct tty_port *port = &info->tport; unsigned long flags; int retval=0; unsigned long page; page = get_zeroed_page(GFP_KERNEL); if (!page) return -ENOMEM; local_irq_save(flags); if (tty_port_initialized(port)) { 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 ttys%d ...", info->line); #endif /* Clear anything in the input buffer */ amiga_custom.intreq = IF_RBF; mb(); retval = request_irq(IRQ_AMIGA_VERTB, ser_vbl_int, 0, "serial status", info); if (retval) { if (capable(CAP_SYS_ADMIN)) { set_bit(TTY_IO_ERROR, &tty->flags); retval = 0; } goto errout; } /* enable both Rx and Tx interrupts */ amiga_custom.intena = IF_SETCLR | IF_RBF | IF_TBE; mb(); info->IER = UART_IER_MSI; /* remember current state of the DCD and CTS bits */ current_ctl_bits = ciab.pra & (SER_DCD | SER_CTS | SER_DSR); info->MCR = 0; if (C_BAUD(tty)) info->MCR = SER_DTR | SER_RTS; rtsdtr_ctrl(info->MCR); clear_bit(TTY_IO_ERROR, &tty->flags); info->xmit.head = info->xmit.tail = 0; /* * and set the speed of the serial port */ change_speed(tty, info, NULL); tty_port_set_initialized(port, true); local_irq_restore(flags); return 0; errout: local_irq_restore(flags); return retval; } /* * 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 tty_struct *tty, struct serial_state *info) { unsigned long flags; if (!tty_port_initialized(&info->tport)) return; #ifdef SERIAL_DEBUG_OPEN printk("Shutting down serial port %d ....\n", info->line); #endif local_irq_save(flags); /* Disable interrupts */ /* * clear delta_msr_wait queue to avoid mem leaks: we may free the irq * here so the queue might never be waken up */ wake_up_interruptible(&info->tport.delta_msr_wait); /* * Free the IRQ, if necessary */ free_irq(IRQ_AMIGA_VERTB, info); free_page((unsigned long)info->xmit.buf); info->xmit.buf = NULL; info->IER = 0; amiga_custom.intena = IF_RBF | IF_TBE; mb(); /* disable break condition */ amiga_custom.adkcon = AC_UARTBRK; mb(); if (C_HUPCL(tty)) info->MCR &= ~(SER_DTR|SER_RTS); rtsdtr_ctrl(info->MCR); set_bit(TTY_IO_ERROR, &tty->flags); tty_port_set_initialized(&info->tport, false); local_irq_restore(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 tty_struct *tty, struct serial_state *info, const struct ktermios *old_termios) { struct tty_port *port = &info->tport; int quot = 0, baud_base, baud; unsigned cflag, cval = 0; int bits; unsigned long flags; cflag = tty->termios.c_cflag; /* Byte size is always 8 bits plus parity bit if requested */ cval = 3; bits = 10; if (cflag & CSTOPB) { cval |= 0x04; bits++; } if (cflag & PARENB) { cval |= UART_LCR_PARITY; bits++; } if (!(cflag & PARODD)) cval |= UART_LCR_EPAR; if (cflag & CMSPAR) cval |= UART_LCR_SPAR; /* Determine divisor based on baud rate */ baud = tty_get_baud_rate(tty); if (!baud) baud = 9600; /* B0 transition handled in rs_set_termios */ baud_base = info->baud_base; if (baud == 38400 && (port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) quot = info->custom_divisor; else { if (baud == 134) /* Special case since 134 is really 134.5 */ quot = (2*baud_base / 269); else if (baud) quot = baud_base / baud; } /* If the quotient is zero refuse the change */ if (!quot && old_termios) { /* FIXME: Will need updating for new tty in the end */ tty->termios.c_cflag &= ~CBAUD; tty->termios.c_cflag |= (old_termios->c_cflag & CBAUD); baud = tty_get_baud_rate(tty); if (!baud) baud = 9600; if (baud == 38400 && (port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) quot = info->custom_divisor; else { if (baud == 134) /* Special case since 134 is really 134.5 */ quot = (2*baud_base / 269); else if (baud) quot = baud_base / baud; } } /* As a last resort, if the quotient is zero, default to 9600 bps */ if (!quot) quot = baud_base / 9600; info->quot = quot; info->timeout = (XMIT_FIFO_SIZE*HZ*bits*quot) / baud_base; info->timeout += HZ/50; /* Add .02 seconds of slop */ /* CTS flow control flag and modem status interrupts */ info->IER &= ~UART_IER_MSI; if (port->flags & ASYNC_HARDPPS_CD) info->IER |= UART_IER_MSI; tty_port_set_cts_flow(port, cflag & CRTSCTS); if (cflag & CRTSCTS) info->IER |= UART_IER_MSI; tty_port_set_check_carrier(port, ~cflag & CLOCAL); if (~cflag & CLOCAL) info->IER |= UART_IER_MSI; /* TBD: * Does clearing IER_MSI imply that we should disable the VBL interrupt ? */ /* * Set up parity check flag */ info->read_status_mask = UART_LSR_OE | UART_LSR_DR; if (I_INPCK(tty)) info->read_status_mask |= UART_LSR_FE | UART_LSR_PE; if (I_BRKINT(tty) || I_PARMRK(tty)) info->read_status_mask |= UART_LSR_BI; /* * Characters to ignore */ info->ignore_status_mask = 0; if (I_IGNPAR(tty)) info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; if (I_IGNBRK(tty)) { info->ignore_status_mask |= UART_LSR_BI; /* * If we're ignore parity and break indicators, ignore * overruns too. (For real raw support). */ if (I_IGNPAR(tty)) info->ignore_status_mask |= UART_LSR_OE; } /* * !!! ignore all characters if CREAD is not set */ if ((cflag & CREAD) == 0) info->ignore_status_mask |= UART_LSR_DR; local_irq_save(flags); { short serper; /* Set up the baud rate */ serper = quot - 1; /* Enable or disable parity bit */ if(cval & UART_LCR_PARITY) serper |= (SERPER_PARENB); amiga_custom.serper = serper; mb(); } local_irq_restore(flags); } static int rs_put_char(struct tty_struct *tty, u8 ch) { struct serial_state *info; unsigned long flags; info = tty->driver_data; if (!info->xmit.buf) return 0; local_irq_save(flags); if (CIRC_SPACE(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) == 0) { local_irq_restore(flags); return 0; } info->xmit.buf[info->xmit.head++] = ch; info->xmit.head &= UART_XMIT_SIZE - 1; local_irq_restore(flags); return 1; } static void rs_flush_chars(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; unsigned long flags; if (info->xmit.head == info->xmit.tail || tty->flow.stopped || tty->hw_stopped || !info->xmit.buf) return; local_irq_save(flags); info->IER |= UART_IER_THRI; amiga_custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ amiga_custom.intreq = IF_SETCLR | IF_TBE; mb(); local_irq_restore(flags); } static ssize_t rs_write(struct tty_struct * tty, const u8 *buf, size_t count) { int c, ret = 0; struct serial_state *info = tty->driver_data; unsigned long flags; if (!info->xmit.buf) return 0; local_irq_save(flags); while (1) { c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE); if (count < c) c = count; if (c <= 0) { break; } memcpy(info->xmit.buf + info->xmit.head, buf, c); info->xmit.head = (info->xmit.head + c) & (UART_XMIT_SIZE - 1); buf += c; count -= c; ret += c; } local_irq_restore(flags); if (info->xmit.head != info->xmit.tail && !tty->flow.stopped && !tty->hw_stopped && !(info->IER & UART_IER_THRI)) { info->IER |= UART_IER_THRI; local_irq_disable(); amiga_custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ amiga_custom.intreq = IF_SETCLR | IF_TBE; mb(); local_irq_restore(flags); } return ret; } static unsigned int rs_write_room(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; return CIRC_SPACE(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE); } static unsigned int rs_chars_in_buffer(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; return CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE); } static void rs_flush_buffer(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; unsigned long flags; local_irq_save(flags); info->xmit.head = info->xmit.tail = 0; local_irq_restore(flags); tty_wakeup(tty); } /* * This function is used to send a high-priority XON/XOFF character to * the device */ static void rs_send_xchar(struct tty_struct *tty, char ch) { struct serial_state *info = tty->driver_data; unsigned long flags; info->x_char = ch; if (ch) { /* Make sure transmit interrupts are on */ /* Check this ! */ local_irq_save(flags); if(!(amiga_custom.intenar & IF_TBE)) { amiga_custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ amiga_custom.intreq = IF_SETCLR | IF_TBE; mb(); } local_irq_restore(flags); info->IER |= UART_IER_THRI; } } /* * ------------------------------------------------------------ * rs_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------ */ static void rs_throttle(struct tty_struct * tty) { struct serial_state *info = tty->driver_data; unsigned long flags; #ifdef SERIAL_DEBUG_THROTTLE printk("throttle %s ....\n", tty_name(tty)); #endif if (I_IXOFF(tty)) rs_send_xchar(tty, STOP_CHAR(tty)); if (C_CRTSCTS(tty)) info->MCR &= ~SER_RTS; local_irq_save(flags); rtsdtr_ctrl(info->MCR); local_irq_restore(flags); } static void rs_unthrottle(struct tty_struct * tty) { struct serial_state *info = tty->driver_data; unsigned long flags; #ifdef SERIAL_DEBUG_THROTTLE printk("unthrottle %s ....\n", tty_name(tty)); #endif if (I_IXOFF(tty)) { if (info->x_char) info->x_char = 0; else rs_send_xchar(tty, START_CHAR(tty)); } if (C_CRTSCTS(tty)) info->MCR |= SER_RTS; local_irq_save(flags); rtsdtr_ctrl(info->MCR); local_irq_restore(flags); } /* * ------------------------------------------------------------ * rs_ioctl() and friends * ------------------------------------------------------------ */ static int get_serial_info(struct tty_struct *tty, struct serial_struct *ss) { struct serial_state *state = tty->driver_data; unsigned int close_delay, closing_wait; tty_lock(tty); close_delay = jiffies_to_msecs(state->tport.close_delay) / 10; closing_wait = state->tport.closing_wait; if (closing_wait != ASYNC_CLOSING_WAIT_NONE) closing_wait = jiffies_to_msecs(closing_wait) / 10; ss->line = tty->index; ss->port = state->port; ss->flags = state->tport.flags; ss->xmit_fifo_size = XMIT_FIFO_SIZE; ss->baud_base = state->baud_base; ss->close_delay = close_delay; ss->closing_wait = closing_wait; ss->custom_divisor = state->custom_divisor; tty_unlock(tty); return 0; } static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss) { struct serial_state *state = tty->driver_data; struct tty_port *port = &state->tport; bool change_spd; int retval = 0; unsigned int close_delay, closing_wait; tty_lock(tty); change_spd = ((ss->flags ^ port->flags) & ASYNC_SPD_MASK) || ss->custom_divisor != state->custom_divisor; if (ss->irq || ss->port != state->port || ss->xmit_fifo_size != XMIT_FIFO_SIZE) { tty_unlock(tty); return -EINVAL; } close_delay = msecs_to_jiffies(ss->close_delay * 10); closing_wait = ss->closing_wait; if (closing_wait != ASYNC_CLOSING_WAIT_NONE) closing_wait = msecs_to_jiffies(closing_wait * 10); if (!capable(CAP_SYS_ADMIN)) { if ((ss->baud_base != state->baud_base) || (close_delay != port->close_delay) || (closing_wait != port->closing_wait) || ((ss->flags & ~ASYNC_USR_MASK) != (port->flags & ~ASYNC_USR_MASK))) { tty_unlock(tty); return -EPERM; } port->flags = ((port->flags & ~ASYNC_USR_MASK) | (ss->flags & ASYNC_USR_MASK)); state->custom_divisor = ss->custom_divisor; goto check_and_exit; } if (ss->baud_base < 9600) { tty_unlock(tty); return -EINVAL; } /* * OK, past this point, all the error checking has been done. * At this point, we start making changes..... */ state->baud_base = ss->baud_base; port->flags = ((port->flags & ~ASYNC_FLAGS) | (ss->flags & ASYNC_FLAGS)); state->custom_divisor = ss->custom_divisor; port->close_delay = close_delay; port->closing_wait = closing_wait; check_and_exit: if (tty_port_initialized(port)) { if (change_spd) { /* warn about deprecation unless clearing */ if (ss->flags & ASYNC_SPD_MASK) dev_warn_ratelimited(tty->dev, "use of SPD flags is deprecated\n"); change_speed(tty, state, NULL); } } else retval = startup(tty, state); tty_unlock(tty); return retval; } /* * get_lsr_info - get line status register info * * Purpose: Let user call ioctl() to get info when the UART physically * is emptied. On bus types like RS485, the transmitter must * release the bus after transmitting. This must be done when * the transmit shift register is empty, not be done when the * transmit holding register is empty. This functionality * allows an RS485 driver to be written in user space. */ static int get_lsr_info(struct serial_state *info, unsigned int __user *value) { unsigned char status; unsigned int result; unsigned long flags; local_irq_save(flags); status = amiga_custom.serdatr; mb(); local_irq_restore(flags); result = ((status & SDR_TSRE) ? TIOCSER_TEMT : 0); if (copy_to_user(value, &result, sizeof(int))) return -EFAULT; return 0; } static int rs_tiocmget(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; unsigned char control, status; unsigned long flags; if (tty_io_error(tty)) return -EIO; control = info->MCR; local_irq_save(flags); status = ciab.pra; local_irq_restore(flags); return ((control & SER_RTS) ? TIOCM_RTS : 0) | ((control & SER_DTR) ? TIOCM_DTR : 0) | (!(status & SER_DCD) ? TIOCM_CAR : 0) | (!(status & SER_DSR) ? TIOCM_DSR : 0) | (!(status & SER_CTS) ? TIOCM_CTS : 0); } static int rs_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct serial_state *info = tty->driver_data; unsigned long flags; if (tty_io_error(tty)) return -EIO; local_irq_save(flags); if (set & TIOCM_RTS) info->MCR |= SER_RTS; if (set & TIOCM_DTR) info->MCR |= SER_DTR; if (clear & TIOCM_RTS) info->MCR &= ~SER_RTS; if (clear & TIOCM_DTR) info->MCR &= ~SER_DTR; rtsdtr_ctrl(info->MCR); local_irq_restore(flags); return 0; } /* * rs_break() --- routine which turns the break handling on or off */ static int rs_break(struct tty_struct *tty, int break_state) { unsigned long flags; local_irq_save(flags); if (break_state == -1) amiga_custom.adkcon = AC_SETCLR | AC_UARTBRK; else amiga_custom.adkcon = AC_UARTBRK; mb(); local_irq_restore(flags); return 0; } /* * 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. */ static int rs_get_icount(struct tty_struct *tty, struct serial_icounter_struct *icount) { struct serial_state *info = tty->driver_data; struct async_icount cnow; unsigned long flags; local_irq_save(flags); cnow = info->icount; local_irq_restore(flags); icount->cts = cnow.cts; icount->dsr = cnow.dsr; icount->rng = cnow.rng; icount->dcd = cnow.dcd; icount->rx = cnow.rx; icount->tx = cnow.tx; icount->frame = cnow.frame; icount->overrun = cnow.overrun; icount->parity = cnow.parity; icount->brk = cnow.brk; icount->buf_overrun = cnow.buf_overrun; return 0; } static int rs_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { struct serial_state *info = tty->driver_data; struct async_icount cprev, cnow; /* kernel counter temps */ void __user *argp = (void __user *)arg; unsigned long flags; DEFINE_WAIT(wait); int ret; if ((cmd != TIOCSERCONFIG) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { if (tty_io_error(tty)) return -EIO; } switch (cmd) { case TIOCSERCONFIG: return 0; case TIOCSERGETLSR: /* Get line status register */ return get_lsr_info(info, argp); /* * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change * - mask passed in arg for lines of interest * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) * Caller should use TIOCGICOUNT to see which one it was */ case TIOCMIWAIT: local_irq_save(flags); /* note the counters on entry */ cprev = info->icount; local_irq_restore(flags); while (1) { prepare_to_wait(&info->tport.delta_msr_wait, &wait, TASK_INTERRUPTIBLE); local_irq_save(flags); cnow = info->icount; /* atomic copy */ local_irq_restore(flags); if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) { ret = -EIO; /* no change => error */ break; } if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) { ret = 0; break; } schedule(); /* see if a signal did it */ if (signal_pending(current)) { ret = -ERESTARTSYS; break; } cprev = cnow; } finish_wait(&info->tport.delta_msr_wait, &wait); return ret; default: return -ENOIOCTLCMD; } return 0; } static void rs_set_termios(struct tty_struct *tty, const struct ktermios *old_termios) { struct serial_state *info = tty->driver_data; unsigned long flags; unsigned int cflag = tty->termios.c_cflag; change_speed(tty, info, old_termios); /* Handle transition to B0 status */ if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) { info->MCR &= ~(SER_DTR|SER_RTS); local_irq_save(flags); rtsdtr_ctrl(info->MCR); local_irq_restore(flags); } /* Handle transition away from B0 status */ if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { info->MCR |= SER_DTR; if (!C_CRTSCTS(tty) || !tty_throttled(tty)) info->MCR |= SER_RTS; local_irq_save(flags); rtsdtr_ctrl(info->MCR); local_irq_restore(flags); } /* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !C_CRTSCTS(tty)) { tty->hw_stopped = false; rs_start(tty); } #if 0 /* * No need to wake up processes in open wait, since they * sample the CLOCAL flag once, and don't recheck it. * XXX It's not clear whether the current behavior is correct * or not. Hence, this may change..... */ if (!(old_termios->c_cflag & CLOCAL) && C_CLOCAL(tty)) wake_up_interruptible(&info->open_wait); #endif } /* * ------------------------------------------------------------ * rs_close() * * This routine is called when the serial port gets closed. First, we * wait for the last remaining data to be sent. Then, we unlink its * async structure from the interrupt chain if necessary, and we free * that IRQ if nothing is left in the chain. * ------------------------------------------------------------ */ static void rs_close(struct tty_struct *tty, struct file * filp) { struct serial_state *state = tty->driver_data; struct tty_port *port = &state->tport; if (tty_port_close_start(port, tty, filp) == 0) return; /* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register. */ state->read_status_mask &= ~UART_LSR_DR; if (tty_port_initialized(port)) { /* disable receive interrupts */ amiga_custom.intena = IF_RBF; mb(); /* clear any pending receive interrupt */ amiga_custom.intreq = IF_RBF; mb(); /* * Before we drop DTR, make sure the UART transmitter * has completely drained; this is especially * important if there is a transmit FIFO! */ rs_wait_until_sent(tty, state->timeout); } shutdown(tty, state); rs_flush_buffer(tty); tty_ldisc_flush(tty); port->tty = NULL; tty_port_close_end(port, tty); } /* * rs_wait_until_sent() --- wait until the transmitter is empty */ static void rs_wait_until_sent(struct tty_struct *tty, int timeout) { struct serial_state *info = tty->driver_data; unsigned long orig_jiffies, char_time; int lsr; orig_jiffies = jiffies; /* * Set the check interval to be 1/5 of the estimated time to * send a single character, and make it at least 1. The check * interval should also be less than the timeout. * * Note: we have to use pretty tight timings here to satisfy * the NIST-PCTS. */ char_time = (info->timeout - HZ/50) / XMIT_FIFO_SIZE; char_time = char_time / 5; if (char_time == 0) char_time = 1; if (timeout) char_time = min_t(unsigned long, char_time, timeout); /* * If the transmitter hasn't cleared in twice the approximate * amount of time to send the entire FIFO, it probably won't * ever clear. This assumes the UART isn't doing flow * control, which is currently the case. Hence, if it ever * takes longer than info->timeout, this is probably due to a * UART bug of some kind. So, we clamp the timeout parameter at * 2*info->timeout. */ if (!timeout || timeout > 2*info->timeout) timeout = 2*info->timeout; #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("In rs_wait_until_sent(%d) check=%lu...", timeout, char_time); printk("jiff=%lu...", jiffies); #endif while(!((lsr = amiga_custom.serdatr) & SDR_TSRE)) { #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("serdatr = %d (jiff=%lu)...", lsr, jiffies); #endif msleep_interruptible(jiffies_to_msecs(char_time)); if (signal_pending(current)) break; if (timeout && time_after(jiffies, orig_jiffies + timeout)) break; } __set_current_state(TASK_RUNNING); #ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies); #endif } /* * rs_hangup() --- called by tty_hangup() when a hangup is signaled. */ static void rs_hangup(struct tty_struct *tty) { struct serial_state *info = tty->driver_data; rs_flush_buffer(tty); shutdown(tty, info); info->tport.count = 0; tty_port_set_active(&info->tport, false); info->tport.tty = NULL; wake_up_interruptible(&info->tport.open_wait); } /* * 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 rs_open(struct tty_struct *tty, struct file * filp) { struct tty_port *port = tty->port; struct serial_state *info = container_of(port, struct serial_state, tport); int retval; port->count++; port->tty = tty; tty->driver_data = info; retval = startup(tty, info); if (retval) { return retval; } return tty_port_block_til_ready(port, tty, filp); } /* * /proc fs routines.... */ static inline void line_info(struct seq_file *m, int line, struct serial_state *state) { char stat_buf[30], control, status; unsigned long flags; seq_printf(m, "%d: uart:amiga_builtin", line); local_irq_save(flags); status = ciab.pra; control = tty_port_initialized(&state->tport) ? state->MCR : status; local_irq_restore(flags); stat_buf[0] = 0; stat_buf[1] = 0; if(!(control & SER_RTS)) strcat(stat_buf, "|RTS"); if(!(status & SER_CTS)) strcat(stat_buf, "|CTS"); if(!(control & SER_DTR)) strcat(stat_buf, "|DTR"); if(!(status & SER_DSR)) strcat(stat_buf, "|DSR"); if(!(status & SER_DCD)) strcat(stat_buf, "|CD"); if (state->quot) seq_printf(m, " baud:%d", state->baud_base / state->quot); seq_printf(m, " tx:%d rx:%d", state->icount.tx, state->icount.rx); if (state->icount.frame) seq_printf(m, " fe:%d", state->icount.frame); if (state->icount.parity) seq_printf(m, " pe:%d", state->icount.parity); if (state->icount.brk) seq_printf(m, " brk:%d", state->icount.brk); if (state->icount.overrun) seq_printf(m, " oe:%d", state->icount.overrun); /* * Last thing is the RS-232 status lines */ seq_printf(m, " %s\n", stat_buf+1); } static int rs_proc_show(struct seq_file *m, void *v) { seq_printf(m, "serinfo:1.0 driver:4.30\n"); line_info(m, 0, &serial_state); return 0; } /* * --------------------------------------------------------------------- * rs_init() and friends * * rs_init() is called at boot-time to initialize the serial driver. * --------------------------------------------------------------------- */ static const struct tty_operations serial_ops = { .open = rs_open, .close = rs_close, .write = rs_write, .put_char = rs_put_char, .flush_chars = rs_flush_chars, .write_room = rs_write_room, .chars_in_buffer = rs_chars_in_buffer, .flush_buffer = rs_flush_buffer, .ioctl = rs_ioctl, .throttle = rs_throttle, .unthrottle = rs_unthrottle, .set_termios = rs_set_termios, .stop = rs_stop, .start = rs_start, .hangup = rs_hangup, .break_ctl = rs_break, .send_xchar = rs_send_xchar, .wait_until_sent = rs_wait_until_sent, .tiocmget = rs_tiocmget, .tiocmset = rs_tiocmset, .get_icount = rs_get_icount, .set_serial = set_serial_info, .get_serial = get_serial_info, .proc_show = rs_proc_show, }; static bool amiga_carrier_raised(struct tty_port *port) { return !(ciab.pra & SER_DCD); } static void amiga_dtr_rts(struct tty_port *port, bool active) { struct serial_state *info = container_of(port, struct serial_state, tport); unsigned long flags; if (active) info->MCR |= SER_DTR|SER_RTS; else info->MCR &= ~(SER_DTR|SER_RTS); local_irq_save(flags); rtsdtr_ctrl(info->MCR); local_irq_restore(flags); } static const struct tty_port_operations amiga_port_ops = { .carrier_raised = amiga_carrier_raised, .dtr_rts = amiga_dtr_rts, }; /* * The serial driver boot-time initialization code! */ static int __init amiga_serial_probe(struct platform_device *pdev) { struct serial_state *state = &serial_state; struct tty_driver *driver; unsigned long flags; int error; driver = tty_alloc_driver(1, TTY_DRIVER_REAL_RAW); if (IS_ERR(driver)) return PTR_ERR(driver); /* Initialize the tty_driver structure */ driver->driver_name = "amiserial"; driver->name = "ttyS"; driver->major = TTY_MAJOR; driver->minor_start = 64; driver->type = TTY_DRIVER_TYPE_SERIAL; driver->subtype = SERIAL_TYPE_NORMAL; driver->init_termios = tty_std_termios; driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; tty_set_operations(driver, &serial_ops); memset(state, 0, sizeof(*state)); state->port = (int)&amiga_custom.serdatr; /* Just to give it a value */ tty_port_init(&state->tport); state->tport.ops = &amiga_port_ops; tty_port_link_device(&state->tport, driver, 0); error = tty_register_driver(driver); if (error) goto fail_tty_driver_kref_put; printk(KERN_INFO "ttyS0 is the amiga builtin serial port\n"); /* Hardware set up */ state->baud_base = amiga_colorclock; /* set ISRs, and then disable the rx interrupts */ error = request_irq(IRQ_AMIGA_TBE, ser_tx_int, 0, "serial TX", state); if (error) goto fail_unregister; error = request_irq(IRQ_AMIGA_RBF, ser_rx_int, 0, "serial RX", state); if (error) goto fail_free_irq; local_irq_save(flags); /* turn off Rx and Tx interrupts */ amiga_custom.intena = IF_RBF | IF_TBE; mb(); /* clear any pending interrupt */ amiga_custom.intreq = IF_RBF | IF_TBE; mb(); local_irq_restore(flags); /* * set the appropriate directions for the modem control flags, * and clear RTS and DTR */ ciab.ddra |= (SER_DTR | SER_RTS); /* outputs */ ciab.ddra &= ~(SER_DCD | SER_CTS | SER_DSR); /* inputs */ platform_set_drvdata(pdev, state); serial_driver = driver; return 0; fail_free_irq: free_irq(IRQ_AMIGA_TBE, state); fail_unregister: tty_unregister_driver(driver); fail_tty_driver_kref_put: tty_port_destroy(&state->tport); tty_driver_kref_put(driver); return error; } static int __exit amiga_serial_remove(struct platform_device *pdev) { struct serial_state *state = platform_get_drvdata(pdev); tty_unregister_driver(serial_driver); tty_driver_kref_put(serial_driver); tty_port_destroy(&state->tport); free_irq(IRQ_AMIGA_TBE, state); free_irq(IRQ_AMIGA_RBF, state); return 0; } static struct platform_driver amiga_serial_driver = { .remove = __exit_p(amiga_serial_remove), .driver = { .name = "amiga-serial", }, }; module_platform_driver_probe(amiga_serial_driver, amiga_serial_probe); #if defined(CONFIG_SERIAL_CONSOLE) && !defined(MODULE) /* * ------------------------------------------------------------ * Serial console driver * ------------------------------------------------------------ */ static void amiga_serial_putc(char c) { amiga_custom.serdat = (unsigned char)c | 0x100; while (!(amiga_custom.serdatr & 0x2000)) barrier(); } /* * Print a string to the serial port trying not to disturb * any possible real use of the port... * * The console must be locked when we get here. */ static void serial_console_write(struct console *co, const char *s, unsigned count) { unsigned short intena = amiga_custom.intenar; amiga_custom.intena = IF_TBE; while (count--) { if (*s == '\n') amiga_serial_putc('\r'); amiga_serial_putc(*s++); } amiga_custom.intena = IF_SETCLR | (intena & IF_TBE); } static struct tty_driver *serial_console_device(struct console *c, int *index) { *index = 0; return serial_driver; } static struct console sercons = { .name = "ttyS", .write = serial_console_write, .device = serial_console_device, .flags = CON_PRINTBUFFER, .index = -1, }; /* * Register console. */ static int __init amiserial_console_init(void) { if (!MACH_IS_AMIGA) return -ENODEV; register_console(&sercons); return 0; } console_initcall(amiserial_console_init); #endif /* CONFIG_SERIAL_CONSOLE && !MODULE */ MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:amiga-serial");