[PATCH] Add PCISerial Driver

Ira W. Snyder iws at ovro.caltech.edu
Mon Aug 18 19:56:33 CEST 2008


This adds a very simple driver which emulates a serial port over the PCI
bus. This is extremely useful for boards in PCISLAVE mode, so you do not
need extra cables to communicate with them.

There is a corresponding Linux driver.

Signed-off-by: Ira W. Snyder <iws at ovro.caltech.edu>
---
 drivers/serial/Makefile    |    3 +-
 drivers/serial/pciserial.c |  230 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 232 insertions(+), 1 deletions(-)
 create mode 100644 drivers/serial/pciserial.c

diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index eafe543..652371d 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -31,13 +31,14 @@ COBJS-$(CONFIG_NS9750_UART) += ns9750_serial.o
 COBJS-y += ns16550.o
 COBJS-$(CONFIG_DRIVER_S3C4510_UART) += s3c4510b_uart.o
 COBJS-$(CONFIG_S3C64XX) += s3c64xx.o
-COBJS-y += serial.o
+COBJS-$(CFG_NS16550_SERIAL) += serial.o
 COBJS-$(CONFIG_MAX3100_SERIAL) += serial_max3100.o
 COBJS-$(CONFIG_PL010_SERIAL) += serial_pl01x.o
 COBJS-$(CONFIG_PL011_SERIAL) += serial_pl01x.o
 COBJS-$(CONFIG_XILINX_UARTLITE) += serial_xuartlite.o
 COBJS-$(CONFIG_SCIF_CONSOLE) += serial_sh.o
 COBJS-$(CONFIG_USB_TTY) += usbtty.o
+COBJS-$(CONFIG_PCISERIAL) += pciserial.o
 
 COBJS	:= $(sort $(COBJS-y))
 SRCS	:= $(COBJS:.o=.c)
