From 6822cf3ec7c8768b8727573b8f4b2cb3d870b881 Mon Sep 17 00:00:00 2001 From: Stefan Roese Date: Fri, 14 Jul 2017 17:25:54 +0200 Subject: [PATCH] serial: ns16550: Add RX interrupt buffer support Pasting longer lines into the U-Boot console prompt sometimes leads to characters missing. One problem here is the small 16-byte FIFO of the legacy NS16550 UART, e.g. on x86 platforms. This patch now introduces a Kconfig option to enable RX interrupt buffer support for NS16550 style UARTs. With this option enabled, I was able paste really long lines into the U-Boot console, without any characters missing. Signed-off-by: Stefan Roese Reviewed-by: Simon Glass Cc: Bin Meng [trini: Guard ns16550_serial_remove with CONFIG_IS_ENABLED(SERIAL_PRESENT) to match struct assignment] Signed-off-by: Tom Rini --- drivers/serial/Kconfig | 10 ++++ drivers/serial/ns16550.c | 123 +++++++++++++++++++++++++++++++++++++-- include/ns16550.h | 10 ++++ 3 files changed, 138 insertions(+), 5 deletions(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 97cef7edbd..0748a92545 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -64,6 +64,16 @@ config DM_SERIAL implements serial_putc() etc. The uclass interface is defined in include/serial.h. +config SERIAL_IRQ_BUFFER + bool "Enable RX interrupt buffer for serial input" + depends on DM_SERIAL + default n + help + Enable RX interrupt buffer support for the serial driver. + This enables pasting longer strings, even when the RX FIFO + of the UART is not big enough (e.g. 16 bytes on the normal + NS16550). + config SPL_DM_SERIAL bool "Enable Driver Model for serial drivers in SPL" depends on DM_SERIAL diff --git a/drivers/serial/ns16550.c b/drivers/serial/ns16550.c index c702304e79..607a1b8c1d 100644 --- a/drivers/serial/ns16550.c +++ b/drivers/serial/ns16550.c @@ -314,6 +314,80 @@ DEBUG_UART_FUNCS #endif #ifdef CONFIG_DM_SERIAL + +#if CONFIG_IS_ENABLED(SERIAL_IRQ_BUFFER) + +#define BUF_COUNT 256 + +static void rx_fifo_to_buf(struct udevice *dev) +{ + struct NS16550 *const com_port = dev_get_priv(dev); + struct ns16550_platdata *plat = dev->platdata; + + /* Read all available chars into buffer */ + while ((serial_in(&com_port->lsr) & UART_LSR_DR)) { + plat->buf[plat->wr_ptr++] = serial_in(&com_port->rbr); + plat->wr_ptr %= BUF_COUNT; + } +} + +static int rx_pending(struct udevice *dev) +{ + struct ns16550_platdata *plat = dev->platdata; + + /* + * At startup it may happen, that some already received chars are + * "stuck" in the RX FIFO, even with the interrupt enabled. This + * RX FIFO flushing makes sure, that these chars are read out and + * the RX interrupts works as expected. + */ + rx_fifo_to_buf(dev); + + return plat->rd_ptr != plat->wr_ptr ? 1 : 0; +} + +static int rx_get(struct udevice *dev) +{ + struct ns16550_platdata *plat = dev->platdata; + char val; + + val = plat->buf[plat->rd_ptr++]; + plat->rd_ptr %= BUF_COUNT; + + return val; +} + +void ns16550_handle_irq(void *data) +{ + struct udevice *dev = (struct udevice *)data; + struct NS16550 *const com_port = dev_get_priv(dev); + + /* Check if interrupt is pending */ + if (serial_in(&com_port->iir) & UART_IIR_NO_INT) + return; + + /* Flush all available characters from the RX FIFO into the RX buffer */ + rx_fifo_to_buf(dev); +} + +#else /* CONFIG_SERIAL_IRQ_BUFFER */ + +static int rx_pending(struct udevice *dev) +{ + struct NS16550 *const com_port = dev_get_priv(dev); + + return serial_in(&com_port->lsr) & UART_LSR_DR ? 1 : 0; +} + +static int rx_get(struct udevice *dev) +{ + struct NS16550 *const com_port = dev_get_priv(dev); + + return serial_in(&com_port->rbr); +} + +#endif /* CONFIG_SERIAL_IRQ_BUFFER */ + static int ns16550_serial_putc(struct udevice *dev, const char ch) { struct NS16550 *const com_port = dev_get_priv(dev); @@ -339,19 +413,17 @@ static int ns16550_serial_pending(struct udevice *dev, bool input) struct NS16550 *const com_port = dev_get_priv(dev); if (input) - return serial_in(&com_port->lsr) & UART_LSR_DR ? 1 : 0; + return rx_pending(dev); else return serial_in(&com_port->lsr) & UART_LSR_THRE ? 0 : 1; } static int ns16550_serial_getc(struct udevice *dev) { - struct NS16550 *const com_port = dev_get_priv(dev); - - if (!(serial_in(&com_port->lsr) & UART_LSR_DR)) + if (!ns16550_serial_pending(dev, true)) return -EAGAIN; - return serial_in(&com_port->rbr); + return rx_get(dev); } static int ns16550_serial_setbrg(struct udevice *dev, int baudrate) @@ -374,8 +446,39 @@ int ns16550_serial_probe(struct udevice *dev) com_port->plat = dev_get_platdata(dev); NS16550_init(com_port, -1); +#if CONFIG_IS_ENABLED(SERIAL_IRQ_BUFFER) + if (gd->flags & GD_FLG_RELOC) { + struct ns16550_platdata *plat = dev->platdata; + + /* Allocate the RX buffer */ + plat->buf = malloc(BUF_COUNT); + + /* Install the interrupt handler */ + irq_install_handler(plat->irq, ns16550_handle_irq, dev); + + /* Enable RX interrupts */ + serial_out(UART_IER_RDI, &com_port->ier); + } +#endif + + return 0; +} + +#if CONFIG_IS_ENABLED(SERIAL_PRESENT) && \ + (!defined(CONFIG_TPL_BUILD) || defined(CONFIG_TPL_DM_SERIAL)) +static int ns16550_serial_remove(struct udevice *dev) +{ +#if CONFIG_IS_ENABLED(SERIAL_IRQ_BUFFER) + if (gd->flags & GD_FLG_RELOC) { + struct ns16550_platdata *plat = dev->platdata; + + irq_free_handler(plat->irq); + } +#endif + return 0; } +#endif #if CONFIG_IS_ENABLED(OF_CONTROL) enum { @@ -458,6 +561,15 @@ int ns16550_serial_ofdata_to_platdata(struct udevice *dev) if (port_type == PORT_JZ4780) plat->fcr |= UART_FCR_UME; +#if CONFIG_IS_ENABLED(SERIAL_IRQ_BUFFER) + plat->irq = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "interrupts", 0); + if (!plat->irq) { + debug("ns16550 interrupt not provided\n"); + return -EINVAL; + } +#endif + return 0; } #endif @@ -505,6 +617,7 @@ U_BOOT_DRIVER(ns16550_serial) = { #endif .priv_auto_alloc_size = sizeof(struct NS16550), .probe = ns16550_serial_probe, + .remove = ns16550_serial_remove, .ops = &ns16550_serial_ops, .flags = DM_FLAG_PRE_RELOC, }; diff --git a/include/ns16550.h b/include/ns16550.h index 5fcbcd2e74..7e9944d0d9 100644 --- a/include/ns16550.h +++ b/include/ns16550.h @@ -51,6 +51,10 @@ * @base: Base register address * @reg_shift: Shift size of registers (0=byte, 1=16bit, 2=32bit...) * @clock: UART base clock speed in Hz + * + * @buf: Pointer to the RX interrupt buffer + * @rd_ptr: Read pointer in the RX interrupt buffer + * @wr_ptr: Write pointer in the RX interrupt buffer */ struct ns16550_platdata { unsigned long base; @@ -58,6 +62,12 @@ struct ns16550_platdata { int clock; int reg_offset; u32 fcr; + + int irq; + + char *buf; + int rd_ptr; + int wr_ptr; }; struct udevice; -- 2.39.2