summaryrefslogtreecommitdiffstats
path: root/target/linux/adm5120/files/drivers/serial/adm5120_uart.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/adm5120/files/drivers/serial/adm5120_uart.c')
-rw-r--r--target/linux/adm5120/files/drivers/serial/adm5120_uart.c520
1 files changed, 520 insertions, 0 deletions
diff --git a/target/linux/adm5120/files/drivers/serial/adm5120_uart.c b/target/linux/adm5120/files/drivers/serial/adm5120_uart.c
new file mode 100644
index 000000000..83c5f7201
--- /dev/null
+++ b/target/linux/adm5120/files/drivers/serial/adm5120_uart.c
@@ -0,0 +1,520 @@
+/*
+ * Serial driver for ADM5120 SoC
+ *
+ * Derived from drivers/serial/uart00.c
+ * Copyright 2001 Altera Corporation
+ *
+ * Some pieces are derived from the ADMtek 2.4 serial driver.
+ * Copyright (C) ADMtek Incorporated, 2003
+ * daniell@admtek.com.tw
+ * Which again was derived from drivers/char/serial.c
+ * Copyright (C) Linus Torvalds et al.
+ *
+ * Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2005
+ */
+
+#include <linux/autoconf.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/console.h>
+
+#include <asm/mach-adm5120/adm5120_defs.h>
+#include <asm/mach-adm5120/adm5120_irq.h>
+
+#define ADM5120_UART_REG(base, reg) \
+ (*(volatile u32 *)KSEG1ADDR((base)+(reg)))
+
+#define ADM5120_UARTCLK_FREQ 62500000
+#define ADM5120_UART_BAUDDIV(rate) ((unsigned long)(ADM5120_UARTCLK_FREQ/(16*(rate)) - 1))
+
+#define ADM5120_UART_BAUD115200 ADM5120_UART_BAUDDIV(115200)
+
+#define ADM5120_UART_DATA 0x00
+#define ADM5120_UART_RS 0x04
+#define ADM5120_UART_LCR_H 0x08
+#define ADM5120_UART_LCR_M 0x0c
+#define ADM5120_UART_LCR_L 0x10
+#define ADM5120_UART_CR 0x14
+#define ADM5120_UART_FR 0x18
+#define ADM5120_UART_IR 0x1c
+
+#define ADM5120_UART_FE 0x01
+#define ADM5120_UART_PE 0x02
+#define ADM5120_UART_BE 0x04
+#define ADM5120_UART_OE 0x08
+#define ADM5120_UART_ERR 0x0f
+#define ADM5120_UART_FIFO_EN 0x10
+#define ADM5120_UART_EN 0x01
+#define ADM5120_UART_TIE 0x20
+#define ADM5120_UART_RIE 0x50
+#define ADM5120_UART_IE 0x78
+#define ADM5120_UART_CTS 0x01
+#define ADM5120_UART_DSR 0x02
+#define ADM5120_UART_DCD 0x04
+#define ADM5120_UART_TXFF 0x20
+#define ADM5120_UART_TXFE 0x80
+#define ADM5120_UART_RXFE 0x10
+#define ADM5120_UART_BRK 0x01
+#define ADM5120_UART_PEN 0x02
+#define ADM5120_UART_EPS 0x04
+#define ADM5120_UART_STP2 0x08
+#define ADM5120_UART_W5 0x00
+#define ADM5120_UART_W6 0x20
+#define ADM5120_UART_W7 0x40
+#define ADM5120_UART_W8 0x60
+#define ADM5120_UART_MIS 0x01
+#define ADM5120_UART_RIS 0x02
+#define ADM5120_UART_TIS 0x04
+#define ADM5120_UART_RTIS 0x08
+
+static void adm5120ser_stop_tx(struct uart_port *port)
+{
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) &= ~ADM5120_UART_TIE;
+}
+
+static void adm5120ser_irq_rx(struct uart_port *port)
+{
+ struct tty_struct *tty = port->info->tty;
+ unsigned int status, ch, rds, flg, ignored = 0;
+
+ status = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR);
+ while (!(status & ADM5120_UART_RXFE)) {
+ /*
+ * We need to read rds before reading the
+ * character from the fifo
+ */
+ rds = ADM5120_UART_REG(port->iobase, ADM5120_UART_RS);
+ ch = ADM5120_UART_REG(port->iobase, ADM5120_UART_DATA);
+ port->icount.rx++;
+
+ if (tty->low_latency)
+ tty_flip_buffer_push(tty);
+
+ flg = TTY_NORMAL;
+
+ /*
+ * Note that the error handling code is
+ * out of the main execution path
+ */
+ if (rds & ADM5120_UART_ERR)
+ goto handle_error;
+ if (uart_handle_sysrq_char(port, ch))
+ goto ignore_char;
+
+ error_return:
+ tty_insert_flip_char(tty, ch, flg);
+
+ ignore_char:
+ status = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR);
+ }
+ out:
+ tty_flip_buffer_push(tty);
+ return;
+
+ handle_error:
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_RS) = 0xff;
+ if (rds & ADM5120_UART_BE) {
+ port->icount.brk++;
+ if (uart_handle_break(port))
+ goto ignore_char;
+ } else if (rds & ADM5120_UART_PE)
+ port->icount.parity++;
+ else if (rds & ADM5120_UART_FE)
+ port->icount.frame++;
+ if (rds & ADM5120_UART_OE)
+ port->icount.overrun++;
+
+ if (rds & port->ignore_status_mask) {
+ if (++ignored > 100)
+ goto out;
+ goto ignore_char;
+ }
+ rds &= port->read_status_mask;
+
+ if (rds & ADM5120_UART_BE)
+ flg = TTY_BREAK;
+ else if (rds & ADM5120_UART_PE)
+ flg = TTY_PARITY;
+ else if (rds & ADM5120_UART_FE)
+ flg = TTY_FRAME;
+
+ if (rds & ADM5120_UART_OE) {
+ /*
+ * CHECK: does overrun affect the current character?
+ * ASSUMPTION: it does not.
+ */
+ tty_insert_flip_char(tty, ch, flg);
+ ch = 0;
+ flg = TTY_OVERRUN;
+ }
+#ifdef CONFIG_MAGIC_SYSRQ
+ port->sysrq = 0;
+#endif
+ goto error_return;
+}
+
+static void adm5120ser_irq_tx(struct uart_port *port)
+{
+ struct circ_buf *xmit = &port->info->xmit;
+ int count;
+
+ if (port->x_char) {
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_DATA) =
+ port->x_char;
+ port->icount.tx++;
+ port->x_char = 0;
+ return;
+ }
+ if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
+ adm5120ser_stop_tx(port);
+ return;
+ }
+
+ count = port->fifosize >> 1;
+ do {
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_DATA) =
+ xmit->buf[xmit->tail];
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ port->icount.tx++;
+ if (uart_circ_empty(xmit))
+ break;
+ } while (--count > 0);
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+
+ if (uart_circ_empty(xmit))
+ adm5120ser_stop_tx(port);
+}
+
+static void adm5120ser_irq_modem(struct uart_port *port)
+{
+ unsigned int status;
+
+ status = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR);
+
+ if (status & ADM5120_UART_DCD)
+ uart_handle_dcd_change(port, status & ADM5120_UART_DCD);
+
+ if (status & ADM5120_UART_DSR)
+ port->icount.dsr++;
+
+ if (status & ADM5120_UART_CTS)
+ uart_handle_cts_change(port, status & ADM5120_UART_CTS);
+
+ wake_up_interruptible(&port->info->delta_msr_wait);
+}
+
+static irqreturn_t adm5120ser_irq(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+ unsigned long ir = ADM5120_UART_REG(port->iobase, ADM5120_UART_IR);
+
+ if (ir & (ADM5120_UART_RIS | ADM5120_UART_RTIS))
+ adm5120ser_irq_rx(port);
+ if (ir & ADM5120_UART_TIS)
+ adm5120ser_irq_tx(port);
+ if (ir & ADM5120_UART_MIS) {
+ adm5120ser_irq_modem(port);
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_IR) = 0xff;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static unsigned int adm5120ser_tx_empty(struct uart_port *port)
+{
+ unsigned int fr = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR);
+ return (fr & ADM5120_UART_TXFE) ? TIOCSER_TEMT : 0;
+}
+
+static void adm5120ser_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+}
+
+static unsigned int adm5120ser_get_mctrl(struct uart_port *port)
+{
+ unsigned int result = 0;
+ unsigned int fr = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR);
+
+ if (fr & ADM5120_UART_CTS)
+ result |= TIOCM_CTS;
+ if (fr & ADM5120_UART_DSR)
+ result |= TIOCM_DSR;
+ if (fr & ADM5120_UART_DCD)
+ result |= TIOCM_CAR;
+ return result;
+}
+
+static void adm5120ser_start_tx(struct uart_port *port)
+{
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) |= ADM5120_UART_TIE;
+}
+
+static void adm5120ser_stop_rx(struct uart_port *port)
+{
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) &= ~ADM5120_UART_RIE;
+}
+
+static void adm5120ser_enable_ms(struct uart_port *port)
+{
+}
+
+static void adm5120ser_break_ctl(struct uart_port *port, int break_state)
+{
+ unsigned long flags;
+ unsigned long lcrh;
+
+ spin_lock_irqsave(&port->lock, flags);
+ lcrh = ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H);
+ if (break_state == -1)
+ lcrh |= ADM5120_UART_BRK;
+ else
+ lcrh &= ~ADM5120_UART_BRK;
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H) = lcrh;
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static int adm5120ser_startup(struct uart_port *port)
+{
+ int ret;
+
+ ret = request_irq(port->irq, adm5120ser_irq, 0, "ADM5120 UART", port);
+ if (ret) {
+ printk(KERN_ERR "Couldn't get irq %d\n", port->irq);
+ return ret;
+ }
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H) |=
+ ADM5120_UART_FIFO_EN;
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) |=
+ ADM5120_UART_EN | ADM5120_UART_IE;
+ return 0;
+}
+
+static void adm5120ser_shutdown(struct uart_port *port)
+{
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) &= ~ADM5120_UART_IE;
+ free_irq(port->irq, port);
+}
+
+static void adm5120ser_set_termios(struct uart_port *port,
+ struct ktermios *termios, struct ktermios *old)
+{
+ unsigned int baud, quot, lcrh;
+ unsigned long flags;
+
+ termios->c_cflag |= CREAD;
+
+ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+ quot = uart_get_divisor(port, baud);
+
+ lcrh = ADM5120_UART_FIFO_EN;
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ lcrh |= ADM5120_UART_W5;
+ break;
+ case CS6:
+ lcrh |= ADM5120_UART_W6;
+ break;
+ case CS7:
+ lcrh |= ADM5120_UART_W7;
+ break;
+ default:
+ lcrh |= ADM5120_UART_W8;
+ break;
+ }
+ if (termios->c_cflag & CSTOPB)
+ lcrh |= ADM5120_UART_STP2;
+ if (termios->c_cflag & PARENB) {
+ lcrh |= ADM5120_UART_PEN;
+ if (!(termios->c_cflag & PARODD))
+ lcrh |= ADM5120_UART_EPS;
+ }
+
+ spin_lock_irqsave(port->lock, flags);
+
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H) = lcrh;
+
+ /*
+ * Update the per-port timeout.
+ */
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ port->read_status_mask = ADM5120_UART_OE;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= ADM5120_UART_FE | ADM5120_UART_PE;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ port->read_status_mask |= ADM5120_UART_BE;
+
+ /*
+ * Characters to ignore
+ */
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= ADM5120_UART_FE | ADM5120_UART_PE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= ADM5120_UART_BE;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns to (for real raw support).
+ */
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= ADM5120_UART_OE;
+ }
+
+ quot = ADM5120_UART_BAUD115200;
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_L) = quot & 0xff;
+ ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_M) = quot >> 8;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static const char *adm5120ser_type(struct uart_port *port)
+{
+ return port->type == PORT_ADM5120 ? "ADM5120" : NULL;
+}
+
+static void adm5120ser_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE)
+ port->type = PORT_ADM5120;
+}
+
+static void adm5120ser_release_port(struct uart_port *port)
+{
+ release_mem_region(port->iobase, ADM5120_UART_SIZE);
+}
+
+static int adm5120ser_request_port(struct uart_port *port)
+{
+ return request_mem_region(port->iobase, ADM5120_UART_SIZE,
+ "adm5120-uart") != NULL ? 0 : -EBUSY;
+}
+
+static struct uart_ops adm5120ser_ops = {
+ .tx_empty = adm5120ser_tx_empty,
+ .set_mctrl = adm5120ser_set_mctrl,
+ .get_mctrl = adm5120ser_get_mctrl,
+ .stop_tx = adm5120ser_stop_tx,
+ .start_tx = adm5120ser_start_tx,
+ .stop_rx = adm5120ser_stop_rx,
+ .enable_ms = adm5120ser_enable_ms,
+ .break_ctl = adm5120ser_break_ctl,
+ .startup = adm5120ser_startup,
+ .shutdown = adm5120ser_shutdown,
+ .set_termios = adm5120ser_set_termios,
+ .type = adm5120ser_type,
+ .config_port = adm5120ser_config_port,
+ .release_port = adm5120ser_release_port,
+ .request_port = adm5120ser_request_port,
+};
+
+static void adm5120console_put(const char c)
+{
+ while ((ADM5120_UART_REG(ADM5120_UART0_BASE, ADM5120_UART_FR) &
+ ADM5120_UART_TXFF) != 0);
+ ADM5120_UART_REG(ADM5120_UART0_BASE, ADM5120_UART_DATA) = c;
+}
+
+static void adm5120console_write(struct console *con, const char *s,
+ unsigned int count)
+{
+ while (count--) {
+ if (*s == '\n')
+ adm5120console_put('\r');
+ adm5120console_put(*s);
+ s++;
+ }
+}
+
+static int adm5120console_setup(struct console *con, char *options)
+{
+ /* Set to 115200 baud, 8N1 and enable FIFO */
+ ADM5120_UART_REG(ADM5120_UART0_BASE, ADM5120_UART_LCR_L) =
+ ADM5120_UART_BAUD115200 & 0xff;
+ ADM5120_UART_REG(ADM5120_UART0_BASE, ADM5120_UART_LCR_M) =
+ ADM5120_UART_BAUD115200 >> 8;
+ ADM5120_UART_REG(ADM5120_UART0_BASE, ADM5120_UART_LCR_H) =
+ ADM5120_UART_W8 | ADM5120_UART_FIFO_EN;
+ /* Enable port */
+ ADM5120_UART_REG(ADM5120_UART0_BASE, ADM5120_UART_CR) =
+ ADM5120_UART_EN;
+
+ return 0;
+}
+
+static struct uart_driver adm5120ser_reg;
+
+static struct console adm5120_serconsole = {
+ .name = "ttyS",
+ .write = adm5120console_write,
+ .device = uart_console_device,
+ .setup = adm5120console_setup,
+ .flags = CON_PRINTBUFFER,
+ .cflag = B115200 | CS8 | CREAD,
+ .index = 0,
+ .data = &adm5120ser_reg,
+};
+
+static int __init adm5120console_init(void)
+{
+ register_console(&adm5120_serconsole);
+ return 0;
+}
+
+console_initcall(adm5120console_init);
+
+
+static struct uart_port adm5120ser_ports[] = {
+ {
+ .iobase = ADM5120_UART0_BASE,
+ .irq = ADM5120_IRQ_UART0,
+ .uartclk = ADM5120_UARTCLK_FREQ,
+ .fifosize = 16,
+ .ops = &adm5120ser_ops,
+ .line = 0,
+ .flags = ASYNC_BOOT_AUTOCONF,
+ },
+#if (CONFIG_ADM5120_NR_UARTS > 1)
+ {
+ .iobase = ADM5120_UART1_BASE,
+ .irq = ADM5120_IRQ_UART1,
+ .uartclk = ADM5120_UARTCLK_FREQ,
+ .fifosize = 16,
+ .ops = &adm5120ser_ops,
+ .line = 1,
+ .flags = ASYNC_BOOT_AUTOCONF,
+ },
+#endif
+};
+
+static struct uart_driver adm5120ser_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = "ttyS",
+ .dev_name = "ttyS",
+ .major = TTY_MAJOR,
+ .minor = 64,
+ .nr = CONFIG_ADM5120_NR_UARTS,
+ .cons = &adm5120_serconsole,
+};
+
+static int __init adm5120ser_init(void)
+{
+ int ret, i;
+
+ ret = uart_register_driver(&adm5120ser_reg);
+ if (!ret) {
+ for (i = 0; i < CONFIG_ADM5120_NR_UARTS; i++)
+ uart_add_one_port(&adm5120ser_reg, &adm5120ser_ports[i]);
+ }
+
+ return ret;
+}
+
+__initcall(adm5120ser_init);