diff --git a/drivers/serial/pciserial.c b/drivers/serial/pciserial.c
new file mode 100644
index 0000000..ae5b8c4
--- /dev/null
+++ b/drivers/serial/pciserial.c
@@ -0,0 +1,230 @@
+/*
+ * ONE-LINE DESCRIPTION
+ *
+ * Copyright (c) 2008 Ira W. Snyder <iws at ovro.caltech.edu>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Access helpers */
+#define W32(addr, b) out_le32((volatile u32 *)(addr), (b))
+#define R32(addr) in_le32((volatile u32 *)(addr))
+#define W32BE(addr, b) out_be32((volatile u32 *)(addr), (b))
+#define R32BE(addr) in_be32((volatile u32 *)(addr))
+
+/* Doorbell Definitions */
+#define UART_RX_READY_DBELL	(1<<0)
+#define UART_TX_EMPTY_DBELL	(1<<1)
+
+/* Important status bits */
+#define PCINET_UART_RX_ENABLED	(1<<0)
+
+/* DEBUGGING NOTES:
+ *
+ * The easiest way to debug this driver is to hook up an actual serial
+ * port to your board, and just print messages out it.
+ *
+ * I did it during this driver's development by piggy-backing these
+ * routines in the drivers/serial/serial.c routines (bypassing the
+ * CONFIG_PCISERIAL_SERIAL routines), inserting them immediately before
+ * the NS16550_*() routines are called, omitting returns, etc.
+ *
+ * I also recommend doing just one part at a time. For example, just
+ * run the putc() code first, omitting everything else. Then you should
+ * get a mirror of the regular serial output when you connect with
+ * this driver.
+ *
+ * I have added a debugging option to this driver. It prints all data it
+ * receives to the 16550 COM1 port. To enable this, you should:
+ * #undef CONFIG_NS16550_SERIAL
+ * #define CONFIG_PCISERIAL_DEBUG
+ */
+
+#ifdef CONFIG_PCISERIAL_DEBUG
+
+#include <ns16550.h>
+
+/* Check for CONFIG_SYS_NS16550_COM1 */
+#ifndef CONFIG_SYS_NS16550_COM1
+#error "CONFIG_SYS_NS16550_COM1 is needed for CONFIG_PCISERIAL_DEBUG to operate"
+#endif
+
+/* Set up an easy way to change where the output will be mirrored to */
+#define PCISERIAL_DEBUG_OUTPUT CONFIG_SYS_NS16550_COM1
+
+static int calc_divisor (NS16550_t port)
+{
+#ifdef CONFIG_OMAP1510
+	/* If can't cleanly clock 115200 set div to 1 */
+	if ((CONFIG_SYS_NS16550_CLK == 12000000) && (gd->baudrate == 115200)) {
+		port->osc_12m_sel = OSC_12M_SEL;	/* enable 6.5 * divisor */
+		return (1);				/* return 1 for base divisor */
+	}
+	port->osc_12m_sel = 0;			/* clear if previsouly set */
+#endif
+#ifdef CONFIG_OMAP1610
+	/* If can't cleanly clock 115200 set div to 1 */
+	if ((CONFIG_SYS_NS16550_CLK == 48000000) && (gd->baudrate == 115200)) {
+		return (26);		/* return 26 for base divisor */
+	}
+#endif
+
+#ifdef CONFIG_APTIX
+#define MODE_X_DIV 13
+#else
+#define MODE_X_DIV 16
+#endif
+
+	/* Compute divisor value. Normally, we should simply return:
+	 *   CONFIG_SYS_NS16550_CLK) / MODE_X_DIV / gd->baudrate
+	 * but we need to round that value by adding 0.5.
+	 * Rounding is especially important at high baud rates.
+	 */
+	return (CONFIG_SYS_NS16550_CLK + (gd->baudrate * (MODE_X_DIV / 2))) /
+		(MODE_X_DIV * gd->baudrate);
+}
+#endif
+
+static void status_setbit(u32 bit)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	W32(&dma->omr1, R32(&dma->omr1) | bit);
+}
+
+static u32 status_remote_testbit(u32 bit)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	return R32(&dma->imr1) & bit;
+}
+
+static void pciserial_init(void)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+#ifdef CONFIG_PCISERIAL_DEBUG
+	int clock_divisor;
+
+	clock_divisor = calc_divisor(PCISERIAL_DEBUG_OUTPUT);
+	NS16550_init(PCISERIAL_DEBUG_OUTPUT, clock_divisor);
+#endif
+
+	/* Mask all MBOX interrupts */
+	W32(&dma->imimr, 0x1 | 0x2);
+
+	/* Let the other side know we are listening */
+	status_setbit(PCINET_UART_RX_ENABLED);
+}
+
+static void pciserial_putc(char c)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+#ifdef CONFIG_PCISERIAL_DEBUG
+	NS16550_putc(PCISERIAL_DEBUG_OUTPUT, c);
+#endif
+
+	/* We just drop all characters until the other side is ready
+	 * so that we do not stall the boot process */
+	if (status_remote_testbit(PCINET_UART_RX_ENABLED)) {
+		W32(&dma->omr0, c);
+		W32(&dma->odr, UART_RX_READY_DBELL);
+
+		/* Wait for other side to set TX_EMPTY */
+		while (!(R32(&dma->idr) & UART_TX_EMPTY_DBELL))
+			;
+
+		/* Clear TX_EMPTY interrupt */
+		W32(&dma->idr, UART_TX_EMPTY_DBELL);
+	}
+}
+
+static char pciserial_getc(void)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+	char ch;
+
+	/* Wait for the remote side of the uart to enable */
+	while (!status_remote_testbit(PCINET_UART_RX_ENABLED))
+		;
+
+	/* Wait for an RX_READY bit */
+	while (!(R32(&dma->idr) & UART_RX_READY_DBELL))
+		;
+
+	ch = R32(&dma->imr0) & 0xff;
+
+	W32(&dma->idr, UART_RX_READY_DBELL);
+	W32(&dma->odr, UART_TX_EMPTY_DBELL);
+
+	return ch;
+}
+
+static int pciserial_tstc(void)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	return R32(&dma->idr) & UART_RX_READY_DBELL;
+}
+
+#ifdef CONFIG_SERIAL_MULTI
+#error "No support for CONFIG_SERIAL_MULTI in the pciserial driver"
+#endif
+
+int serial_init(void)
+{
+	pciserial_init ();
+	return 0;
+}
+
+
+void serial_putc(const char c)
+{
+	if (c == '\n')
+		pciserial_putc('\r');
+
+	pciserial_putc(c);
+}
+
+void serial_putc_raw(const char c)
+{
+	pciserial_putc(c);
+}
+
+void serial_puts(const char *s)
+{
+	while (*s)
+		serial_putc(*s++);
+}
+
+int serial_getc(void)
+{
+	return pciserial_getc();
+}
+
+int serial_tstc(void)
+{
+	return pciserial_tstc();
+}
+
+void serial_setbrg(void)
+{
+	pciserial_init();
+}
+
+/* vim: set ts=8 sts=8 sw=8 noet tw=92: */
-- 
1.5.4.3



More information about the U-Boot mailing list