diff mbox

[V3,37/41] xen/arm: Add exynos 4210 UART support

Message ID 1368152307-598-38-git-send-email-julien.grall@linaro.org
State Superseded, archived
Headers show

Commit Message

Julien Grall May 10, 2013, 2:18 a.m. UTC
From: Anthony PERARD <anthony.perard@citrix.com>

Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
Signed-off-by: Julien Grall <julien.grall@linaro.org>

Changes in v3:
    - Directly get the base address from the DT and map it with ioremap_attr
    - Use ioreadl/iowritel
    - Typoes
    - Remove the remaining //

Changes in v2:
    - Use defines where it's possible
    - Move UART definition in a separate header. Will be used for early
    UART
    - Replace all  // by /* ... */
    - Add Anthony as first author
---
 config/arm32.mk                       |    1 +
 xen/drivers/char/Makefile             |    1 +
 xen/drivers/char/exynos4210-uart.c    |  359 +++++++++++++++++++++++++++++++++
 xen/include/asm-arm/exynos4210-uart.h |  111 ++++++++++
 4 files changed, 472 insertions(+)
 create mode 100644 xen/drivers/char/exynos4210-uart.c
 create mode 100644 xen/include/asm-arm/exynos4210-uart.h

Comments

Ian Campbell May 10, 2013, 9:49 a.m. UTC | #1
On Fri, 2013-05-10 at 03:18 +0100, Julien Grall wrote:
> From: Anthony PERARD <anthony.perard@citrix.com>
> 
> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
> Signed-off-by: Julien Grall <julien.grall@linaro.org>
> [...]

> +        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");

I'm curious to know where these dprintk's go ;-), but:

Acked-by: Ian Campbell <ian.campbell@citrix.com>

Ian.
Julien Grall May 10, 2013, 1 p.m. UTC | #2
On 05/10/2013 10:49 AM, Ian Campbell wrote:

> On Fri, 2013-05-10 at 03:18 +0100, Julien Grall wrote:
>> From: Anthony PERARD <anthony.perard@citrix.com>
>>
>> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
>> Signed-off-by: Julien Grall <julien.grall@linaro.org>
>> [...]
> 
>> +        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");
> 
> I'm curious to know where these dprintk's go ;-), but:

Directly on the UART :/. But I'm not sure if UART errors are fatal or not.

> Acked-by: Ian Campbell <ian.campbell@citrix.com>
diff mbox

Patch

diff --git a/config/arm32.mk b/config/arm32.mk
index f64f0c1..d8e958b 100644
--- a/config/arm32.mk
+++ b/config/arm32.mk
@@ -8,6 +8,7 @@  CONFIG_ARM_$(XEN_OS) := y
 CFLAGS += -marm
 
 HAS_PL011 := y
+HAS_EXYNOS4210 := y
 
 # Use only if calling $(LD) directly.
 #LDFLAGS_DIRECT_OpenBSD = _obsd
diff --git a/xen/drivers/char/Makefile b/xen/drivers/char/Makefile
index 9c067f9..37543f0 100644
--- a/xen/drivers/char/Makefile
+++ b/xen/drivers/char/Makefile
@@ -1,6 +1,7 @@ 
 obj-y += console.o
 obj-$(HAS_NS16550) += ns16550.o
 obj-$(HAS_PL011) += pl011.o
+obj-$(HAS_EXYNOS4210) += exynos4210-uart.o
 obj-$(HAS_EHCI) += ehci-dbgp.o
 obj-$(CONFIG_ARM) += dt-uart.o
 obj-y += serial.o
