[U-Boot] [PATCH v2] serial: ns16550: Add RX interrupt buffer support

Stefan Roese sr at denx.de
Fri Jul 14 15:25:54 UTC 2017


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 at denx.de>
Reviewed-by: Simon Glass <sjg at chromium.org>
Cc: Bin Meng <bmeng.cn at gmail.com>
---
v2:
- Added Simon's RB
- Added irq_free_handler to newly introduced driver remove function
- Added comments to the introduced variables on the ns16550_platdata
  struct

 drivers/serial/Kconfig   |  10 ++++
 drivers/serial/ns16550.c | 120 +++++++++++++++++++++++++++++++++++++++++++++--
 include/ns16550.h        |  10 ++++
 3 files changed, 135 insertions(+), 5 deletions(-)

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index b7dd2ac103..8284febae3 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 e0e70244ce..0c43cb1fe2 100644
--- a/drivers/serial/ns16550.c
+++ b/drivers/serial/ns16550.c
@@ -315,6 +315,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);
@@ -340,19 +414,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)
@@ -375,6 +447,34 @@ 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;
+}
+
+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;
 }
 
@@ -459,6 +559,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
@@ -506,6 +615,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.13.3



More information about the U-Boot mailing list