From: Uwe Kleine-König <Uwe.Kleine-Koenig@xxxxxxxx> Signed-off-by: Uwe Kleine-König <Uwe.Kleine-Koenig@xxxxxxxx> --- Hello, [I tried to send this patch already yesterday from my work's address, but it seems to have problems to send to vger. So I resend from my private address in the hope that I don't annoy to much if the first post will make it through sometime later.] This driver is not yet completly finished, I still have an issue loosing characters. By tracking TXIRQPENDING made all characters appear as expected, but still I'm not sure if this is the problem because I have no explanation why this should change anything. The idea came after seeing that ns921x_uart_tx_chars is called most of the time from ns921x_uart_start_tx and not as I expected from ns921x_uart_int. Is this an expected behaviour or do I have specified some values wrong? Moreover I have a few open questions and some suggestions, some of them are marked with XXX in the code: - NS912X_TTY_MAJOR and NS912X_TTY_MINOR_START are stolen from serial_txx9. Before this patch can go in, I still need to reserve some numbers for ns921x. I didn't want to do that before the driver got some review. - should I call ns921x_uart_check_msr only if I got a modem status irq? - release_mem_region is marked as "Compatibility cruft" in include/linux/ioport.h. Is that correct, do I use it in the right way? (Same for amba-pl011.c) - The clk API states that clk_get_rate is only to be called if the clock is enabled. I think (read, I didn't check) that ns921x_uart_console_setup calls clk_get_rate without asserting that the clock is enabled. (Same for amba-pl011.c) - About fifosize: The device allows to write 16 times up to four characters to the fifo. I don't understand what port.fifosize is used for, so I don't know if I should use 16 or 64. - Documentation/serial/driver, get_mctrl(...) s/TIOCM_DCD/TIOCM_CAR/ s/TIOCM_RI/TIOCM_RNG/ - Documentation/serial/driver uses port_sem and port->lock to describe locking of several functions. Are both terms supposed to mean the same? If so, I suggest to use only one. If not I might want to rethink locking in the driver. - Looking at break_ctl, Documentation states to test ctl for != 0, drivers/serial/amba-pl011.c tests for == -1 - sparse reports warning: context imbalance in 'ns921x_uart_rx_chars' - unexpected unlock that's something amba-pl011.c suffers from, too. - Is it correct to use include/linux/ns921x-serial.h? I still need to check if I need some #ifdef KERNEL or something. I'm looking forward to any comments, best regards Uwe drivers/serial/Kconfig | 15 + drivers/serial/Makefile | 1 + drivers/serial/ns921x-serial.c | 1038 ++++++++++++++++++++++++++++++++++++++++ include/linux/ns921x-serial.h | 23 + include/linux/serial_core.h | 2 + 5 files changed, 1079 insertions(+), 0 deletions(-) create mode 100644 drivers/serial/ns921x-serial.c create mode 100644 include/linux/ns921x-serial.h diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index ed438bc..1f89ba7 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1284,4 +1284,19 @@ config SERIAL_OF_PLATFORM Currently, only 8250 compatible ports are supported, but others can easily be added. +config SERIAL_NS921X + tristate "NetSilicon 921x serial port support" + depends on ARM && ARCH_NS9XXX && PROCESSOR_NS921X + select SERIAL_CORE + help + If you have a Digi NS921x based system and wish to use its serial + ports, say Y or M here + +config SERIAL_NS921X_CONSOLE + bool "Console on NetSilicon 921x serial + depends on SERIAL_NS921X + select SERIAL_CORE_CONSOLE + help + Console support for serial ports of Digi NS921x based systems + endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index af6377d..bb732b9 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -64,3 +64,4 @@ obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_NETX) += netx-serial.o obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o +obj-$(CONFIG_SERIAL_NS921X) += ns921x-serial.o diff --git a/drivers/serial/ns921x-serial.c b/drivers/serial/ns921x-serial.c new file mode 100644 index 0000000..cfad6f2 --- /dev/null +++ b/drivers/serial/ns921x-serial.c @@ -0,0 +1,1038 @@ +/* + * drivers/serial/ns921x-serial.c + * + * Copyright (C) 2007 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/module.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ns921x-serial.h> +#include <linux/platform_device.h> +#include <linux/serial_core.h> +#include <linux/serial.h> + +#define HUB_IFS 0x0000 +#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_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_TXFUFIE (1 << 26) + +#define HUB_DMTXDF 0x0028 + +#define UART_WC 0x1000 +#define UART_WC_RXEN (1 << 30) +#define UART_WC_TXEN (1 << 29) +#define UART_WC_RXFLUSH (1 << 17) +#define UART_WC_TXFLUSH (1 << 16) + +#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_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_FIFOEN (1 << 1) + +#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_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-uart" +#define NS912X_TTY_NAME "ttyNS" +#define NS912X_TTY_MAJOR 204 /* XXX */ +#define NS912X_TTY_MINOR_START 196 /* XXX */ + +#define NS921X_UART_NR 4 + +#define up2unp(up) container_of(up, struct uart_ns921x_port, port) +struct uart_ns921x_port { + struct uart_port port; + struct clk *clk; + int (*gpio_request)(void *data); + void (*gpio_configure)(void *data); + void (*gpio_free)(void *data); + void *gpio_data; +#define TXIRQPENDING 1 + unsigned int flags; + unsigned int ifs2ack; + unsigned int is2ack; +}; + +/* + * 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 unsigned long ns921x_uart_read_ifs(struct uart_ns921x_port *unp) +{ + unsigned long ret = readl(unp->port.membase + HUB_IFS); + unp->ifs2ack |= ret & IFS_BITSTOACK; + return ret; +} + +static inline void ns921x_uart_clear_ifs(struct uart_ns921x_port *unp, + unsigned int mask) +{ + BUG_ON((unp->ifs2ack & mask) != mask); + BUG_ON(mask & ~IFS_BITSTOACK); + unp->ifs2ack &= ~mask; + writel(mask, unp->port.membase + 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 unsigned long ns921x_uart_read_is(struct uart_ns921x_port *unp) +{ + unsigned long ret = readl(unp->port.membase + UART_IS); + unp->is2ack |= ret; + return ret; +} + +static inline void ns921x_uart_clear_is(struct uart_ns921x_port *unp, + unsigned int mask) +{ + BUG_ON((unp->is2ack & mask) != mask); + unp->is2ack &= ~mask; + writel(mask, unp->port.membase + UART_IS); +} + +static unsigned int ns921x_uart_tx_empty(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + if (!(ns921x_uart_read_ifs(unp) & HUB_IFS_TXFE)) + return 0; + + if (ns921x_uart_read_is(unp) & UART_IS_TXIDLE) + return TIOCSER_TEMT; + else + return 0; +} + +static void ns921x_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + unsigned long mcr = 0; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + writel(mcr, port->membase + UART_MCR); +} + +static unsigned int ns921x_uart_check_msr(struct uart_port *port) +{ + unsigned int status = readl(port->membase + UART_MSR); + + if (status & UMSR_ANYDELTA /* && IER_MSI */ && port->info != NULL) { + if (status & UART_MSR_TERI) + port->icount.rng++; + if (status & UART_MSR_DDSR) + port->icount.dsr++; + if (status & UART_MSR_DDCD) + uart_handle_dcd_change(port, status & UART_MSR_DCD); + if (status & UART_MSR_DCTS) + 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); + + 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; +} + +/* called with port->lock taken */ +static void ns921x_uart_stop_tx(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + + unsigned long ie = readl(unp->port.membase + UART_IE); + + writel(ie & ~UART_IE_TXIDLE, unp->port.membase + UART_IE); + + unp->flags &= ~TXIRQPENDING; +} + +/* 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; + + BUG_ON(!freebuffers); + + if (unp->port.x_char) { + dev_dbg(unp->port.dev, "%s: send x_char %hu\n", + __func__, unp->port.x_char); + writeb(unp->port.x_char, unp->port.membase + HUB_DMTXDF); + unp->port.icount.tx++; + unp->port.x_char = 0; + freebuffers--; + unp->flags |= TXIRQPENDING; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(&unp->port)) { + ns921x_uart_stop_tx(&unp->port); + return; + } + + while (freebuffers-- && uart_circ_chars_pending(xmit)) { + if (uart_circ_chars_pending(xmit) >= 4) { + unsigned long 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); + + writel(fourchars, unp->port.membase + HUB_DMTXDF); + unp->port.icount.tx += 4; + } else { + writeb(xmit->buf[xmit->tail], + unp->port.membase + HUB_DMTXDF); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + unp->port.icount.tx++; + } + unp->flags |= TXIRQPENDING; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&unp->port); + + if (uart_circ_empty(xmit)) + ns921x_uart_stop_tx(&unp->port); +} + +/* called with port->lock taken */ +static void ns921x_uart_start_tx(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + + unsigned long ie = readl(port->membase + UART_IE); + dev_dbg(port->dev, "%s: flags = %u\n", __func__, unp->flags); + + writel(ie | UART_IE_TXIDLE, port->membase + UART_IE); + + if (!(unp->flags & TXIRQPENDING) && + !(ns921x_uart_read_ifs(unp) & HUB_IFS_TXFF)) + ns921x_uart_tx_chars(up2unp(port), 1); +} + +static void ns921x_uart_stop_rx(struct uart_port *port) +{ + unsigned long ie = readl(port->membase + UART_IE); + + writel(ie & ~UIE_RX, port->membase + UART_IE); +} + +static void ns921x_uart_enable_ms(struct uart_port *port) +{ + unsigned long ie = readl(port->membase + UART_IE); + + writel(ie | UIE_MS, port->membase + UART_IE); +} + +static void ns921x_uart_break_ctl(struct uart_port *port, int break_state) +{ + unsigned long flags; + unsigned long lcr; + + /* XXX: amba_pl011_break_ctl tests for break_state being -1, + * Documentation/serial/driver tells to tests for != 0 */ + spin_lock_irqsave(&port->lock, flags); + + lcr = readl(port->membase + UART_LCR); + + if (break_state) + lcr |= UART_LCR_SBC; + else + lcr &= ~UART_LCR_SBC; + + writel(lcr, port->membase + UART_LCR); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void ns921x_uart_rx_char(struct uart_ns921x_port *unp, + unsigned int ch, unsigned long 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_dbg(unp->port.dev, "%s: IS=%lx\n", __func__, is); + if (unlikely(is & ISERR)) { + if (is & UART_IS_BREAK) { + dev_dbg(unp->port.dev, "break, IS=%lx\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_dbg(unp->port.dev, "%s: insert %x (IS=%lx, 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, + unsigned long ifs, unsigned long is) +{ + struct tty_struct *tty = unp->port.info->tty; + + if (ifs & HUB_IFS_RXFE) { + BUG_ON(!(is & UART_IS_BREAK)); /* maybe overflow? */ + ns921x_uart_rx_char(unp, 0, is); + } + + while (!(ifs & HUB_IFS_RXFE)) { + unsigned long dmrxsf = readl(unp->port.membase + HUB_DMRXSF); + unsigned long dmrxdf = readl(unp->port.membase + HUB_DMRXDF); + /* XXX: better put this into a macro */ + unsigned int bytes = (dmrxsf & HUB_DMRXSF_BYTE) >> 9; + int i; + + dev_dbg(unp->port.dev, "%s: DMRXSF = %lx, DMRXDF = %lx\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); + /* don't call tty_flip_buffer_push with low_latency set */ + BUG_ON(tty->low_latency); + 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; + unsigned long ifs; + int handled = 0; + + spin_lock(&unp->port.lock); + + ifs = ns921x_uart_read_ifs(unp); + dev_dbg(unp->port.dev, "%s: IFS=%lx\n", __func__, ifs); + + if (ifs & HUB_IFS_MODIP) { + unsigned long is; + unsigned long clear_is = 0; + + is = ns921x_uart_read_is(unp); + dev_dbg(unp->port.dev, "%s: IS=%lx\n", __func__, is); + + if (is & UIS_RX) { + ns921x_uart_rx_chars(unp, ifs, is); + + clear_is |= is & UIS_RX; + + handled = 1; + } + + if (is & UIS_MS) { + clear_is |= is & UIS_MS; + + handled = 1; + } + /* XXX: call this only after an MS irq? */ + ns921x_uart_check_msr(&unp->port); + + if (is & UART_IS_TXIDLE) { + BUG_ON(!(unp->flags & TXIRQPENDING)); + unp->flags &= ~TXIRQPENDING; + + ns921x_uart_tx_chars(unp, 1); + clear_is |= UART_IS_TXIDLE; + handled = 1; + } + + /* ack the handled irq */ + ns921x_uart_clear_is(unp, clear_is); + } + + spin_unlock(&unp->port.lock); + + if (!handled) + dev_dbg(unp->port.dev, "%s: unhandled\n", __func__); + return IRQ_RETVAL(handled); +} + +static int ns921x_uart_startup(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + int ret; + u32 ie; + + dev_dbg(port->dev, "%s\n", __func__); + + ret = clk_enable(unp->clk); + if (ret) + goto err_clkenable; + + ret = request_irq(unp->port.irq, ns921x_uart_int, 0, DRIVER_NAME, unp); + if (ret) { + + clk_disable(unp->clk); + +err_clkenable: + return ret; + } + + ie = readl(port->membase + UART_IE); + writel(ie | UIE_RX, port->membase + UART_IE); + + writel(UART_WC_RXEN | UART_WC_TXEN, port->membase + UART_WC); + + return 0; +} + +static void ns921x_uart_shutdown(struct uart_port *port) +{ + struct uart_ns921x_port *unp = up2unp(port); + + writel(0, port->membase + UART_IE); + writel(0, port->membase + 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; + unsigned long cval = 0; + + unsigned int baud, quot; + unsigned int 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; + } + + /* XXX: handle SPAR? */ + + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); + quot = uart_get_divisor(port, baud); + + spin_lock_irqsave(&port->lock, flags); + + 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; + } + + dev_dbg(port->dev, "read_status_mask = %x, ignore_status_mask = %x\n", + port->read_status_mask, port->ignore_status_mask); + + /* disable everything, prepare fifo flushing */ + saved_wc = readl(port->membase + UART_WC); + writel(UART_WC_RXFLUSH | UART_WC_TXFLUSH, port->membase + UART_WC); + + if (UART_ENABLE_MS(port, termios->c_cflag)) + ns921x_uart_enable_ms(port); + + /* set character gap period */ + writel(UART_CGAPCTRL_EN | (10 * (port->uartclk / baud) - 1), + port->membase + UART_CGAPCTRL); + + /* set buffer gap period */ + writel(UART_BGAPCTRL_EN | (640 * (port->uartclk / baud) - 1), + port->membase + UART_BGAPCTRL); + + /* prepare to access baud rate registers */ + writel(UART_LCR_DLAB, port->membase + UART_LCR); + + /* set baud rate */ + writel(quot & 0xff, port->membase + UART_BRDL); + writel((quot >> 8) & 0xff, port->membase + UART_BRDM); + + dev_dbg(port->dev, "%s: LCR <- %lx\n", __func__, cval); + writel(cval, port->membase + UART_LCR); + + /* flush fifos and restore state */ + writel(saved_wc, port->membase + UART_WC); + writel(UART_UIE_ETBEI, port->membase + 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(); + + writeb(ch, unp->port.membase + 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]; + unsigned long saved_ie = readl(unp->port.membase + UART_IE); + unsigned long saved_fcr = readl(unp->port.membase + UART_FCR); + unsigned long saved_wc = readl(unp->port.membase + UART_WC); + unsigned long saved_uie = readl(unp->port.membase + UART_UIE); + unsigned long new_wc; + int ret; + + BUG_ON(!irqs_disabled()); + + ret = clk_enable(unp->clk); + if (ret) + return; + + new_wc = saved_wc | UART_WC_RXEN | UART_WC_TXEN; + new_wc &= ~(UART_WC_RXFLUSH | UART_WC_TXFLUSH); + + writel(saved_ie & ~(UART_IE_TXIDLE | UIE_RX), + unp->port.membase + UART_IE); + writel(saved_fcr | UART_FCR_FIFOEN, unp->port.membase + UART_FCR); + writel(UART_UIE_ETBEI, unp->port.membase + UART_UIE); + writel(new_wc, unp->port.membase + 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(); + + /* wait for the transmitter to become empty */ + while (!(readl(unp->port.membase + UART_LSR) & UART_LSR_TEMT)) + barrier(); + + writel(saved_wc, unp->port.membase + UART_WC); + writel(saved_uie, unp->port.membase + UART_UIE); + writel(saved_fcr, unp->port.membase + UART_FCR); + writel(saved_ie, unp->port.membase + UART_IE); + + clk_disable(unp->clk); +} + +static void __init ns921x_uart_console_get_options(struct uart_ns921x_port *unp, + int *baud, int *bits, int *parity, int *flow) +{ + if (readl(unp->port.membase + UART_WC) & UART_WC_TXEN) { + unsigned long cval = readl(unp->port.membase + UART_LCR); + unsigned int quot = 0; + + writel(UART_LCR_DLAB, unp->port.membase + UART_LCR); + + quot = (readl(unp->port.membase + UART_BRDM) & 0xff) << 8; + quot |= readl(unp->port.membase + UART_BRDL) & 0xff; + + writel(cval, unp->port.membase + 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'; + } + + 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'; + + if (co->index >= NS921X_UART_NR) + co->index = 0; + unp = ns921x_uart_ports[co->index]; + if (!unp) + return -ENODEV; + + /* XXX: assert unp->clk is enabled */ + 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); + + return uart_set_options(&unp->port, co, baud, parity, bits, flow); +} + +static struct uart_driver ns921x_uart_reg; +static struct console ns921x_uart_console = { + .name = NS912X_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 = NS912X_TTY_NAME, + .major = NS912X_TTY_MAJOR, + .minor = NS912X_TTY_MINOR_START, + .nr = NS921X_UART_NR, + .cons = NS921X_UART_CONSOLE, +}; + +static int __init ns921x_uart_pdrv_probe(struct platform_device *pdev) +{ + int ret; + struct uart_ns921x_port *unp; + struct resource *mem; + void __iomem *base; + struct plat_ns921x_serial *pdata = pdev->dev.platform_data; + int line; + + if (!pdata) { + ret = -ENODEV; + goto err_pdata; + } + + line = pdata->line; + + if (ns921x_uart_ports[line] != NULL) { + ret = -EBUSY; + goto err_line; + } + + unp = kzalloc(sizeof(struct uart_ns921x_port), GFP_KERNEL); + ns921x_uart_ports[line] = unp; + + if (unp == NULL) { + ret = -ENOMEM; + goto err_alloc; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + ret = -ENODEV; + goto err_get_mem; + } + + base = ioremap(mem->start, 0x8000); + if (!base) { + ret = -ENOMEM; + goto err_ioremap; + } + dev_dbg(&pdev->dev, "base = %p\n", base); + + unp->clk = clk_get(&pdev->dev, "UART"); + if (IS_ERR(unp->clk)) { + ret = PTR_ERR(unp->clk); + dev_dbg(&pdev->dev, "clk ret = %d\n", ret); + goto err_clk_get; + } + + unp->port.dev = &pdev->dev; + unp->port.mapbase = mem->start; + unp->port.membase = base; + unp->port.iotype = UPIO_MEM; + unp->port.irq = pdata->irq; + unp->port.fifosize = 64; /* XXX */ + unp->port.ops = &ns921x_uart_pops; + unp->port.flags = UPF_BOOT_AUTOCONF; + unp->port.line = line; + + unp->gpio_request = pdata->gpio_request; + unp->gpio_configure = pdata->gpio_configure; + unp->gpio_free = pdata->gpio_free; + unp->gpio_data = pdata->gpio_data; + + ret = unp->gpio_request(unp->gpio_data); + if (ret) + goto err_gpio_request; + + unp->gpio_configure(unp->gpio_data); + + /* no DMA */ + writel(HUB_DMARXCTRL_DIRECT, unp->port.membase + HUB_DMARXCTRL); + writel(HUB_DMATXCTRL_DIRECT, unp->port.membase + HUB_DMATXCTRL); + + ret = uart_add_one_port(&ns921x_uart_reg, &unp->port); + if (ret) { + + unp->gpio_free(unp->gpio_data); +err_gpio_request: + + clk_put(unp->clk); +err_clk_get: + + iounmap(base); +err_ioremap: +err_get_mem: + + kfree(unp); + ns921x_uart_ports[line] = NULL; +err_alloc: + +err_line: +err_pdata: + + return ret; + } + platform_set_drvdata(pdev, unp); + + return 0; +} + +static 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; + + unp->gpio_free(unp->gpio_data); + clk_put(unp->clk); + iounmap(unp->port.membase); + kfree(unp); + ns921x_uart_ports[line] = NULL; + + return 0; +} + +static struct platform_driver ns921x_uart_pdrv = { + .remove = ns921x_uart_pdrv_remove, + /* .shutdown = ns921x_uart_pdrv_shutdown, */ + /* .suspend = ns921x_uart_pdrv_suspend, */ + /* .suspend_late = ns921x_uart_pdrv_suspend_late, */ + /* .resume_early = ns921x_uart_pdrv_resume_early, */ + /* .resume = ns921x_uart_pdrv_resume, */ + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ns921x_uart_init(void) +{ + int ret; + printk(KERN_INFO "Serial: Digi NS921x UART driver\n"); + + ret = uart_register_driver(&ns921x_uart_reg); + if (ret) + goto err_uart_register_driver; + + ret = platform_driver_probe(&ns921x_uart_pdrv, ns921x_uart_pdrv_probe); + + if (ret) { + + uart_unregister_driver(&ns921x_uart_reg); +err_uart_register_driver: + + return ret; + } + + return 0; +} + +static void __exit ns921x_uart_exit(void) +{ + platform_driver_unregister(&ns921x_uart_pdrv); + 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"); diff --git a/include/linux/ns921x-serial.h b/include/linux/ns921x-serial.h new file mode 100644 index 0000000..b6305cc --- /dev/null +++ b/include/linux/ns921x-serial.h @@ -0,0 +1,23 @@ +/* + * include/linux/ns921x-serial.h + * + * Copyright (C) 2007 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. + */ +#ifndef _LINUX_NS921X_SERIAL_H +#define _LINUX_NS921X_SERIAL_H + +struct plat_ns921x_serial { + unsigned int line; + unsigned int irq; + int (*gpio_request)(void *data); + void (*gpio_configure)(void *data); + void (*gpio_free)(void *data); + void *gpio_data; +}; + +#endif /* ifndef _LINUX_NS921X_SERIAL_H */ diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 6a5203f..bc73988 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -149,6 +149,8 @@ /* Freescale ColdFire */ #define PORT_MCF 78 +/* NetSilicon 921x */ +#define PORT_NS921X 79 #ifdef __KERNEL__ -- 1.5.3.4 - To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html