diff options
Diffstat (limited to 'xen/drivers/char/omap-uart.c')
-rw-r--r-- | xen/drivers/char/omap-uart.c | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/xen/drivers/char/omap-uart.c b/xen/drivers/char/omap-uart.c new file mode 100644 index 0000000000..91391c86c8 --- /dev/null +++ b/xen/drivers/char/omap-uart.c @@ -0,0 +1,374 @@ +/* + * omap-uart.c + * Based on drivers/char/ns16550.c + * + * Driver for OMAP-UART controller + * + * Copyright (C) 2013, Chen Baozi <baozich@gmail.com> + * + * Note: This driver is made separate from 16550-series UART driver as + * omap platform has some specific configurations + */ + +#include <xen/config.h> +#include <xen/console.h> +#include <xen/serial.h> +#include <xen/init.h> +#include <xen/irq.h> +#include <asm/early_printk.h> +#include <xen/device_tree.h> +#include <asm/device.h> +#include <xen/errno.h> +#include <xen/mm.h> +#include <xen/vmap.h> +#include <xen/8250-uart.h> + +#define REG_SHIFT 2 + +#define omap_read(uart, off) ioreadl((uart)->regs + (off<<REG_SHIFT)) +#define omap_write(uart, off, val) iowritel((uart)->regs + (off<<REG_SHIFT), (val)) + +static struct omap_uart { + u32 baud, clock_hz, data_bits, parity, stop_bits, fifo_size; + struct dt_irq irq; + char __iomem *regs; + struct irqaction irqaction; + struct vuart_info vuart; +} omap_com = {0}; + +static void omap_uart_interrupt(int irq, void *data, struct cpu_user_regs *regs) +{ + struct serial_port *port = data; + struct omap_uart *uart = port->uart; + u32 lsr; + uint32_t reg; + + while ( !(omap_read(uart, UART_IIR) & UART_IIR_NOINT) ) + { + lsr = omap_read(uart, UART_LSR) & 0xff; + if ( lsr & UART_LSR_THRE ) + serial_tx_interrupt(port, regs); + if ( lsr & UART_LSR_DR ) + serial_rx_interrupt(port, regs); + + if ( port->txbufc == port->txbufp ) { + reg = omap_read(uart, UART_IER); + omap_write(uart, UART_IER, reg & (~UART_IER_ETHREI)); + } + }; +} + +static void baud_protocol_setup(struct omap_uart *uart) +{ + u32 dll, dlh, efr; + unsigned int divisor; + + divisor = uart->clock_hz / (uart->baud << 4); + dll = divisor & 0xff; + dlh = divisor >> 8; + + /* + * Switch to register configuration mode B to access the UART_OMAP_EFR + * register. + */ + omap_write(uart, UART_LCR, UART_LCR_CONF_MODE_B); + /* + * Enable access to the UART_IER[7:4] bit field. + */ + efr = omap_read(uart, UART_OMAP_EFR); + omap_write(uart, UART_OMAP_EFR, efr|UART_OMAP_EFR_ECB); + /* + * Switch to register operation mode to access the UART_IER register. + */ + omap_write(uart, UART_LCR, 0); + /* + * Clear the UART_IER register (set the UART_IER[4] SLEEP_MODE bit + * to 0 to change the UART_DLL and UART_DLM register). Set the + * UART_IER register value to 0x0000. + */ + omap_write(uart, UART_IER, 0); + /* + * Switch to register configuartion mode B to access the UART_DLL and + * UART_DLM registers. + */ + omap_write(uart, UART_LCR, UART_LCR_CONF_MODE_B); + /* + * Load divisor value. + */ + omap_write(uart, UART_DLL, dll); + omap_write(uart, UART_DLM, dlh); + /* + * Restore the UART_OMAP_EFR + */ + omap_write(uart, UART_OMAP_EFR, efr); + /* + * Load the new protocol formatting (parity, stop-bit, character length) + * and switch to register operational mode. + */ + omap_write(uart, UART_LCR, (uart->data_bits - 5) | + ((uart->stop_bits - 1) << 2) | uart->parity); +} + +static void fifo_setup(struct omap_uart *uart) +{ + u32 lcr, efr, mcr; + /* + * Switch to register configuration mode B to access the UART_OMAP_EFR + * register. + */ + lcr = omap_read(uart, UART_LCR); + omap_write(uart, UART_LCR, UART_LCR_CONF_MODE_B); + /* + * Enable register submode TCR_TLR to access the UART_OMAP_TLR register. + */ + efr = omap_read(uart, UART_OMAP_EFR); + omap_write(uart, UART_OMAP_EFR, efr|UART_OMAP_EFR_ECB); + /* + * Switch to register configuration mode A to access the UART_MCR + * register. + */ + omap_write(uart, UART_LCR, UART_LCR_CONF_MODE_A); + /* + * Enable register submode TCR_TLR to access the UART_OMAP_TLR register + */ + mcr = omap_read(uart, UART_MCR); + omap_write(uart, UART_MCR, mcr|UART_MCR_TCRTLR); + /* + * Enable the FIFO; load the new FIFO trigger and the new DMA mode. + */ + omap_write(uart, UART_FCR, UART_FCR_R_TRIG_01| + UART_FCR_T_TRIG_10|UART_FCR_ENABLE); + /* + * Switch to register configuration mode B to access the UART_EFR + * register. + */ + omap_write(uart, UART_LCR, UART_LCR_CONF_MODE_B); + /* + * Load the new FIFO triggers and the new DMA mode bit. + */ + omap_write(uart, UART_OMAP_SCR, OMAP_UART_SCR_RX_TRIG_GRANU1_MASK); + /* + * Restore the UART_OMAP_EFR[4] value. + */ + omap_write(uart, UART_OMAP_EFR, efr); + /* + * Switch to register configuration mode A to access the UART_MCR + * register. + */ + omap_write(uart, UART_LCR, UART_LCR_CONF_MODE_A); + /* + * Restore UART_MCR[6] value. + */ + omap_write(uart, UART_MCR, mcr); + /* + * Restore UART_LCR value. + */ + omap_write(uart, UART_LCR, lcr); + + uart->fifo_size = 64; +} + +static void __init omap_uart_init_preirq(struct serial_port *port) +{ + struct omap_uart *uart = port->uart; + + /* + * Clear the FIFO buffers. + */ + omap_write(uart, UART_FCR, UART_FCR_ENABLE); + omap_write(uart, UART_FCR, UART_FCR_ENABLE|UART_FCR_CLRX|UART_FCR_CLTX); + omap_write(uart, UART_FCR, 0); + + /* + * The TRM says the mode should be disabled while UART_DLL and UART_DHL + * are being changed so we disable before setup, then enable. + */ + omap_write(uart, UART_OMAP_MDR1, UART_OMAP_MDR1_DISABLE); + + /* Baud rate & protocol format setup */ + baud_protocol_setup(uart); + + /* FIFO setup */ + fifo_setup(uart); + + /* No flow control */ + omap_write(uart, UART_MCR, UART_MCR_DTR|UART_MCR_RTS); + + omap_write(uart, UART_OMAP_MDR1, UART_OMAP_MDR1_16X_MODE); +} + +static void __init omap_uart_init_postirq(struct serial_port *port) +{ + struct omap_uart *uart = port->uart; + + uart->irqaction.handler = omap_uart_interrupt; + uart->irqaction.name = "omap_uart"; + uart->irqaction.dev_id = port; + + if ( setup_dt_irq(&uart->irq, &uart->irqaction) != 0 ) + { + dprintk(XENLOG_ERR, "Failed to allocated omap_uart IRQ %d\n", + uart->irq.irq); + return; + } + + /* Enable interrupts */ + omap_write(uart, UART_IER, UART_IER_ERDAI|UART_IER_ETHREI|UART_IER_ELSI); +} + +static void omap_uart_suspend(struct serial_port *port) +{ + BUG(); +} + +static void omap_uart_resume(struct serial_port *port) +{ + BUG(); +} + +static unsigned int omap_uart_tx_ready(struct serial_port *port) +{ + struct omap_uart *uart = port->uart; + uint32_t reg; + + reg = omap_read(uart, UART_IER); + omap_write(uart, UART_IER, reg | UART_IER_ETHREI); + + return omap_read(uart, UART_LSR) & UART_LSR_THRE ? uart->fifo_size : 0; +} + +static void omap_uart_putc(struct serial_port *port, char c) +{ + struct omap_uart *uart = port->uart; + + omap_write(uart, UART_THR, (uint32_t)(unsigned char)c); +} + +static int omap_uart_getc(struct serial_port *port, char *pc) +{ + struct omap_uart *uart = port->uart; + + if ( !(omap_read(uart, UART_LSR) & UART_LSR_DR) ) + return 0; + + *pc = omap_read(uart, UART_RBR) & 0xff; + return 1; +} + +static int __init omap_uart_irq(struct serial_port *port) +{ + struct omap_uart *uart = port->uart; + + return ((uart->irq.irq > 0) ? uart->irq.irq : -1); +} + +static const struct dt_irq __init *omap_uart_dt_irq(struct serial_port *port) +{ + struct omap_uart *uart = port->uart; + + return &uart->irq; +} + +static const struct vuart_info *omap_vuart_info(struct serial_port *port) +{ + struct omap_uart *uart = port->uart; + + return &uart->vuart; +} + +static struct uart_driver __read_mostly omap_uart_driver = { + .init_preirq = omap_uart_init_preirq, + .init_postirq = omap_uart_init_postirq, + .endboot = NULL, + .suspend = omap_uart_suspend, + .resume = omap_uart_resume, + .tx_ready = omap_uart_tx_ready, + .putc = omap_uart_putc, + .getc = omap_uart_getc, + .irq = omap_uart_irq, + .dt_irq_get = omap_uart_dt_irq, + .vuart_info = omap_vuart_info, +}; + +static int __init omap_uart_init(struct dt_device_node *dev, + const void *data) +{ + const char *config = data; + struct omap_uart *uart; + u32 clkspec; + int res; + u64 addr, size; + + if ( strcmp(config, "") ) + early_printk("WARNING: UART configuration is not supported\n"); + + uart = &omap_com; + + res = dt_property_read_u32(dev, "clock-frequency", &clkspec); + if ( !res ) + { + early_printk("omap-uart: Unable to retrieve the clock frequency\n"); + return -EINVAL; + } + + uart->clock_hz = clkspec; + uart->baud = 115200; + uart->data_bits = 8; + uart->parity = UART_PARITY_NONE; + uart->stop_bits = 1; + + res = dt_device_get_address(dev, 0, &addr, &size); + if ( res ) + { + early_printk("omap-uart: 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("omap-uart: Unable to map the UART memory\n"); + return -ENOMEM; + } + + res = dt_device_get_irq(dev, 0, &uart->irq); + if ( res ) + { + early_printk("omap-uart: Unable to retrieve the IRQ\n"); + return res; + } + + uart->vuart.base_addr = addr; + uart->vuart.size = size; + uart->vuart.data_off = UART_THR; + uart->vuart.status_off = UART_LSR << REG_SHIFT; + uart->vuart.status = UART_LSR_THRE; + + /* Register with generic serial driver */ + serial_register_uart(SERHND_DTUART, &omap_uart_driver, uart); + + dt_device_set_used_by(dev, DOMID_XEN); + + return 0; +} + +static const char * const omap_uart_dt_compat[] __initdata = +{ + "ti,omap4-uart", + NULL +}; + +DT_DEVICE_START(omap_uart, "OMAP UART", DEVICE_SERIAL) + .compatible = omap_uart_dt_compat, + .init = omap_uart_init, +DT_DEVICE_END + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ |