aboutsummaryrefslogtreecommitdiffstats
path: root/xen/drivers/char/exynos4210-uart.c
diff options
context:
space:
mode:
Diffstat (limited to 'xen/drivers/char/exynos4210-uart.c')
-rw-r--r--xen/drivers/char/exynos4210-uart.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/xen/drivers/char/exynos4210-uart.c b/xen/drivers/char/exynos4210-uart.c
new file mode 100644
index 0000000000..f151390352
--- /dev/null
+++ b/xen/drivers/char/exynos4210-uart.c
@@ -0,0 +1,365 @@
+/*
+ * xen/drivers/char/exynos4210-uart.c
+ *
+ * Driver for Exynos 4210 UART.
+ *
+ * Anthony PERARD <anthony.perard@citrix.com>
+ * Copyright (c) 2012 Citrix Systems.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <xen/config.h>
+#include <xen/console.h>
+#include <xen/errno.h>
+#include <xen/serial.h>
+#include <xen/init.h>
+#include <xen/irq.h>
+#include <xen/mm.h>
+#include <asm/early_printk.h>
+#include <asm/device.h>
+#include <asm/exynos4210-uart.h>
+
+static struct exynos4210_uart {
+ unsigned int baud, clock_hz, data_bits, parity, stop_bits;
+ struct dt_irq irq;
+ void *regs;
+ struct irqaction irqaction;
+} exynos4210_com = {0};
+
+/* These parity settings can be ORed directly into the ULCON. */
+#define PARITY_NONE (0)
+#define PARITY_ODD (0x4)
+#define PARITY_EVEN (0x5)
+#define FORCED_CHECKED_AS_ONE (0x6)
+#define FORCED_CHECKED_AS_ZERO (0x7)
+
+#define exynos4210_read(uart, off) ioreadl((uart)->regs + off)
+#define exynos4210_write(uart, off, val) iowritel((uart->regs) + off, val)
+
+static void exynos4210_uart_interrupt(int irq, void *data, struct cpu_user_regs *regs)
+{
+ struct serial_port *port = data;
+ struct exynos4210_uart *uart = port->uart;
+ unsigned int status;
+
+ status = exynos4210_read(uart, UINTP);
+
+ while ( status != 0 )
+ {
+ /* Clear all pending interrupts
+ * but should take care of ERROR and MODEM
+ */
+
+ if ( status & UINTM_ERROR )
+ {
+ uint32_t error_bit;
+
+ error_bit = exynos4210_read(uart, UERSTAT);
+
+ if ( error_bit & UERSTAT_OVERRUN )
+ dprintk(XENLOG_ERR, "uart: overrun error\n");
+ if ( error_bit & UERSTAT_PARITY )
+ dprintk(XENLOG_ERR, "uart: parity error\n");
+ if ( error_bit & UERSTAT_FRAME )
+ dprintk(XENLOG_ERR, "uart: frame error\n");
+ if ( error_bit & UERSTAT_BREAK )
+ dprintk(XENLOG_ERR, "uart: break detected\n");
+ /* Clear error pending interrupt */
+ exynos4210_write(uart, UINTP, UINTM_ERROR);
+ }
+
+
+ if ( status & (UINTM_RXD | UINTM_ERROR) )
+ {
+ /* uart->regs[UINTM] |= RXD|ERROR; */
+ serial_rx_interrupt(port, regs);
+ /* uart->regs[UINTM] &= ~(RXD|ERROR); */
+ exynos4210_write(uart, UINTP, UINTM_RXD | UINTM_ERROR);
+ }
+
+ if ( status & (UINTM_TXD | UINTM_MODEM) )
+ {
+ /* uart->regs[UINTM] |= TXD|MODEM; */
+ serial_tx_interrupt(port, regs);
+ /* uart->regs[UINTM] &= ~(TXD|MODEM); */
+ exynos4210_write(uart, UINTP, UINTM_TXD | UINTM_MODEM);
+ }
+
+ status = exynos4210_read(uart, UINTP);
+ }
+}
+
+static void __init exynos4210_uart_init_preirq(struct serial_port *port)
+{
+ struct exynos4210_uart *uart = port->uart;
+ unsigned int divisor;
+ uint32_t ulcon;
+
+ /* reset, TX/RX disables */
+ exynos4210_write(uart, UCON, 0);
+
+ /* No Interrupt, auto flow control */
+ exynos4210_write(uart, UMCON, 0);
+
+ /* Line control and baud-rate generator. */
+ if ( uart->baud != BAUD_AUTO )
+ {
+ /* Baud rate specified: program it into the divisor latch. */
+ divisor = ((uart->clock_hz) / (uart->baud)) - 1;
+ /* FIXME: will use a hacked divisor, assuming the src clock and bauds */
+ exynos4210_write(uart, UFRACVAL, 53);
+ exynos4210_write(uart, UBRDIV, 4);
+ }
+ else
+ {
+ /*
+ * TODO: should be updated
+ * Baud rate already set: read it out from the divisor latch.
+ * divisor = (uart->regs[IBRD] << 6) | uart->regs[FBRD];
+ * uart->baud = (uart->clock_hz << 2) / divisor;
+ */
+ }
+
+ /*
+ * Number of bits per character
+ * 0 => 5 bits
+ * 1 => 6 bits
+ * 2 => 7 bits
+ * 3 => 8 bits
+ */
+ ASSERT(uart->data_bits >= 5 && uart->data_bits <= 8);
+ ulcon = (uart->data_bits - 5);
+
+ /*
+ * Stop bits
+ * 0 => 1 stop bit per frame
+ * 1 => 2 stop bit per frame
+ */
+ ASSERT(uart->stop_bits >= 1 && uart->stop_bits <= 2);
+ ulcon |= (uart->stop_bits - 1) << ULCON_STOPB_SHIFT;
+
+
+ /* Parity */
+ ulcon |= uart->parity << ULCON_PARITY_SHIFT;
+
+ exynos4210_write(uart, ULCON, ulcon);
+
+ /* Mask and clear the interrupts */
+ exynos4210_write(uart, UINTM, UINTM_ALLI);
+ exynos4210_write(uart, UINTP, UINTM_ALLI);
+
+ /* reset FIFO */
+ exynos4210_write(uart, UFCON, UFCON_FIFO_RESET);
+
+ /* TODO: Add timeout to avoid infinite loop */
+ while ( exynos4210_read(uart, UFCON) & UFCON_FIFO_RESET )
+ ;
+
+ /*
+ * Enable FIFO and set the trigger level of Tx FIFO
+ * The trigger level is always set to b101, an interrupt will be
+ * generated when data count of Tx FIFO is less than or equal to the
+ * following value:
+ * UART0 => 160 bytes
+ * UART1 => 40 bytes
+ * UART2 => 10 bytes
+ * UART3 => 10 bytes
+ */
+ exynos4210_write(uart, UFCON, UFCON_FIFO_TX_TRIGGER | UFCON_FIFO_EN);
+
+ /*
+ * Enable the UART for Rx and Tx
+ * - Use only interrupt request
+ * - Interrupts are level trigger
+ * - Enable Rx timeout
+ */
+ exynos4210_write(uart, UCON,
+ UCON_RX_IRQ_LEVEL | UCON_TX_IRQ_LEVEL | UCON_RX_IRQ |
+ UCON_TX_IRQ | UCON_RX_TIMEOUT);
+}
+
+static void __init exynos4210_uart_init_postirq(struct serial_port *port)
+{
+ struct exynos4210_uart *uart = port->uart;
+ int rc;
+
+ uart->irqaction.handler = exynos4210_uart_interrupt;
+ uart->irqaction.name = "exynos4210_uart";
+ uart->irqaction.dev_id = port;
+
+ if ( (rc = setup_dt_irq(&uart->irq, &uart->irqaction)) != 0 )
+ dprintk(XENLOG_ERR, "Failed to allocated exynos4210_uart IRQ %d\n",
+ uart->irq.irq);
+
+ /* Unmask interrupts */
+ exynos4210_write(uart, UINTM, ~UINTM_ALLI);
+
+ /* Clear pending interrupts */
+ exynos4210_write(uart, UINTP, UINTM_ALLI);
+
+ /* Enable interrupts */
+ exynos4210_write(uart, UMCON, exynos4210_read(uart, UMCON) | UMCON_INT_EN);
+}
+
+static void exynos4210_uart_suspend(struct serial_port *port)
+{
+ BUG(); // XXX
+}
+
+static void exynos4210_uart_resume(struct serial_port *port)
+{
+ BUG(); // XXX
+}
+
+static unsigned int exynos4210_uart_tx_ready(struct serial_port *port)
+{
+ struct exynos4210_uart *uart = port->uart;
+
+ /* Tx fifo full ? */
+ if ( exynos4210_read(uart, UFSTAT) & UFSTAT_TX_FULL )
+ return 0;
+ else
+ {
+ uint32_t val = exynos4210_read(uart, UFSTAT);
+
+ val = (val & UFSTAT_TX_COUNT_MASK) >> UFSTAT_TX_COUNT_SHIFT;
+
+ /* XXX: Here we assume that we use UART 2/3, on the others
+ * UART the buffer is bigger
+ */
+ ASSERT(val >= 0 && val <= FIFO_MAX_SIZE);
+
+ return (FIFO_MAX_SIZE - val);
+ }
+}
+
+static void exynos4210_uart_putc(struct serial_port *port, char c)
+{
+ struct exynos4210_uart *uart = port->uart;
+
+ exynos4210_write(uart, UTXH, (uint32_t)(unsigned char)c);
+}
+
+static int exynos4210_uart_getc(struct serial_port *port, char *pc)
+{
+ struct exynos4210_uart *uart = port->uart;
+ uint32_t ufstat = exynos4210_read(uart, UFSTAT);
+ uint32_t count;
+
+ count = (ufstat & UFSTAT_RX_COUNT_MASK) >> UFSTAT_RX_COUNT_SHIFT;
+
+ /* Check if Rx fifo is full or if the is something in it */
+ if ( ufstat & UFSTAT_RX_FULL || count )
+ {
+ *pc = exynos4210_read(uart, URXH) & URXH_DATA_MASK;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static int __init exynos4210_uart_irq(struct serial_port *port)
+{
+ struct exynos4210_uart *uart = port->uart;
+
+ return uart->irq.irq;
+}
+
+static const struct dt_irq __init *exynos4210_uart_dt_irq(struct serial_port *port)
+{
+ struct exynos4210_uart *uart = port->uart;
+
+ return &uart->irq;
+}
+
+static struct uart_driver __read_mostly exynos4210_uart_driver = {
+ .init_preirq = exynos4210_uart_init_preirq,
+ .init_postirq = exynos4210_uart_init_postirq,
+ .endboot = NULL,
+ .suspend = exynos4210_uart_suspend,
+ .resume = exynos4210_uart_resume,
+ .tx_ready = exynos4210_uart_tx_ready,
+ .putc = exynos4210_uart_putc,
+ .getc = exynos4210_uart_getc,
+ .irq = exynos4210_uart_irq,
+ .dt_irq_get = exynos4210_uart_dt_irq,
+};
+
+/* TODO: Parse UART config from the command line */
+static int __init exynos4210_uart_init(struct dt_device_node *dev,
+ const void *data)
+{
+ const char *config = data;
+ struct exynos4210_uart *uart;
+ int res;
+ u64 addr, size;
+
+ if ( strcmp(config, "") )
+ {
+ early_printk("WARNING: UART configuration is not supported\n");
+ }
+
+ uart = &exynos4210_com;
+
+ /* uart->clock_hz = 0x16e3600; */
+ uart->baud = BAUD_AUTO;
+ uart->data_bits = 8;
+ uart->parity = PARITY_NONE;
+ uart->stop_bits = 1;
+
+ res = dt_device_get_address(dev, 0, &addr, &size);
+ if ( res )
+ {
+ early_printk("exynos4210: Unable to retrieve the base"
+ " address of the UART\n");
+ return res;
+ }
+
+ uart->regs = ioremap_nocache(addr, size);
+ if ( !uart->regs )
+ {
+ early_printk("exynos4210: Unable to map the UART memory\n");
+ }
+ res = dt_device_get_irq(dev, 0, &uart->irq);
+ if ( res )
+ {
+ early_printk("exynos4210: Unable to retrieve the IRQ\n");
+ return res;
+ }
+
+ /* Register with generic serial driver. */
+ serial_register_uart(SERHND_DTUART, &exynos4210_uart_driver, uart);
+
+ dt_device_set_used_by(dev, DOMID_XEN);
+
+ return 0;
+}
+
+static const char const *exynos4210_dt_compat[] __initdata =
+{
+ "samsung,exynos4210-uart",
+ NULL
+};
+
+DT_DEVICE_START(exynos4210, "Exynos 4210 UART", DEVICE_SERIAL)
+ .compatible = exynos4210_dt_compat,
+ .init = exynos4210_uart_init,
+DT_DEVICE_END
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */