diff options
Diffstat (limited to 'drivers/serial/ns921x-serial.c')
-rw-r--r-- | drivers/serial/ns921x-serial.c | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/drivers/serial/ns921x-serial.c b/drivers/serial/ns921x-serial.c new file mode 100644 index 000000000000..7aa8f7f2b103 --- /dev/null +++ b/drivers/serial/ns921x-serial.c @@ -0,0 +1,1401 @@ +/* + * drivers/serial/ns921x-serial.c + * + * Copyright (C) 2007,2008 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#if defined(CONFIG_SERIAL_NS921X_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial_core.h> +#include <linux/serial.h> + +#include <mach/ns921x-serial.h> +#include <mach/gpio.h> + +#define HUB_IFS 0x0000 +#define HUB_IFS_RXFSRIP (1 << 25) +#define HUB_IFS_TXFSRIP (1 << 19) +#define HUB_IFS_MODIP (1 << 18) +#define HUB_IFS_RXFE (1 << 13) +#define HUB_IFS_TXFF (1 << 11) +#define HUB_IFS_TXFE (1 << 10) + +#define HUB_DMARXCTRL 0x0004 +#define HUB_DMARXCTRL_DIRECT (1 << 28) + +#define HUB_RXIC 0x000c +#define HUB_RXIC_RXTHRS (15 << 28) +#define HUB_RXIC_RXFSRIE (1 << 25) + +#define HUB_DMRXSF 0x0010 +#define HUB_DMRXSF_BYTE (7 << 9) +#define HUB_DMRXSF_FULL (1 << 7) + +#define HUB_DMRXDF 0x0014 + +#define HUB_DMATXCTRL 0x0018 +#define HUB_DMATXCTRL_DIRECT (1 << 28) + +#define HUB_TXIC 0x0020 +#define HUB_TXIC_TXTHRS (15 << 28) +#define HUB_TXIC_TXFUFIE (1 << 26) +#define HUB_TXIC_TXFSRIE (1 << 25) + +#define HUB_DMTXDF 0x0028 + +#define UART_WC 0x1000 +#define UART_WC_RXEN (1 << 30) +#define UART_WC_TXEN (1 << 29) +#define UART_WC_RTSEN (1 << 19) +#define UART_WC_RXFLUSH (1 << 17) +#define UART_WC_TXFLUSH (1 << 16) +#define UART_WC_TXFLOW_CHAR (1 << 11) +#define UART_WC_TXFLOW_SOFT (1 << 10) +#define UART_WC_TXFLOW_RI (1 << 9) +#define UART_WC_TXFLOW_DSR (1 << 8) +#define UART_WC_TXFLOW_DCD (1 << 7) +#define UART_WC_TXFLOW_CTS (1 << 6) +#define UART_WC_TXFLOW (UART_WC_TXFLOW_RI | UART_WC_TXFLOW_DSR | \ + UART_WC_TXFLOW_DCD | UART_WC_TXFLOW_CTS) + +#define UART_IE 0x1004 +#define UART_IE_OFLOW (1 << 19) +#define UART_IE_PARITY (1 << 18) +#define UART_IE_FRAME (1 << 17) +#define UART_IE_BREAK (1 << 16) +#define UART_IE_DSR (1 << 7) +#define UART_IE_DCD (1 << 6) +#define UART_IE_CTS (1 << 5) +#define UART_IE_RI (1 << 4) +#define UART_IE_TBC (1 << 3) +#define UART_IE_RBC (1 << 2) +#define UART_IE_TXIDLE (1 << 1) + +#define UART_IS 0x1008 +#define UART_IS_OFLOW (1 << 19) +#define UART_IS_PARITY (1 << 18) +#define UART_IS_FRAME (1 << 17) +#define UART_IS_BREAK (1 << 16) +#define UART_IS_DSR (1 << 7) +#define UART_IS_DCD (1 << 6) +#define UART_IS_CTS (1 << 5) +#define UART_IS_RI (1 << 4) +#define UART_IS_TBC (1 << 3) +#define UART_IS_RBC (1 << 2) +#define UART_IS_TXIDLE (1 << 1) + +#define UIE_RX (UART_IE_OFLOW | UART_IE_PARITY | UART_IE_FRAME | \ + UART_IE_BREAK | UART_IE_RBC) +#define UIS_RX (UART_IS_OFLOW | UART_IS_PARITY | UART_IS_FRAME | \ + UART_IS_BREAK | UART_IS_RBC) +#define UIE_MS (UART_IE_DSR | UART_IE_DCD | UART_IE_CTS | UART_IE_RI) +#define UIS_MS (UART_IS_DSR | UART_IS_DCD | UART_IS_CTS | UART_IS_RI) + +#define UART_CGAPCTRL 0x100c +#define UART_CGAPCTRL_EN (1 << 31) + +#define UART_BGAPCTRL 0x1010 +#define UART_BGAPCTRL_EN (1 << 31) + +#define UART_RCMC0 0x1014 +#define UART_RCMC0_ENABLE (1 << 31) +#define UART_RCMC0_MASK (0xff << 16) +#define UART_RCMC0_DATA (0xff << 0) + +#define UART_AWC 0x1030 +#define UART_AWC_ENABLE (1 << 0) + +#define UART_BRDL 0x1100 /* DLAB = 1 */ + +#define UART_UIE 0x1104 /* DLAB = 0 */ +#define UART_UIE_ETBEI (1 << 1) + +#define UART_BRDM 0x1104 /* DLAB = 1 */ + +#define UART_FCR 0x1108 +#define UART_FCR_TXCLR (1 << 2) +#define UART_FCR_RXCLR (1 << 1) +#define UART_FCR_FIFOEN (1 << 0) + +#define UART_LCR 0x110c +#define UART_LCR_DLAB (1 << 7) +#define UART_LCR_SBC (1 << 6) +#define UART_LCR_SPAR (1 << 5) +#define UART_LCR_EPAR (1 << 4) +#define UART_LCR_PARITY (1 << 3) +#define UART_LCR_STOP (1 << 2) +#define UART_LCR_WLEN 0x0003 +#define UART_LCR_WLEN_5 0x0000 +#define UART_LCR_WLEN_6 0x0001 +#define UART_LCR_WLEN_7 0x0002 +#define UART_LCR_WLEN_8 0x0003 + +#define UART_MCR 0x1110 +#define UART_MCR_AFE (1 << 5) +#define UART_MCR_LOOP (1 << 4) +#define UART_MCR_RTS (1 << 1) +#define UART_MCR_DTR (1 << 0) + +#define UART_LSR 0x1114 +#define UART_LSR_TEMT (1 << 6) + +#define UART_MSR 0x1118 +#define UART_MSR_DCD (1 << 7) +#define UART_MSR_RI (1 << 6) +#define UART_MSR_DSR (1 << 5) +#define UART_MSR_CTS (1 << 4) +#define UART_MSR_DDCD (1 << 3) +#define UART_MSR_TERI (1 << 2) +#define UART_MSR_DDSR (1 << 1) +#define UART_MSR_DCTS (1 << 0) + +#define UMSR_ANYDELTA (UART_MSR_DDCD | UART_MSR_TERI | \ + UART_MSR_DDSR | UART_MSR_DCTS) + +#define DRIVER_NAME "ns921x-serial" +#define NS921X_TTY_NAME "ttyNS" +#define NS921X_TTY_MAJOR 204 /* XXX */ +#define NS921X_TTY_MINOR_START 196 /* XXX */ + +#define NS921X_UART_NR 4 + +#define FIFOSIZE 16 +#define TXTHRESHOLD 1 +#define RXTHRESHOLD 5 + +#define up2unp(up) container_of(up, struct uart_ns921x_port, port) +struct uart_ns921x_port { + struct uart_port port; + struct clk *clk; +#define NSUPF_TXIRQPENDING 1 +#define NSUPF_SCHEDSTOPTX 2 + unsigned int flags; + u32 ifs2ack; + u32 is2ack; + struct ns921x_uart_data *data; +}; + +#if defined(CONFIG_DEBUG_LL) && 0 +void printch(char); +void printhex2(unsigned); +void printhex4(unsigned); +void printhex8(unsigned); +void printascii(const char *); +#else +#define printch(c) ((void)0) +#define printhex2(u) ((void)0) +#define printhex4(u) ((void)0) +#define printhex8(u) ((void)0) +#define printascii(s) ((void)0) +#endif + +static inline u32 uartread32(struct uart_port *port, unsigned int offset) +{ + u32 ret = ioread32(port->membase + offset); + +#if defined(DEBUG_UARTRW) + dev_dbg(port->dev, "read 0x%p -> 0x%08x\n", + port->membase + offset, ret); +#endif + + return ret; +} + +static inline void uartwrite32(struct uart_port *port, + u32 value, unsigned int offset) +{ +#if defined(DEBUG_UARTRW) + dev_dbg(port->dev, "write 0x%p <- 0x%08x\n", + port->membase + offset, value); +#endif + iowrite32(value, port->membase + offset); +} + +static inline void uartwrite8(struct uart_port *port, u8 value, + unsigned int offset) +{ +#if defined(DEBUG_UARTRW) + dev_dbg(port->dev, "write 0x%p <- 0x%02x\n", + port->membase + offset, value); +#endif + iowrite8(value, port->membase + offset); +} + +/* + * bits 19 to 31 of HUB_IFS need to be cleard by writing a 1 to it. + * ns921x_uart_read_ifs and ns921x_uart_clear_ifs help debugging missed clears + * by tracking the bits set + */ +#define IFS_BITSTOACK 0xfff80000 +static inline u32 ns921x_uart_read_ifs(struct uart_ns921x_port *unp) +{ + u32 ret = uartread32(&unp->port, HUB_IFS); + if (unp->ifs2ack) + dev_dbg(unp->port.dev, "%s: %s still unacked: %x " + "(called from %p)\n", __func__, "IFS", + unp->ifs2ack, __builtin_return_address(0)); + unp->ifs2ack |= ret & IFS_BITSTOACK; + + if (unp->ifs2ack & ~ret) + dev_dbg(unp->port.dev, "%s: rereading %s doesn't " + "yield unacked bits (called from %p): " + "%08x %08x\n", __func__, "IFS", + __builtin_return_address(0), unp->ifs2ack, ret); + + return ret; +} + +static inline void ns921x_uart_clear_ifs(struct uart_ns921x_port *unp, + unsigned int mask) +{ + if (mask & ~unp->ifs2ack) { + dev_dbg(unp->port.dev, "%s: unp->%s = %08x, mask = %08x\n", + __func__, "ifs2ack", unp->ifs2ack, mask); + BUG(); + } + BUG_ON(mask & ~IFS_BITSTOACK); + unp->ifs2ack &= ~mask; + uartwrite32(&unp->port, mask, HUB_IFS); +} + +/* + * all bits of UART_IS need to be cleard by writing a 1 to it. + * ns921x_uart_read_is and ns921x_uart_clear_is help debugging missed clears by + * tracking the bits set + */ +static inline u32 ns921x_uart_read_is(struct uart_ns921x_port *unp) +{ + u32 ret = uartread32(&unp->port, UART_IS); + if (unp->is2ack) + dev_dbg(unp->port.dev, "%s: %s still unacked: %x " + "(called from %p)\n", __func__, "IS", + unp->is2ack, __builtin_return_address(0)); + unp->is2ack |= ret; + + if (unp->is2ack & ~ret) + dev_dbg(unp->port.dev, "%s: rereading %s doesn't " + "yield unacked bits (called from %p): " + "%08x %08x\n", __func__, "IS", + __builtin_return_address(0), unp->is2ack, ret); + + return ret; +} + +static inline void ns921x_uart_clear_is(struct uart_ns921x_port *unp, + u32 mask) +{ + if (mask & ~unp->is2ack) { + dev_dbg(unp->port.dev, "%s: unp->%s = %08x, mask = %08x\n", + __func__, "is2ack", unp->is2ack, mask); + BUG(); + } + unp->is2ack &= ~mask; + uartwrite32(&unp->port, mask, UART_IS); +} + +static inline void ns921x_uart_rmw_uartie(struct uart_port *port, + u32 mask, u32 value) +{ + u32 ie = uartread32(port, UART_IE); + + BUG_ON((value & mask) != value); + + ie = (ie & ~mask) | value; + + uartwrite32(port, ie, UART_IE); +} + +static unsigned int ns921x_uart_tx_empty(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + /* Should I use UART_IS_TXIDLE? HUB_IFS_TXFE? */ + + u32 lsr; + +#if defined(NSUPF_TXIRQPENDING) + if (unp->flags & NSUPF_TXIRQPENDING) + return 0; +#endif + + lsr = uartread32(&unp->port, UART_LSR); + + if (!(lsr & UART_LSR_TEMT)) + return 0; + + return TIOCSER_TEMT; +} + +static void ns921x_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + u32 mcr = 0; + + /* RTS signal should be controlled as gpio to allow using AFE */ + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + uartwrite32(port, mcr, UART_MCR); +} + + +/* + * Depending on the configuration of the HW flow control, the CTS line is + * not be passed to the uart, making impossible to read the status of some + * modem lines through the modem status register. + * Following function workarounds that by reading the CTS line as gpio. + */ +static unsigned int ns921x_uart_check_msr(struct uart_port *port, u32 irqstat) +{ + struct uart_ns921x_port *unp = up2unp(port); + u32 status, cts_gpio; + + /* We can read the status of the modem lines through the + * modem status register, but we need to add the value of + * CTS line from the gpio value */ + status = uartread32(port, UART_MSR); + cts_gpio = unp->data->gpios[2]; + status |= (gpio_get_value(cts_gpio) != 0) ? 0 : UART_MSR_CTS; + + /* For event detection on the modem lines, we use the interrupt status + * register instead of the delta section of the modem status register. + * The interrupt flags are valid in all the cases */ + if ((irqstat & (UART_IS_DSR|UART_IS_DCD|UART_IS_CTS|UART_IS_RI)) && + port->info != NULL) { + if (irqstat & UART_IS_RI) + port->icount.rng++; + if (irqstat & UART_IS_DSR) + port->icount.dsr++; + if (irqstat & UART_IS_DCD) + uart_handle_dcd_change(port, status & UART_MSR_DCD); + if (irqstat & UART_IS_CTS) + uart_handle_cts_change(port, status & UART_MSR_CTS); + + wake_up_interruptible(&port->info->delta_msr_wait); + } + + return status; +} + +static unsigned int ns921x_uart_get_mctrl(struct uart_port *port) +{ + unsigned int status; + unsigned int ret = 0; + + status = ns921x_uart_check_msr(port, 0); + + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + + return ret; +} + +static void ns921x_uart_stop_tx_real(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + + u32 txic = uartread32(&unp->port, HUB_TXIC); + + printch('.'); + + ns921x_uart_rmw_uartie(&unp->port, UART_IE_TXIDLE, 0); + + uartwrite32(port, txic & ~HUB_TXIC_TXFSRIE, HUB_TXIC); + +#if defined(NSUPF_TXIRQPENDING) + unp->flags &= ~NSUPF_TXIRQPENDING; +#endif +} + +/* called with port->lock taken */ +static void ns921x_uart_stop_tx(struct uart_port *port) +{ +#if defined(NSUPF_TXIRQPENDING) && defined(NSUPF_SCHEDSTOPTX) + struct uart_ns921x_port *unp = up2unp(port); + + /* don't stop the port if there is a tx irq pending */ + if (unp->flags & NSUPF_TXIRQPENDING) { + unp->flags |= NSUPF_SCHEDSTOPTX; + } else { + ns921x_uart_stop_tx_real(port); + + unp->flags &= ~NSUPF_SCHEDSTOPTX; + } +#else + ns921x_uart_stop_tx_real(port); +#endif +} + +/* send out chars in xmit buffer. This is called with port->lock taken */ +static void ns921x_uart_tx_chars(struct uart_ns921x_port *unp, + unsigned int freebuffers) +{ + struct circ_buf *xmit = &unp->port.info->xmit; + unsigned long ifs; + + BUG_ON(!freebuffers); + + assert_spin_locked(&unp->port.lock); + + /* + * If the FIFO is not empty return at this point, then the + * interrupt-handler will recall this function + */ + ifs = ns921x_uart_read_ifs(unp); + if (!(ifs & HUB_IFS_TXFE)) + return; + + if (unp->port.x_char) { + uartwrite8(&unp->port, unp->port.x_char, HUB_DMTXDF); + unp->port.icount.tx++; + unp->port.x_char = 0; + freebuffers--; +#if defined(NSUPF_TXIRQPENDING) + unp->flags |= NSUPF_TXIRQPENDING; +#endif + } + if (uart_circ_empty(xmit) || uart_tx_stopped(&unp->port)) { + ns921x_uart_stop_tx(&unp->port); + return; + } + + printch('T'); + printhex4(uart_circ_chars_pending(xmit)); + while (freebuffers && uart_circ_chars_pending(xmit)) { + if (uart_circ_chars_pending(xmit) >= 4) { + u32 fourchars = xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + fourchars |= xmit->buf[xmit->tail] << 8; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + fourchars |= xmit->buf[xmit->tail] << 16; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + fourchars |= xmit->buf[xmit->tail] << 24; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + + uartwrite32(&unp->port, fourchars, HUB_DMTXDF); + printhex8(cpu_to_be32(fourchars)); + unp->port.icount.tx += 4; + } else { + uartwrite8(&unp->port, + xmit->buf[xmit->tail], HUB_DMTXDF); + printhex2(xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + unp->port.icount.tx++; + } +#if defined(NSUPF_TXIRQPENDING) + unp->flags |= NSUPF_TXIRQPENDING; +#endif + --freebuffers; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&unp->port); + + if (uart_circ_empty(xmit)) + ns921x_uart_stop_tx(&unp->port); +} + +#define maskshiftfac(mask) ((mask) & (-(mask))) +#define im2mask(val, mask) \ + (((val) * maskshiftfac(mask)) & (mask)) + +/* called with port->lock taken */ +static void ns921x_uart_start_tx(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + + u32 txic = uartread32(port, HUB_TXIC); + + uartwrite32(port, txic | im2mask(TXTHRESHOLD, HUB_TXIC_TXTHRS) | + HUB_TXIC_TXFSRIE, HUB_TXIC); + ns921x_uart_rmw_uartie(&unp->port, UART_IE_TXIDLE, UART_IE_TXIDLE); + +#if defined(NSUPF_SCHEDSTOPTX) + unp->flags &= ~NSUPF_SCHEDSTOPTX; +#endif + + if ( +#if defined(NSUPF_TXIRQPENDING) + !(unp->flags & NSUPF_TXIRQPENDING) && +#endif + !(ns921x_uart_read_ifs(unp) & HUB_IFS_TXFF)) { + unsigned int freebuffers = 1; + ns921x_uart_tx_chars(up2unp(port), freebuffers); + } +} + +static void ns921x_uart_stop_rx(struct uart_port *port) +{ + dev_vdbg(port->dev, "%s\n", __func__); + + ns921x_uart_rmw_uartie(port, UIE_RX, 0); +} + +static void ns921x_uart_enable_ms(struct uart_port *port) +{ + dev_vdbg(port->dev, "%s\n", __func__); + + ns921x_uart_rmw_uartie(port, UIE_MS, UIE_MS); +} + +static void ns921x_uart_break_ctl(struct uart_port *port, int break_state) +{ + unsigned long flags; + u32 lcr; + + dev_vdbg(port->dev, "%s\n", __func__); + + spin_lock_irqsave(&port->lock, flags); + + lcr = uartread32(port, UART_LCR); + + /* XXX: amba_pl011_break_ctl tests for break_state being -1, + * Documentation/serial/driver tells to tests for != 0 */ + if (break_state) + lcr |= UART_LCR_SBC; + else + lcr &= ~UART_LCR_SBC; + + uartwrite32(port, lcr, UART_LCR); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void ns921x_uart_rx_char(struct uart_ns921x_port *unp, + unsigned int ch, u32 is) +{ + unsigned int flag = TTY_NORMAL; + + unp->port.icount.rx++; +#define ISERR (UART_IS_BREAK | UART_IS_PARITY | UART_IS_FRAME | UART_IS_OFLOW) + + dev_vdbg(unp->port.dev, "%s: IS=%x\n", __func__, is); + if (unlikely(is & ISERR)) { + if (is & UART_IS_BREAK) { + dev_vdbg(unp->port.dev, "break, IS=%x\n", is); + unp->port.icount.brk++; + if (uart_handle_break(&unp->port)) + return; + } else if (is & UART_IS_PARITY) + unp->port.icount.parity++; + else if (is & UART_IS_FRAME) + unp->port.icount.frame++; + + if (is & UART_IS_OFLOW) + unp->port.icount.overrun++; + + is &= unp->port.read_status_mask; + + if (is & UART_IS_BREAK) + flag = TTY_BREAK; + else if (is & UART_IS_PARITY) + flag = TTY_PARITY; + else if (is & UART_IS_FRAME) + flag = TTY_FRAME; + } + + if (uart_handle_sysrq_char(&unp->port, ch)) + return; + + dev_vdbg(unp->port.dev, "%s: insert %x (IS=%x, ism=%x, flag=%x)\n", + __func__, ch, is, unp->port.ignore_status_mask, flag); + uart_insert_char(&unp->port, is, UART_IS_OFLOW, ch, flag); +} + +/* This is called with port->lock taken */ +static void ns921x_uart_rx_chars(struct uart_ns921x_port *unp, + u32 ifs, u32 is) +{ + struct tty_struct *tty = unp->port.info->port.tty; + + if (is & (UART_IS_RBC | UIS_RX)) + ns921x_uart_clear_is(unp, is & (UART_IS_RBC | UIS_RX)); + + if (ifs & HUB_IFS_RXFE) { + if (is & (UART_IS_BREAK | UART_IS_OFLOW)) + ns921x_uart_rx_char(unp, 0, is); + } + + while (!(ifs & HUB_IFS_RXFE)) { + u32 dmrxsf = uartread32(&unp->port, HUB_DMRXSF); + u32 dmrxdf = uartread32(&unp->port, HUB_DMRXDF); + /* XXX: better put this into a macro */ + unsigned int bytes = (dmrxsf & HUB_DMRXSF_BYTE) >> 9; + int i; + + dev_vdbg(unp->port.dev, "%s: DMRXSF = %x, DMRXDF = %x\n", + __func__, dmrxsf, dmrxdf); + + BUG_ON(bytes > 4); + + for (i = 0; i < bytes; ++i) { + unsigned int ch = (dmrxdf >> (8 * i)) & 0xff; + + /* + * assume break and errors only apply to the last + * character. + */ + ns921x_uart_rx_char(unp, ch, i == bytes - 1 ? is : 0); + } + ifs = ns921x_uart_read_ifs(unp); + } + spin_unlock(&unp->port.lock); + tty_flip_buffer_push(tty); + spin_lock(&unp->port.lock); +} + +static irqreturn_t ns921x_uart_int(int irq, void *dev_id) +{ + struct uart_ns921x_port *unp = dev_id; + u32 ifs, uninitialized_var(is); + + spin_lock(&unp->port.lock); + + ifs = ns921x_uart_read_ifs(unp); + + if (ifs & HUB_IFS_TXFSRIP) { + ns921x_uart_clear_ifs(unp, HUB_IFS_TXFSRIP); + + /* don't clear NSUPF_TXIRQPENDING, we still wait for TXdone */ + + ns921x_uart_tx_chars(unp, FIFOSIZE - TXTHRESHOLD - 1); + } + + if ((ifs & HUB_IFS_RXFSRIP) || (ifs & HUB_IFS_MODIP)) + is = ns921x_uart_read_is(unp); + + if (ifs & HUB_IFS_RXFSRIP) { + ns921x_uart_clear_ifs(unp, HUB_IFS_RXFSRIP); + + ns921x_uart_rx_chars(unp, ifs, is); + } + + if (ifs & HUB_IFS_MODIP) { + dev_vdbg(unp->port.dev, "%s: IS=%x\n", __func__, is); + + /* test for HUB_IFS_RXFSRIP not being set to only fetch + * characters once. + */ + if (is & UIS_RX && !(ifs & HUB_IFS_RXFSRIP)) + ns921x_uart_rx_chars(unp, ifs, is); + + if (is & UIS_MS) + ns921x_uart_clear_is(unp, is & UIS_MS); + + /* XXX: call this only after an MS irq? */ + ns921x_uart_check_msr(&unp->port, is); + + if (is & UART_IS_TXIDLE) { + unsigned int freebuffers = TXTHRESHOLD; +#if defined(NSUPF_TXIRQPENDING) + unp->flags &= ~NSUPF_TXIRQPENDING; +#endif + + ns921x_uart_clear_is(unp, UART_IS_TXIDLE); + + /* The fifo might not be empty. The following can + * happen: + * - irq for HUB_IFS_TXFSRIP serviced without + * UART_IS_TXIDLE + * - before new data is written the fifo becomes empty + * and UART_IS_TXIDLE is pending + * - UART_IS_TXIDLE is serviced with fifo still having + * some data from servicing UART_IS_TXIDLE. + * + * So we only write TXTHRESHOLD buffers. Only if the + * fifo is reported to be empty and we didn't wrote + * something above, the full fifo is used. + */ + if ((ifs & (HUB_IFS_TXFE | HUB_IFS_TXFSRIP)) == + HUB_IFS_TXFE) + freebuffers = FIFOSIZE; + + ns921x_uart_tx_chars(unp, freebuffers); + } + + if (unp->is2ack) + dev_dbg(unp->port.dev, + "%s: unacked flags in UART_IS: %x %x\n", + __func__, unp->is2ack, is); + } + + if (unp->ifs2ack) + dev_dbg(unp->port.dev, "%s: unacked flags in HUB_IFS: %x\n", + __func__, unp->ifs2ack); + +#if defined(NSUPF_SCHEDSTOPTX) && defined(NSUPF_TXIRQPENDING) + if ((unp->flags & (NSUPF_TXIRQPENDING | NSUPF_SCHEDSTOPTX)) == + NSUPF_SCHEDSTOPTX) { + ns921x_uart_stop_tx_real(&unp->port); + unp->flags &= ~NSUPF_SCHEDSTOPTX; + } +#endif + + spin_unlock(&unp->port.lock); + + return IRQ_HANDLED; +} + +static int ns921x_uart_startup(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + int ret; + u32 rxic; + u32 wc; + + unp->flags = 0; + + ret = clk_enable(unp->clk); + if (ret) { + dev_dbg(port->dev, "%s: err_clkenable", __func__); + goto err_clkenable; + } + + unp->port.uartclk = clk_get_rate(unp->clk); + + ret = request_irq(unp->port.irq, ns921x_uart_int, 0, DRIVER_NAME, unp); + if (ret) { + dev_dbg(port->dev, "%s: err_request_irq", __func__); + + clk_disable(unp->clk); +err_clkenable: + + return ret; + } + + ns921x_uart_rmw_uartie(port, UIE_RX, UIE_RX); + + rxic = uartread32(port, HUB_RXIC); + uartwrite32(port, rxic | im2mask(RXTHRESHOLD, HUB_RXIC_RXTHRS) + | HUB_RXIC_RXFSRIE, HUB_RXIC); + + wc = UART_WC_RXEN | UART_WC_TXEN; + if (unp->data->rtsen) + wc |= UART_WC_RTSEN; + uartwrite32(port, wc, UART_WC); + uartwrite32(&unp->port, UART_FCR_FIFOEN, UART_FCR); + + return 0; +} + +static void ns921x_uart_shutdown(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + + /* wait up to 10ms for the fifo to empty */ + unsigned long expire = jiffies + msecs_to_jiffies(10); + + printch('\\'); + + while (!ns921x_uart_tx_empty(port)) { + msleep(1); + if (time_after(jiffies, expire)) + break; + } + + ns921x_uart_rmw_uartie(port, -1, 0); + uartwrite32(port, 0, UART_WC); + + ns921x_uart_break_ctl(port, 0); + + free_irq(port->irq, unp); + + clk_disable(unp->clk); +} + +static void ns921x_uart_set_termios(struct uart_port *port, + struct ktermios *termios, struct ktermios *old) +{ + unsigned long flags; + u32 cval = 0; + + unsigned int baud, quot; + u32 saved_wc; + + switch (termios->c_cflag & CSIZE) { + case CS5: + cval = UART_LCR_WLEN_5; + break; + case CS6: + cval = UART_LCR_WLEN_6; + break; + case CS7: + cval = UART_LCR_WLEN_7; + break; + default: + case CS8: + cval = UART_LCR_WLEN_8; + break; + } + + if (termios->c_cflag & CSTOPB) + cval |= UART_LCR_STOP; + + if (termios->c_cflag & PARENB) { + cval |= UART_LCR_PARITY; + + if (!(termios->c_cflag & PARODD)) + cval |= UART_LCR_EPAR; +#ifdef CMSPAR + /* stick parity: + * - MARK parity requires flags PARENB | CMSPAR | PARODD + * and in the controller the EPS and PEN bitfields + * must be set to 0 + * - SPACE parity requires flags PARENB | CMSPAR | ~PARODD + * and in the controller the EPS and PEN bitfields + * must be set to 1 (these are already set by the + * code above this, so there is nothing to do here) + * In any case SP bitfield must be set to 1 + */ + if(termios->c_cflag & CMSPAR) { + cval |= UART_LCR_SPAR; /* set stick parity */ + if (termios->c_cflag & PARODD) { + cval &= ~UART_LCR_EPAR; + cval &= ~UART_LCR_PARITY; + } + } +#endif + } + + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); + quot = uart_get_divisor(port, baud); + + spin_lock_irqsave(&port->lock, flags); + + uart_update_timeout(port, termios->c_cflag, baud); + + port->read_status_mask = UART_IS_OFLOW; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UART_IS_FRAME | UART_IS_PARITY; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UART_IS_BREAK; + + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_IS_FRAME | UART_IS_PARITY; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= UART_IS_BREAK; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_IS_OFLOW; + } + + /* disable everything, prepare fifo flushing */ + saved_wc = uartread32(port, UART_WC); + uartwrite32(port, UART_WC_RXFLUSH | UART_WC_TXFLUSH, UART_WC); + + if (UART_ENABLE_MS(port, termios->c_cflag)) + ns921x_uart_enable_ms(port); + + /* set character gap period */ + uartwrite32(port, UART_CGAPCTRL_EN | (10 * (port->uartclk / baud) - 1), + UART_CGAPCTRL); + + /* set buffer gap period */ + uartwrite32(port, UART_BGAPCTRL_EN | (640 * (port->uartclk / baud) - 1), + UART_BGAPCTRL); + + /* prepare to access baud rate registers */ + uartwrite32(port, UART_LCR_DLAB, UART_LCR); + + /* set baud rate */ + uartwrite32(port, quot & 0xff, UART_BRDL); + uartwrite32(port, (quot >> 8) & 0xff, UART_BRDM); + + udelay(1); + uartwrite32(port, cval, UART_LCR); + + /* flush fifos and restore state */ + uartwrite32(port, saved_wc, UART_WC); + uartwrite32(port, UART_UIE_ETBEI, UART_UIE); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *ns921x_uart_type(struct uart_port *port) +{ + return port->type == PORT_NS921X ? "NS921X" : NULL; +} + +static void ns921x_uart_release_port(struct uart_port *port) +{ + /* XXX: release_mem_region is marked as Compatibility cruft ??? */ + release_mem_region(port->mapbase, 0x8000); +} + +static int ns921x_uart_request_port(struct uart_port *port) +{ + return request_mem_region(port->mapbase, + 0x8000, DRIVER_NAME) ? 0 : -EBUSY; +} + +static void ns921x_uart_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + port->type = PORT_NS921X; + ns921x_uart_request_port(port); + } +} + +static int ns921x_uart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + int ret = 0; + if (ser->type != PORT_UNKNOWN && ser->type != PORT_NS921X) + ret = -EINVAL; + + if (ser->irq < 0 || ser->irq >= NR_IRQS) + ret = -EINVAL; + + if (ser->baud_base < 9600) + ret = -EINVAL; + + return ret; +} + +static struct uart_ops ns921x_uart_pops = { + .tx_empty = ns921x_uart_tx_empty, + .set_mctrl = ns921x_uart_set_mctrl, + .get_mctrl = ns921x_uart_get_mctrl, + .stop_tx = ns921x_uart_stop_tx, + .start_tx = ns921x_uart_start_tx, + .stop_rx = ns921x_uart_stop_rx, + .enable_ms = ns921x_uart_enable_ms, + .break_ctl = ns921x_uart_break_ctl, + .startup = ns921x_uart_startup, + .shutdown = ns921x_uart_shutdown, + .set_termios = ns921x_uart_set_termios, + /* .pm = ns921x_uart_pm, */ + /* .set_wake = ns921x_uart_set_wake, */ + .type = ns921x_uart_type, + .release_port = ns921x_uart_release_port, + .request_port = ns921x_uart_request_port, + .config_port = ns921x_uart_config_port, + .verify_port = ns921x_uart_verify_port, + /* .ioctl */ +}; + +static struct uart_ns921x_port *ns921x_uart_ports[NS921X_UART_NR]; + +#if defined(CONFIG_SERIAL_NS921X_CONSOLE) + +static void ns921x_uart_console_putchar(struct uart_port *port, int ch) +{ + struct uart_ns921x_port *unp = up2unp(port); + + while (ns921x_uart_read_ifs(unp) & HUB_IFS_TXFF) + barrier(); + + uartwrite8(&unp->port, ch, HUB_DMTXDF); +} + +/* called with console_sem hold. irqs locally disabled */ +static void ns921x_uart_console_write(struct console *co, + const char *s, unsigned int count) +{ + struct uart_ns921x_port *unp = ns921x_uart_ports[co->index]; + u32 saved_txic, saved_wc, saved_uie; + u32 new_wc; + + BUG_ON(!irqs_disabled()); + + saved_txic = uartread32(&unp->port, HUB_TXIC); + saved_wc = uartread32(&unp->port, UART_WC); + saved_uie = uartread32(&unp->port, UART_UIE); + + new_wc = saved_wc | UART_WC_RXEN | UART_WC_TXEN; + new_wc &= ~(UART_WC_RXFLUSH | UART_WC_TXFLUSH); + + /* XXX: assert baud, bits, parity etc. to be correct. */ + + uartwrite32(&unp->port, 0, HUB_TXIC); + uartwrite32(&unp->port, UART_FCR_FIFOEN, UART_FCR); + uartwrite32(&unp->port, UART_UIE_ETBEI, UART_UIE); + uartwrite32(&unp->port, new_wc, UART_WC); + + uart_console_write(&unp->port, s, count, ns921x_uart_console_putchar); + + /* wait for HUB fifo to become empty */ + while (!(ns921x_uart_read_ifs(unp) & HUB_IFS_TXFE)) + barrier(); + + while (!(uartread32(&unp->port, UART_LSR) & UART_LSR_TEMT)) + barrier(); + + uartwrite32(&unp->port, saved_wc, UART_WC); + uartwrite32(&unp->port, saved_uie, UART_UIE); + uartwrite32(&unp->port, saved_txic, HUB_TXIC); +} + +static void __init ns921x_uart_console_get_options(struct uart_ns921x_port *unp, + int *baud, int *parity, int *bits, int *flow) +{ + if (uartread32(&unp->port, UART_WC) & UART_WC_TXEN) { + u32 cval = uartread32(&unp->port, UART_LCR); + unsigned int quot = 0; + + uartwrite32(&unp->port, UART_LCR_DLAB, UART_LCR); + + /* XXX: write barrier */ + + quot = (uartread32(&unp->port, UART_BRDM) & 0xff) << 8; + quot |= uartread32(&unp->port, UART_BRDL) & 0xff; + + uartwrite32(&unp->port, cval, UART_LCR); + + *baud = unp->port.uartclk / (16 * quot); + + *parity = 'n'; + if (cval & UART_LCR_PARITY) { + if (cval & UART_LCR_EPAR) + *parity = 'e'; + else + *parity = 'o'; + } + if (cval & UART_LCR_SPAR) { + if( (cval & UART_LCR_PARITY) && + (cval & UART_LCR_EPAR) ) + *parity = 'm'; + else + *parity = 's'; + } + + switch (cval & UART_LCR_WLEN) { + case UART_LCR_WLEN_5: + *bits = 5; + break; + case UART_LCR_WLEN_6: + *bits = 6; + break; + case UART_LCR_WLEN_7: + *bits = 7; + break; + case UART_LCR_WLEN_8: + *bits = 8; + break; + } + + /* XXX: *flow = ... */ + } +} + +static int __init ns921x_uart_console_setup(struct console *co, char *options) +{ + struct uart_ns921x_port *unp; + int baud = 38400; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + int ret; + + if (co->index >= NS921X_UART_NR) + co->index = 0; + unp = ns921x_uart_ports[co->index]; + if (!unp) + return -ENODEV; + + ret = clk_enable(unp->clk); + if (ret) + return ret; + + unp->port.uartclk = clk_get_rate(unp->clk); + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + ns921x_uart_console_get_options(unp, + &baud, &parity, &bits, &flow); + + ret = uart_set_options(&unp->port, co, baud, parity, bits, flow); + + /* disable the clock only in the error path, because + * otherwise it would need to be enabled and disabled in console_write + * where it must not sleep. But as clk_disable does a gpio_free it + * might. + */ + if (ret) + clk_disable(unp->clk); + + return ret; +} + +static struct uart_driver ns921x_uart_reg; +static struct console ns921x_uart_console = { + .name = NS921X_TTY_NAME, + .write = ns921x_uart_console_write, + /* .read */ + .device = uart_console_device, + /* .unblank */ + .setup = ns921x_uart_console_setup, + /* .early_setup */ + .flags = CON_PRINTBUFFER, + .index = -1, + /* .cflag */ + .data = &ns921x_uart_reg, + /* .next */ +}; + +#define NS921X_UART_CONSOLE (&ns921x_uart_console) +#else +#define NS921X_UART_CONSOLE NULL +#endif + +static struct uart_driver ns921x_uart_reg = { + .owner = THIS_MODULE, + .driver_name = DRIVER_NAME, + .dev_name = NS921X_TTY_NAME, + .major = NS921X_TTY_MAJOR, + .minor = NS921X_TTY_MINOR_START, + .nr = NS921X_UART_NR, + .cons = NS921X_UART_CONSOLE, +}; + +static __devinit int ns921x_uart_pdrv_probe(struct platform_device *pdev) +{ + int ret; + struct uart_ns921x_port *unp; + struct resource *mem; + void __iomem *base; + int line, irq; + + line = pdev->id; + if (line < 0 || line >= ARRAY_SIZE(ns921x_uart_ports)) { + ret = -ENODEV; + dev_dbg(&pdev->dev, "%s: err_line\n", __func__); + goto err_line; + } + + if (ns921x_uart_ports[line] != NULL) { + ret = -EBUSY; + dev_dbg(&pdev->dev, "%s: err_line\n", __func__); + goto err_line; + } + + unp = kzalloc(sizeof(struct uart_ns921x_port), GFP_KERNEL); + ns921x_uart_ports[line] = unp; + + if (unp == NULL) { + ret = -ENOMEM; + dev_dbg(&pdev->dev, "%s: err_alloc\n", __func__); + goto err_alloc; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENOENT; + dev_info(&pdev->dev, "%s: err_get_irq\n", __func__); + goto err_get_irq; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + ret = -ENODEV; + dev_dbg(&pdev->dev, "%s: err_get_mem\n", __func__); + goto err_get_mem; + } + + base = ioremap(mem->start, 0x8000); + if (!base) { + ret = -ENOMEM; + dev_dbg(&pdev->dev, "%s: err_ioremap\n", __func__); + goto err_ioremap; + } + dev_dbg(&pdev->dev, "%s: membase = %p, unp = %p\n", + __func__, base, unp); + + unp->clk = clk_get(&pdev->dev, DRIVER_NAME); + if (IS_ERR(unp->clk)) { + ret = PTR_ERR(unp->clk); + dev_dbg(&pdev->dev, "%s: err_clk_get -> %d\n", __func__, ret); + goto err_clk_get; + } + + unp->data = pdev->dev.platform_data; + unp->port.dev = &pdev->dev; + unp->port.mapbase = mem->start; + unp->port.membase = base; + unp->port.iotype = UPIO_MEM; + unp->port.irq = irq; + unp->port.fifosize = 64; /* XXX */ + unp->port.ops = &ns921x_uart_pops; + unp->port.flags = UPF_BOOT_AUTOCONF; + unp->port.line = line; + + /* no DMA */ + uartwrite32(&unp->port, HUB_DMARXCTRL_DIRECT, HUB_DMARXCTRL); + uartwrite32(&unp->port, HUB_DMATXCTRL_DIRECT, HUB_DMATXCTRL); + + ret = uart_add_one_port(&ns921x_uart_reg, &unp->port); + if (ret) { + + dev_dbg(&pdev->dev, "%s: err_uart_add1port -> %d\n", + __func__, ret); + + clk_put(unp->clk); +err_clk_get: + + iounmap(base); +err_ioremap: +err_get_mem: +err_get_irq: + + kfree(unp); + ns921x_uart_ports[line] = NULL; +err_alloc: + +err_line: + + return ret; + } + + /* Make the device wakeup capable, but disabled by default */ + device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_enable(&pdev->dev, 0); + + platform_set_drvdata(pdev, unp); + + return 0; +} + +static __devexit int ns921x_uart_pdrv_remove(struct platform_device *pdev) +{ + int line; + struct uart_ns921x_port *unp = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "%s\n", __func__); + + line = unp->port.line; + + uart_remove_one_port(&ns921x_uart_reg, &unp->port); + clk_put(unp->clk); + iounmap(unp->port.membase); + kfree(unp); + ns921x_uart_ports[line] = NULL; + + return 0; +} + +#ifdef CONFIG_PM +static int ns921x_uart_pdrv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct uart_ns921x_port *unp = platform_get_drvdata(pdev); + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (device_may_wakeup(&pdev->dev)) { + ret = enable_irq_wake(unp->port.irq); + if (ret) + dev_dbg(&pdev->dev, "%s: err_enable_irq_wake -> %d\n", + __func__, ret); + + else { + uartwrite32(&unp->port, UART_RCMC0_ENABLE | + UART_RCMC0_MASK, UART_RCMC0); + /* Write a 0 and then enable to clear previous interrupts */ + uartwrite32(&unp->port, 0x0, UART_AWC); + uartwrite32(&unp->port, UART_AWC_ENABLE, UART_AWC); + } + } else + ret = uart_suspend_port(&ns921x_uart_reg, &unp->port); + + return ret; +} + +static int ns921x_uart_pdrv_resume(struct platform_device *pdev) +{ + struct uart_ns921x_port *unp = platform_get_drvdata(pdev); + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (device_may_wakeup(&pdev->dev)) { + uartwrite32(&unp->port, 0x0, UART_RCMC0); + ret = disable_irq_wake(unp->port.irq); + } + else + ret = uart_resume_port(&ns921x_uart_reg, &unp->port); + + return ret; +} +#else +#define ns921x_uart_pdrv_suspend NULL +#define ns921x_uart_pdrv_resume NULL +#endif + +static struct platform_driver ns921x_uart_pdriver = { + .probe = ns921x_uart_pdrv_probe, + .remove = __devexit_p(ns921x_uart_pdrv_remove), + .suspend = ns921x_uart_pdrv_suspend, + .resume = ns921x_uart_pdrv_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ns921x_uart_init(void) +{ + int ret; + + ret = uart_register_driver(&ns921x_uart_reg); + if (ret) { + pr_debug("%s: err_uart_register_driver\n", __func__); + goto err_uart_register_driver; + } + + ret = platform_driver_register(&ns921x_uart_pdriver); + + if (ret) { + pr_debug("%s: err_pdrv_register\n", __func__); + + uart_unregister_driver(&ns921x_uart_reg); +err_uart_register_driver: + + return ret; + } + pr_info("Digi NS921x UART driver\n"); + + return 0; +} + +static void __exit ns921x_uart_exit(void) +{ + platform_driver_unregister(&ns921x_uart_pdriver); + uart_unregister_driver(&ns921x_uart_reg); +} + +module_init(ns921x_uart_init); +module_exit(ns921x_uart_exit); + +MODULE_AUTHOR("Uwe Kleine-Koenig"); +MODULE_DESCRIPTION("Digi NS921x UART driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); |