summaryrefslogtreecommitdiff
path: root/drivers/serial/ns9360-serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/serial/ns9360-serial.c')
-rw-r--r--drivers/serial/ns9360-serial.c878
1 files changed, 878 insertions, 0 deletions
diff --git a/drivers/serial/ns9360-serial.c b/drivers/serial/ns9360-serial.c
new file mode 100644
index 000000000000..ac37f6f5fcab
--- /dev/null
+++ b/drivers/serial/ns9360-serial.c
@@ -0,0 +1,878 @@
+/*
+ * drivers/serial/ns9360-serial.c
+ *
+ * Copyright (C) 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.
+ *
+ * Based on drivers/serial/ns9xxx_serial.c by Markus Pietrek
+ */
+
+#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>
+
+/* register offsets */
+
+#define UART_CTRLA 0x00
+#define UART_CTRLA_CE (1 << 31)
+#define UART_CTRLA_BRK (1 << 30)
+#define UART_CTRLA_EPS (1 << 28)
+#define UART_CTRLA_PE (1 << 27)
+#define UART_CTRLA_STOP (1 << 26)
+#define UART_CTRLA_WLS_5 (0)
+#define UART_CTRLA_WLS_6 (1 << 24)
+#define UART_CTRLA_WLS_7 (2 << 24)
+#define UART_CTRLA_WLS_8 (3 << 24)
+#define UART_CTRLA_DTR (1 << 17)
+#define UART_CTRLA_RTS (1 << 16)
+#define UART_CTRLA_RIE_RDY (1 << 11)
+#define UART_CTRLA_RIE_CLOSED (1 << 9)
+#define UART_CTRLA_RIE_MASK (0xe00)
+#define UART_CTRLA_RIC_MASK (0xe0)
+#define UART_CTRLA_TIC_HALF (1 << 2)
+#define UART_CTRLA_TIC_MASK (0x1e)
+
+#define UART_CTRLB 0x04
+#define UART_CTRLB_RCGT (1 << 26)
+
+#define UART_STATUSA 0x08
+#define UART_STATUSA_RXFDB_MASK (3 << 20)
+#define UART_STATUSA_DCD (1 << 19)
+#define UART_STATUSA_RI (1 << 18)
+#define UART_STATUSA_DSR (1 << 17)
+#define UART_STATUSA_CTS (1 << 16)
+#define UART_STATUSA_RBRK (1 << 15)
+#define UART_STATUSA_RFE (1 << 14)
+#define UART_STATUSA_RPE (1 << 13)
+#define UART_STATUSA_ROVER (1 << 12)
+#define UART_STATUSA_RRDY (1 << 11)
+#define UART_STATUSA_RBC (1 << 9)
+#define UART_STATUSA_TRDY (1 << 3)
+#define UART_STATUSA_TEMPTY (1 << 0)
+
+#define UART_FIFO 0x10
+
+#define UART_BITRATE 0x0c
+#define UART_BITRATE_EBIT (1 << 31)
+#define UART_BITRATE_TMODE (1 << 30)
+#define UART_BITRATE_CLKMUX_BCLK (1 << 24)
+#define UART_BITRATE_TCDR_16 (1 << 20)
+#define UART_BITRATE_RCDR_16 (1 << 18)
+#define UART_BITRATE_N_MASK (0x7fff)
+
+#define UART_RXCHARTIMER 0x18
+#define UART_RXCHARTIMER_TRUN (1 << 31)
+
+#if defined(CONFIG_SERIAL_NS9360_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#define SUPPORT_SYSRQ
+#endif
+
+#define DRIVER_NAME "ns9360-serial"
+
+#ifdef CONFIG_SERIAL_NS9360_COMPAT
+# define NS9360_TTY_NAME "ttyS"
+# define NS9360_TTY_MAJOR TTY_MAJOR
+# define NS9360_TTY_MINOR_START 64
+#else
+# define NS9360_TTY_NAME "ttyNS"
+# define NS9360_TTY_MAJOR 204
+# define NS9360_TTY_MINOR_START 200
+#endif
+
+#define NS9360_UART_NR 4
+
+#define up2unp(up) container_of(up, struct uart_ns9360_port, port)
+struct uart_ns9360_port {
+ struct uart_port port;
+ struct clk *clk;
+};
+
+#ifdef CONFIG_SERIAL_NS9750_CONSOLE
+
+#endif
+
+static inline u32 uartread32(struct uart_port *port, unsigned int offset)
+{
+ u32 ret = ioread32(port->membase + offset);
+
+#if defined(DEBUG_UARTRW)
+ dev_info(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_info(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_info(port->dev, "write 0x%p <- 0x%02x\n",
+ port->membase + offset, value);
+#endif
+ iowrite8(value, port->membase + offset);
+}
+
+static inline void ns9360_uart_wait_fifo_empty(struct uart_port *port)
+{
+ while (!(uartread32(port, UART_STATUSA) & UART_STATUSA_TEMPTY))
+ udelay(1);
+
+ /* TODO: until we have buffer closed again, do just a delay, enough
+ * for 38400 baud */
+ mdelay(1);
+}
+
+static inline void ns9360_uart_wait_xmitr(struct uart_port *port)
+{
+ while (!(uartread32(port, UART_STATUSA) & UART_STATUSA_TRDY))
+ udelay(1);
+}
+
+/* called with port->lock taken */
+static void ns9360_uart_stop_tx(struct uart_port *port)
+{
+ u32 ctrl;
+ struct uart_ns9360_port *unp = up2unp(port);
+
+ assert_spin_locked(&unp->port.lock);
+
+ /* disable TIC_HALF */
+ ctrl = uartread32(port, UART_CTRLA);
+ ctrl &= ~UART_CTRLA_TIC_HALF;
+ uartwrite32(port, ctrl, UART_CTRLA);
+}
+
+/* receive chars from serial fifo and store them in tty
+ * This is called with port->lock taken */
+static void ns9360_uart_rx_chars(struct uart_ns9360_port *unp)
+{
+ struct tty_struct *tty = unp->port.info->port.tty;
+ u32 status, available, characters, flag;
+
+ /* acknowledge rbc if set */
+ status = uartread32(&unp->port, UART_STATUSA);
+ if (status & UART_STATUSA_RBC) {
+ uartwrite32(&unp->port, UART_STATUSA_RBC, UART_STATUSA);
+ status = uartread32(&unp->port, UART_STATUSA);
+ }
+
+ while (status & UART_STATUSA_RRDY) {
+ available = (status & UART_STATUSA_RXFDB_MASK) >> 20;
+ available = available ? : 4;
+
+ flag = TTY_NORMAL;
+
+ if (status & UART_STATUSA_ROVER) {
+ unp->port.icount.overrun++;
+ flag |= TTY_OVERRUN;
+ }
+ if (status & UART_STATUSA_RFE) {
+ unp->port.icount.frame++;
+ flag |= TTY_FRAME;
+ }
+ if (status & UART_STATUSA_RPE) {
+ unp->port.icount.parity++;
+ flag |= TTY_PARITY;
+ }
+ if (status & UART_STATUSA_RBRK) {
+ unp->port.icount.brk++;
+ flag |= TTY_BREAK;
+ }
+
+ /* read characters from fifo */
+ characters = uartread32(&unp->port, UART_FIFO);
+ do {
+ unp->port.icount.rx++;
+ /* TODO: switch to uart_insert_char */
+ tty_insert_flip_char(tty, characters & 0xff, flag);
+ characters >>= 8;
+ } while (--available);
+
+ status = uartread32(&unp->port, UART_STATUSA);
+ }
+
+ if (!tty->low_latency)
+ tty_flip_buffer_push(tty);
+
+#ifdef SUPPORT_SYSRQ
+ sport->port.sysrq = 0;
+#endif
+}
+
+/* send out chars in xmit buffer. This is called with port->lock taken */
+static void ns9360_uart_tx_chars(struct uart_ns9360_port *unp)
+{
+ struct circ_buf *xmit = &unp->port.info->xmit;
+
+ assert_spin_locked(&unp->port.lock);
+
+ if (unp->port.x_char) {
+ uartwrite8(&unp->port, unp->port.x_char, UART_FIFO);
+ unp->port.icount.tx++;
+ unp->port.x_char = 0;
+ } else
+ if (!uart_circ_empty(xmit) && !uart_tx_stopped(&unp->port)) {
+ while (uartread32(&unp->port, UART_STATUSA) &
+ UART_STATUSA_TRDY) {
+ uartwrite8(&unp->port, xmit->buf[xmit->tail],
+ UART_FIFO);
+ xmit->tail = (xmit->tail + 1) &
+ (UART_XMIT_SIZE - 1);
+ unp->port.icount.tx++;
+ if (uart_circ_empty(xmit))
+ break;
+ }
+
+ /* wakup ourself if number of pending chars do not reach
+ * the minimum wakeup level */
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&unp->port);
+ }
+
+ /* if uart is empty now ? */
+ if (uart_circ_empty(xmit) || uart_tx_stopped(&unp->port))
+ ns9360_uart_stop_tx(&unp->port);
+}
+
+static irqreturn_t ns9360_uart_rxint(int irq, void *dev_id)
+{
+ struct uart_ns9360_port *unp = dev_id;
+
+ spin_lock(&unp->port.lock);
+ ns9360_uart_rx_chars(unp);
+ spin_unlock(&unp->port.lock);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ns9360_uart_txint(int irq, void *dev_id)
+{
+ struct uart_ns9360_port *unp = dev_id;
+
+ spin_lock(&unp->port.lock);
+ ns9360_uart_tx_chars(unp);
+ spin_unlock(&unp->port.lock);
+
+ return IRQ_HANDLED;
+}
+
+static void ns9360_uart_release_port(struct uart_port *port)
+{
+ /* XXX: release_mem_region is marked as Compatibility cruft ??? */
+ release_mem_region(port->mapbase, 0x3f);
+}
+
+static int ns9360_uart_request_port(struct uart_port *port)
+{
+ return request_mem_region(port->mapbase, 0x3f,
+ DRIVER_NAME) ? 0 : -EBUSY;
+}
+
+static void ns9360_uart_enable_ms(struct uart_port *port)
+{
+ u32 status;
+
+ status = uartread32(port, UART_STATUSA);
+ /* status |= NS_SER_CTRL_A_RIC_MA; */
+ /* FIXME: sense? */
+ uartwrite32(port, status, UART_STATUSA);
+}
+
+static void ns9360_uart_shutdown(struct uart_port *port)
+{
+ struct uart_ns9360_port *unp = up2unp(port);
+ unsigned long flags;
+ u32 ctrl;
+
+ ns9360_uart_wait_fifo_empty(port);
+
+ spin_lock_irqsave(&port->lock, flags);
+ ctrl = uartread32(port, UART_CTRLA);
+ ctrl &= ~UART_CTRLA_RIE_RDY;
+ uartwrite32(port, ctrl, UART_CTRLA);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ free_irq(port->irq, unp);
+ free_irq(port->irq + 1, unp);
+
+ clk_disable(unp->clk);
+}
+
+static int ns9360_uart_startup(struct uart_port *port)
+{
+ struct uart_ns9360_port *unp = up2unp(port);
+ int ret;
+ u32 ctrl;
+ unsigned long flags;
+
+ ret = clk_enable(unp->clk);
+ if (ret) {
+ dev_info(port->dev, "%s: err_clk_enable", __func__);
+ goto err_clk_enable;
+ }
+
+ unp->port.uartclk = clk_get_rate(unp->clk);
+
+ ret = request_irq(unp->port.irq, ns9360_uart_rxint, 0,
+ DRIVER_NAME, unp);
+ if (ret) {
+ dev_info(port->dev, "%s: err_request_irq_rx", __func__);
+ goto err_request_irq_rx;
+ }
+
+ ret = request_irq(unp->port.irq + 1, ns9360_uart_txint, 0,
+ DRIVER_NAME, unp);
+ if (ret) {
+ dev_info(port->dev, "%s: err_request_irq_tx", __func__);
+ goto err_request_irq_tx;
+ }
+
+ /* enable receive interrupts */
+ spin_lock_irqsave (&unp->port.lock, flags);
+ ctrl = uartread32(&unp->port, UART_CTRLA);
+ ctrl |= UART_CTRLA_CE | UART_CTRLA_RIE_RDY | UART_CTRLA_RIE_CLOSED;
+ uartwrite32(&unp->port, ctrl, UART_CTRLA);
+
+ /* enable modem status interrupts */
+ ns9360_uart_enable_ms(&unp->port);
+ spin_unlock_irqrestore (&unp->port.lock, flags);
+
+ /* enable character gap timer */
+ uartwrite32(&unp->port, UART_CTRLB_RCGT, UART_CTRLB);
+
+ return 0;
+
+err_request_irq_tx:
+ free_irq(unp->port.irq, unp);
+err_request_irq_rx:
+ clk_disable(unp->clk);
+err_clk_enable:
+ return ret;
+}
+
+static void ns9360_uart_set_termios(struct uart_port *port,
+ struct ktermios *termios, struct ktermios *old)
+{
+ struct uart_ns9360_port *unp = up2unp(port);
+ unsigned long flags;
+ unsigned int baud, quot, nr_bits;
+ u32 ctrl, bitrate, gap_timer;
+
+ 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);
+
+ /* read out configuration and mask out bits going to be updated */
+ ctrl = uartread32(port, UART_CTRLA);
+ ctrl &= ~(UART_CTRLA_WLS_8 | UART_CTRLA_STOP | UART_CTRLA_PE |
+ UART_CTRLA_EPS);
+
+ ctrl |= UART_CTRLA_CE;
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ ctrl |= UART_CTRLA_WLS_5;
+ nr_bits = 5;
+ break;
+ case CS6:
+ ctrl |= UART_CTRLA_WLS_6;
+ nr_bits = 6;
+ break;
+ case CS7:
+ ctrl |= UART_CTRLA_WLS_7;
+ nr_bits = 7;
+ break;
+ default:
+ case CS8:
+ ctrl |= UART_CTRLA_WLS_8;
+ nr_bits = 8;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB) {
+ ctrl |= UART_CTRLA_STOP;
+ nr_bits++;
+ }
+
+ if (termios->c_cflag & PARENB) {
+ ctrl |= UART_CTRLA_PE;
+ nr_bits++;
+ }
+
+ if (!(termios->c_cflag & PARODD)) {
+ ctrl |= UART_CTRLA_EPS;
+ nr_bits++;
+ }
+
+ /* set configuration */
+ ns9360_uart_wait_fifo_empty(port);
+ uartwrite32(port, ctrl, UART_CTRLA);
+
+ /* set baudrate */
+ bitrate = UART_BITRATE_EBIT | UART_BITRATE_CLKMUX_BCLK |
+ UART_BITRATE_TMODE | UART_BITRATE_TCDR_16 |
+ UART_BITRATE_RCDR_16 | ((quot - 1) & UART_BITRATE_N_MASK);
+ uartwrite32(port, bitrate, UART_BITRATE);
+
+ /* set character gap timer */
+ gap_timer = UART_RXCHARTIMER_TRUN;
+
+ /* calculate gap timer */
+ gap_timer |= (clk_get_rate(unp->clk) * nr_bits / baud / 8) - 1;
+ pr_debug(DRIVER_NAME " %s: gap-timer = %08x (baud=%i, clk=%li)\n",
+ __func__, gap_timer, baud, clk_get_rate(unp->clk));
+
+ uartwrite32(port, gap_timer, UART_RXCHARTIMER);
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static unsigned int ns9360_uart_tx_empty(struct uart_port *port)
+{
+ u32 status;
+
+ status = uartread32(port, UART_STATUSA);
+ return (status & UART_STATUSA_TRDY) ? TIOCSER_TEMT : 0;
+}
+
+static void ns9360_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ u32 mcr;
+
+ mcr = uartread32(port, UART_CTRLA);
+
+ if (mctrl & TIOCM_RTS)
+ mcr &= ~UART_CTRLA_RTS;
+ else
+ mcr |= UART_CTRLA_RTS;
+
+ if (mctrl & TIOCM_DTR)
+ mcr &= ~UART_CTRLA_DTR;
+ else
+ mcr |= UART_CTRLA_DTR;
+
+ uartwrite32(port, mcr, UART_CTRLA);
+}
+
+static unsigned int ns9360_uart_get_mctrl(struct uart_port *port)
+{
+ unsigned int status;
+ unsigned int ret = 0;
+
+ status = uartread32(port, UART_STATUSA);
+
+ if (!(status & UART_STATUSA_DCD))
+ ret |= TIOCM_CD;
+ if (!(status & UART_STATUSA_CTS))
+ ret |= TIOCM_CTS;
+ if (!(status & UART_STATUSA_DSR))
+ ret |= TIOCM_DSR;
+ if (!(status & UART_STATUSA_RI))
+ ret |= TIOCM_RI;
+
+ return ret;
+}
+
+static int ns9360_uart_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ struct uart_ns9360_port *unp = up2unp(port);
+ int ret = 0;
+
+ if (ser->type != PORT_UNKNOWN && ser->type != PORT_NS9360)
+ ret = -EINVAL;
+
+ if (ser->irq != unp->port.irq)
+ ret = -EINVAL;
+
+ if (ser->io_type != UPIO_MEM)
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static void ns9360_uart_start_tx(struct uart_port *port)
+{
+ u32 ctrl;
+
+ ctrl = uartread32(port, UART_CTRLA);
+ ctrl |= UART_CTRLA_TIC_HALF;
+ uartwrite32(port, ctrl, UART_CTRLA);
+}
+
+static void ns9360_uart_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE)
+ if (!ns9360_uart_request_port(port))
+ port->type = PORT_NS9360;
+}
+
+static const char *ns9360_uart_type(struct uart_port *port)
+{
+ return port->type == PORT_NS9360 ? "NS9360" : NULL;
+}
+
+static void ns9360_uart_break_ctl(struct uart_port *port, int break_state)
+{
+ u32 ctrl;
+
+ ctrl = uartread32(port, UART_CTRLA);
+
+ if (break_state == -1)
+ ctrl |= UART_CTRLA_BRK;
+ else
+ ctrl &= ~UART_CTRLA_BRK;
+
+ uartwrite32(port, ctrl, UART_CTRLA);
+}
+
+static void ns9360_uart_stop_rx(struct uart_port *port)
+{
+ u32 ctrl;
+
+ ctrl = uartread32(port, UART_CTRLA);
+ ctrl &= ~(UART_CTRLA_RIE_RDY | UART_CTRLA_RIE_CLOSED);
+ uartwrite32(port, ctrl, UART_CTRLA);
+}
+
+static struct uart_ops ns9360_uart_pops = {
+ .stop_rx = ns9360_uart_stop_rx,
+ .enable_ms = ns9360_uart_enable_ms,
+ .break_ctl = ns9360_uart_break_ctl,
+ .type = ns9360_uart_type,
+ .config_port = ns9360_uart_config_port,
+ .start_tx = ns9360_uart_start_tx,
+ .verify_port = ns9360_uart_verify_port,
+ .set_mctrl = ns9360_uart_set_mctrl,
+ .get_mctrl = ns9360_uart_get_mctrl,
+ .tx_empty = ns9360_uart_tx_empty,
+ .set_termios = ns9360_uart_set_termios,
+ .shutdown = ns9360_uart_shutdown,
+ .startup = ns9360_uart_startup,
+ .stop_tx = ns9360_uart_stop_tx,
+ .release_port = ns9360_uart_release_port,
+ .request_port = ns9360_uart_request_port,
+};
+
+static struct uart_ns9360_port *ns9360_uart_ports[NS9360_UART_NR];
+
+#ifdef CONFIG_SERIAL_NS9360_CONSOLE
+
+static void ns9360_uart_console_write(struct console *co,
+ const char *s, unsigned int count)
+{
+ struct uart_ns9360_port *unp = ns9360_uart_ports[co->index];
+ u32 ctrl, saved_ctrl;
+
+ /* save current state */
+ saved_ctrl = uartread32(&unp->port, UART_CTRLA);
+
+ /* keep configuration, disable interrupts and enable uart */
+ ctrl = (saved_ctrl | UART_CTRLA_CE) & ~(UART_CTRLA_TIC_MASK |
+ UART_CTRLA_RIC_MASK | UART_CTRLA_RIE_MASK);
+ uartwrite32(&unp->port, ctrl, UART_CTRLA);
+
+ while (count) {
+ ns9360_uart_wait_xmitr(&unp->port);
+
+ if (*s == '\n') {
+ uartwrite8(&unp->port, '\r', UART_FIFO);
+ ns9360_uart_wait_xmitr(&unp->port);
+ }
+
+ uartwrite8(&unp->port, *s, UART_FIFO);
+
+ count--;
+ s++;
+ }
+
+ /* wait for fifo empty if uart was (and will be) disabled */
+ if (!(saved_ctrl & UART_CTRLA_CE))
+ ns9360_uart_wait_fifo_empty(&unp->port);
+
+ /* recover state */
+ uartwrite32(&unp->port, saved_ctrl, UART_CTRLA);
+}
+
+/* If the port was already initialised (eg, by a boot loader),
+ * try to determine the current setup. */
+static void __init ns9360_uart_console_get_options(struct uart_ns9360_port *unp,
+ int *baud, int *parity, int *bits, int *flow)
+{
+ u32 ctrl, bitrate;
+
+ ctrl = uartread32(&unp->port, UART_CTRLA);
+ bitrate = uartread32(&unp->port, UART_BITRATE);
+
+ /* not enabled, don't modify defaults */
+ if (!(ctrl & UART_CTRLA_CE))
+ return;
+
+ switch (ctrl & UART_CTRLA_WLS_8) {
+ case UART_CTRLA_WLS_5:
+ *bits = 5;
+ break;
+ case UART_CTRLA_WLS_6:
+ *bits = 6;
+ break;
+ case UART_CTRLA_WLS_7:
+ *bits = 7;
+ break;
+ default:
+ case UART_CTRLA_WLS_8:
+ *bits = 8;
+ break;
+ }
+
+ *parity = 'n';
+ if (ctrl & UART_CTRLA_PE) {
+ /* parity enabled */
+ if (ctrl & UART_CTRLA_EPS)
+ *parity = 'o';
+ else
+ *parity = 'e';
+ }
+
+ *baud = unp->port.uartclk /
+ (((bitrate & UART_BITRATE_N_MASK) + 1) * 16);
+}
+
+static int __init ns9360_uart_console_setup(struct console *co, char *options)
+{
+ struct uart_ns9360_port *unp;
+ int baud = 38400;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ if (co->index >= NS9360_UART_NR)
+ co->index = 0;
+
+ unp = ns9360_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
+ ns9360_uart_console_get_options(unp,
+ &baud, &parity, &bits, &flow);
+
+ /* Enable UART. For some strange reason, in NS9360 the UART
+ * must be enabled in the setup, rather than in the write
+ * function, otherwise nothing is printed and the system
+ * doesn't boot */
+ uartwrite32(&unp->port,
+ uartread32(&unp->port, UART_CTRLA) | UART_CTRLA_CE,
+ UART_CTRLA);
+
+ return uart_set_options(&unp->port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver ns9360_uart_reg;
+static struct console ns9360_uart_console = {
+ .name = NS9360_TTY_NAME,
+ .write = ns9360_uart_console_write,
+ .device = uart_console_device,
+ .setup = ns9360_uart_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &ns9360_uart_reg,
+};
+
+#define NS9360_UART_CONSOLE (&ns9360_uart_console)
+#else
+#define NS9360_UART_CONSOLE NULL
+#endif
+
+static struct uart_driver ns9360_uart_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = DRIVER_NAME,
+ .dev_name = NS9360_TTY_NAME,
+ .major = NS9360_TTY_MAJOR,
+ .minor = NS9360_TTY_MINOR_START,
+ .nr = NS9360_UART_NR,
+ .cons = NS9360_UART_CONSOLE,
+};
+
+static __devinit int ns9360_uart_pdrv_probe(struct platform_device *pdev)
+{
+ int ret, line, irq;
+ struct uart_ns9360_port *unp;
+ struct resource *mem;
+ void __iomem *base;
+
+ line = pdev->id;
+ if (line < 0 || line >= ARRAY_SIZE(ns9360_uart_ports)) {
+ ret = -ENODEV;
+ dev_info(&pdev->dev, "%s: err_line\n", __func__);
+ goto err_line;
+ }
+
+ if (ns9360_uart_ports[line] != NULL) {
+ ret = -EBUSY;
+ dev_info(&pdev->dev, "%s: err_line\n", __func__);
+ goto err_line;
+ }
+
+ unp = kzalloc(sizeof(struct uart_ns9360_port), GFP_KERNEL);
+ ns9360_uart_ports[line] = unp;
+
+ if (unp == NULL) {
+ ret = -ENOMEM;
+ dev_info(&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_info(&pdev->dev, "%s: err_get_mem\n", __func__);
+ goto err_get_mem;
+ }
+
+ base = ioremap(mem->start, 0x3f);
+ if (!base) {
+ ret = -ENOMEM;
+ dev_info(&pdev->dev, "%s: err_ioremap\n", __func__);
+ goto err_ioremap;
+ }
+ dev_info(&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_info(&pdev->dev, "%s: err_clk_get -> %d\n", __func__, 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 = irq;
+ unp->port.fifosize = 64; /* XXX */
+ unp->port.ops = &ns9360_uart_pops;
+ unp->port.flags = UPF_BOOT_AUTOCONF;
+ unp->port.line = line;
+
+ ret = uart_add_one_port(&ns9360_uart_reg, &unp->port);
+ if (ret) {
+ dev_info(&pdev->dev, "%s: err_uart_add1port -> %d\n",
+ __func__, ret);
+ goto err_uart_add1port;
+ }
+
+ return 0;
+
+err_uart_add1port:
+ clk_put(unp->clk);
+err_clk_get:
+ iounmap(base);
+err_ioremap:
+ release_resource(mem);
+err_get_mem:
+err_get_irq:
+ kfree(unp);
+ ns9360_uart_ports[line] = NULL;
+err_alloc:
+err_line:
+ return ret;
+}
+
+static __devexit int ns9360_uart_pdrv_remove(struct platform_device *pdev)
+{
+ int line;
+ struct uart_ns9360_port *unp = platform_get_drvdata(pdev);
+
+ line = unp->port.line;
+
+ uart_remove_one_port(&ns9360_uart_reg, &unp->port);
+ clk_put(unp->clk);
+ iounmap(unp->port.membase);
+ kfree(unp);
+ ns9360_uart_ports[line] = NULL;
+
+ return 0;
+}
+
+static struct platform_driver ns9360_uart_pdriver = {
+ .probe = ns9360_uart_pdrv_probe,
+ .remove = __devexit_p(ns9360_uart_pdrv_remove),
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ns9360_uart_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&ns9360_uart_reg);
+ if (ret) {
+ pr_debug("%s: unable to register uart driver\n", __func__);
+ goto err_uart_register_driver;
+ }
+
+ ret = platform_driver_register(&ns9360_uart_pdriver);
+ if (ret) {
+ pr_debug("%s: unable to register platform driver\n", __func__);
+ goto err_platform_driver_register;
+ }
+
+ pr_info("Digi NS9360 UART driver\n");
+
+ return 0;
+
+err_platform_driver_register:
+ uart_unregister_driver(&ns9360_uart_reg);
+err_uart_register_driver:
+ return ret;
+}
+
+static void __exit ns9360_uart_exit(void)
+{
+ platform_driver_unregister(&ns9360_uart_pdriver);
+ uart_unregister_driver(&ns9360_uart_reg);
+}
+
+module_init(ns9360_uart_init);
+module_exit(ns9360_uart_exit);
+
+MODULE_DESCRIPTION("Digi NS9360 UART driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);