]> git.ipfire.org Git - people/ms/u-boot.git/commitdiff
serial: ns16550: Add RX interrupt buffer support
authorStefan Roese <sr@denx.de>
Fri, 14 Jul 2017 15:25:54 +0000 (17:25 +0200)
committerTom Rini <trini@konsulko.com>
Sun, 23 Jul 2017 21:04:46 +0000 (17:04 -0400)
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 <sr@denx.de>
Reviewed-by: Simon Glass <sjg@chromium.org>
Cc: Bin Meng <bmeng.cn@gmail.com>
[trini: Guard ns16550_serial_remove with
CONFIG_IS_ENABLED(SERIAL_PRESENT) to match struct assignment]
Signed-off-by: Tom Rini <trini@konsulko.com>
drivers/serial/Kconfig
drivers/serial/ns16550.c
include/ns16550.h

index 97cef7edbda69a08856614270d7d323493e083fc..0748a9254504c510d9ca0e7225f713d3427543ca 100644 (file)
@@ -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
index c702304e79bd22e414f92c614105c76ee35e1f7c..607a1b8c1def202f67b8d9fd406861ff92f4c3b9 100644 (file)
@@ -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,
 };
index 5fcbcd2e74e3a2965eda64905416f1f2b792d4bc..7e9944d0d92e1fddb3b56908d12a6873115b74de 100644 (file)
  * @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;