aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/serial/Kconfig10
-rw-r--r--drivers/serial/ns16550.c123
-rw-r--r--include/ns16550.h10
3 files changed, 138 insertions, 5 deletions
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 97cef7edbda..0748a925450 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 c702304e79b..607a1b8c1de 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 5fcbcd2e74e..7e9944d0d92 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;