diff --git a/xen/drivers/char/exynos4210-uart.c b/xen/drivers/char/exynos4210-uart.c
new file mode 100644
index 0000000..437a987
--- /dev/null
+++ b/xen/drivers/char/exynos4210-uart.c
@@ -0,0 +1,359 @@ 
+/*
+ * 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)
+{
+    struct exynos4210_uart *uart;
+    int res;
+    u64 addr, size;
+
+    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_attr(addr, size, PAGE_HYPERVISOR_NOCACHE);
+    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:
+ */
diff --git a/xen/include/asm-arm/exynos4210-uart.h b/xen/include/asm-arm/exynos4210-uart.h
new file mode 100644
index 0000000..330e1c0
--- /dev/null
+++ b/xen/include/asm-arm/exynos4210-uart.h
@@ -0,0 +1,111 @@ 
+/*
+ * xen/include/asm-arm/exynos4210-uart.h
+ *
+ * Common constant definition between early printk and the UART driver
+ * for the exynos 4210 UART
+ *
+ * Julien Grall <julien.grall@linaro.org>
+ * Copyright (c) 2013 Linaro Limited.
+ *
+ * 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.
+ */
+
+#ifndef __ASM_ARM_EXYNOS4210_H
+#define __ASM_ARM_EXYNOS4210_H
+
+
+/*
+ * this value is only valid for UART 2 and UART 3
+ * XXX: define per UART
+ */
+#define FIFO_MAX_SIZE 16
+
+/* register addresses */
+#define ULCON     (0x00)
+#define UCON      (0x04)
+#define UFCON     (0x08)
+#define UMCON     (0x0c)
+#define UTRSTAT   (0x10)
+#define UERSTAT   (0x14)
+#define UFSTAT    (0x18)
+#define UMSTAT    (0x1c)
+#define UTXH      (0x20)
+#define URXH      (0x24)
+#define UBRDIV    (0x28)
+#define UFRACVAL  (0x2c)
+#define UINTP     (0x30)
+#define UINTS     (0x34)
+#define UINTM     (0x38)
+
+/* UCON */
+#define UCON_RX_IRQ         (1 << 0)
+#define UCON_TX_IRQ         (1 << 2)
+#define UCON_RX_TIMEOUT     (1 << 7)
+
+/*
+ * FIXME: IRQ_LEVEL should be 1 << n but with this value, the IRQ
+ * handler will never end...
+ */
+#define UCON_RX_IRQ_LEVEL   (0 << 8)
+#define UCON_TX_IRQ_LEVEL   (0 << 9)
+
+/* ULCON */
+#define ULCON_STOPB_SHIFT 2
+#define ULCON_PARITY_SHIFT 3
+
+/* UFCON */
+#define UFCON_FIFO_TX_RESET     (1 << 2)
+#define UFCON_FIFO_RX_RESET     (1 << 1)
+#define UFCON_FIFO_RESET        (UFCON_FIFO_TX_RESET | UFCON_FIFO_RX_RESET)
+#define UFCON_FIFO_EN           (1 << 0)
+
+#define UFCON_FIFO_TX_TRIGGER   (0x6 << 8)
+
+/* UMCON */
+#define UMCON_INT_EN            (1 << 3)
+
+/* UERSTAT */
+#define UERSTAT_OVERRUN (1 << 0)
+#define UERSTAT_PARITY  (1 << 1)
+#define UERSTAT_FRAME   (1 << 2)
+#define UERSTAT_BREAK   (1 << 3)
+
+/* UFSTAT */
+#define UFSTAT_TX_FULL          (1 << 24)
+#define UFSTAT_TX_COUNT_SHIFT   (16)
+#define UFSTAT_TX_COUNT_MASK    (0xff << UFSTAT_TX_COUNT_SHIFT)
+#define UFSTAT_RX_FULL          (1 << 8)
+#define UFSTAT_RX_COUNT_SHIFT   (0)
+#define UFSTAT_RX_COUNT_MASK    (0xff << UFSTAT_RX_COUNT_SHIFT)
+
+/* UTRSTAT */
+#define UTRSTAT_TX_EMPTY        (1 << 1)
+
+/* URHX */
+#define URXH_DATA_MASK  (0xff)
+
+/* Interrupt bits (UINTP, UINTS, UINTM) */
+#define UINTM_MODEM     (1 << 3)
+#define UINTM_TXD       (1 << 2)
+#define UINTM_ERROR     (1 << 1)
+#define UINTM_RXD       (1 << 0)
+#define UINTM_ALLI      (UINTM_MODEM | UINTM_TXD | UINTM_ERROR | UINTM_RXD)
+
+#endif /* __ASM_ARM_EXYNOS4210_H */
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */