Subject: [PATCH] serial: DCC(JTAG) serial and console emulation support

From: [email-masked] (Daniel Walker)
Date: Tue, 5 Oct 2010 12:07:48 -0700

Many of JTAG debuggers for ARM support DCC protocol over JTAG
connection, which is very useful to debug hardwares which has no
serial port. This patch adds DCC serial emulation and the console
support. System timer based polling method is used for the
emulation of serial input interrupt handling.

Most of the code was taken from Hyok S. Choi original work, but the
inline assmebly needed some work and updating. It now supports ARMv7.
Also the description above is from Hyok also.

CC: Hyok S. Choi <[email-masked]>
CC: Tony Lindgren <[email-masked]>
Signed-off-by: Jeff Ohlstein <[email-masked]>
Signed-off-by: Daniel Walker <[email-masked]>
---
 drivers/serial/Kconfig      |   31 +++
 drivers/serial/Makefile     |    1 +
 drivers/serial/dcc.c        |  467 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/serial_core.h |    3 +
 4 files changed, 502 insertions(+), 0 deletions(-)
 create mode 100644 drivers/serial/dcc.c

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 12900f7..24ead62 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -495,6 +495,37 @@ config SERIAL_S3C2400
 	help
 	  Serial port support for the Samsung S3C2400 SoC
 
+config SERIAL_DCC
+	bool "JTAG ICE/ICD DCC serial port emulation support"
+	depends on ARM
+	select SERIAL_CORE
+	help
+	  This selects serial port emulation driver for ICE/ICD JTAG debugger
+	  (e.g. Trace32) for ARM architecture. You should make an terminal with
+	  DCC(JTAG1) protocol.
+
+	  if unsure, say N.
+ 
+config SERIAL_DCC_CONSOLE
+	bool "Support for console on JTAG ICE/ICD DCC"
+	depends on SERIAL_DCC
+	select SERIAL_CORE_CONSOLE
+	help
+	  Say Y here if you wish to use ICE/ICD JTAG DCC serial port emulation
+	  as the system console.
+
+	  if unsure, say N.
+
+config SERIAL_DCC_STDSERIAL
+	bool "Install JTAG ICE/ICD DCC as standard serial"
+	default y
+	depends on !SERIAL_8250 && SERIAL_DCC
+	help
+	  Say Y here if you want to install DCC driver as a normal serial port
+	  /dev/ttyS0 (major 4, minor 64). Otherwise, it appears as /dev/ttyJ0
+	  (major 4, minor 128) and can co-exist with other UARTs, such as
+	  8250/16C550 compatibles.
+
 config SERIAL_S3C2410
 	tristate "Samsung S3C2410 Serial port support"
 	depends on SERIAL_SAMSUNG && CPU_S3C2410
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 1ca4fd5..896c3f6 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_SERIAL_8250_MCA) += 8250_mca.o
 obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
 obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
 obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
+obj-$(CONFIG_SERIAL_DCC) += dcc.o
 obj-$(CONFIG_SERIAL_PXA) += pxa.o
 obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o
 obj-$(CONFIG_SERIAL_SA1100) += sa1100.o
diff --git a/drivers/serial/dcc.c b/drivers/serial/dcc.c
new file mode 100644
index 0000000..a1a74d3
--- /dev/null
+++ b/drivers/serial/dcc.c
@@ -0,0 +1,467 @@
+/*
+ *  linux/drivers/serial/dcc.c
+ *
+ *  serial port emulation driver for the JTAG DCC Terminal.
+ *
+ * DCC(JTAG1) protocol version for JTAG ICE/ICD Debuggers:
+ * 	Copyright (C) 2003, 2004, 2005 Hyok S. Choi (hyok.choi at samsung.com)
+ * 	SAMSUNG ELECTRONICS Co.,Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Changelog:
+ *   Oct-2003 Hyok S. Choi	Created
+ *   Feb-2004 Hyok S. Choi	Updated for serial_core.c and 2.6 kernel
+ *   Mar-2005 Hyok S. Choi	renamed from T32 to DCC
+ *   Apr-2006 Hyok S. Choi	revised including the MAJOR number
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/serial.h>
+#include <linux/console.h>
+#include <linux/sysrq.h>
+#include <linux/tty_flip.h>
+#include <linux/major.h>
+
+#include <linux/io.h>
+#include <linux/irq.h>
+
+#include <linux/serial_core.h>
+
+#define DCC_POLL_RUN		0
+#define DCC_POLL_STOP		1
+#define DCC_POLL_STOPPED	2
+
+static struct uart_port dcc_port;
+static struct delayed_work dcc_poll_task;
+static void dcc_poll(struct work_struct *work);
+static int dcc_poll_state = DCC_POLL_STOPPED;
+
+#define UART_NR			1	/* we have only one JTAG port */
+
+#ifdef CONFIG_SERIAL_DCC_STDSERIAL
+/* use ttyS for emulation of standard serial driver */
+#define SERIAL_DCC_NAME		"ttyS"
+#define SERIAL_DCC_MINOR	64
+#else
+/* use ttyJ0(128) */
+#define SERIAL_DCC_NAME		"ttyJ"
+#define SERIAL_DCC_MINOR	(64 + 64)
+#endif
+#define SERIAL_DCC_MAJOR	TTY_MAJOR
+
+/* DCC Status Bits */
+#define DCC_STATUS_RX		(1 << 30)
+#define DCC_STATUS_TX		(1 << 29) 
+
+static inline u32
+__dcc_getstatus(void)
+{
+	u32 __ret;
+
+	asm("mrc p14, 0, %0, c0, c1, 0	@ read comms ctrl reg"
+		: "=r" (__ret) : : "cc");
+
+	return __ret;
+}
+
+
+#if !defined(CONFIG_CPU_V7)
+static inline char
+__dcc_getchar(void)
+{
+	char __c;
+
+	asm("mrc p14, 0, %0, c0, c5, 0	@ read comms data reg"
+		: "=r" (__c) : : "cc");
+
+	return __c;
+}
+#else
+static inline char
+__dcc_getchar(void)
+{
+	char __c;
+
+	asm(
+	"get_wait:	mrc p14, 0, pc, c0, c1, 0                   \n\
+			bne get_wait                                    \n\
+			mrc p14, 0, %0, c0, c5, 0	@ read comms data reg"
+		: "=r" (__c) : : "cc");
+
+	return __c;
+}
+#endif
+
+#if !defined(CONFIG_CPU_V7)
+static inline void
+__dcc_putchar(char c)
+{
+	asm("mcr p14, 0, %0, c0, c5, 0	@ write a char"
+		: /* no output register */
+		: "r" (c) : "cc");
+}
+#else
+static inline void
+__dcc_putchar(char c)
+{
+	asm(
+	"put_wait:	mrc p14, 0, pc, c0, c1, 0                   \n\
+			bcs put_wait                                \n\
+			mcr p14, 0, %0, c0, c5, 0                   "
+	: : "r" (c) : "cc");
+}
+#endif
+
+static void
+dcc_putchar(struct uart_port *port, int ch)
+{
+	while (__dcc_getstatus() & DCC_STATUS_TX)
+		cpu_relax();
+	__dcc_putchar((char)(ch & 0xFF));
+}
+
+static inline void
+xmit_string(struct uart_port *port, char *p, int len)
+{
+	for ( ; len; len--, p++)
+		dcc_putchar(port, *p);
+}
+
+static inline void
+dcc_transmit_buffer(struct uart_port *port)
+{
+	struct circ_buf *xmit = &port->state->xmit;
+	int pendings = uart_circ_chars_pending(xmit);
+
+	if(pendings + xmit->tail > UART_XMIT_SIZE)
+	{
+		xmit_string(port, &(xmit->buf[xmit->tail]),
+			UART_XMIT_SIZE - xmit->tail);
+		xmit_string(port, &(xmit->buf[0]), xmit->head);
+	} else
+		xmit_string(port, &(xmit->buf[xmit->tail]), pendings);
+	
+	xmit->tail = (xmit->tail + pendings) & (UART_XMIT_SIZE-1);
+	port->icount.tx += pendings;
+}
+
+static inline void
+dcc_transmit_x_char(struct uart_port *port)
+{
+	dcc_putchar(port, port->x_char);
+	port->icount.tx++;
+	port->x_char = 0;
+}
+
+static void
+dcc_start_tx(struct uart_port *port)
+{
+	dcc_transmit_buffer(port);
+}
+
+static inline void
+dcc_rx_chars(struct uart_port *port)
+{
+	unsigned char ch;
+	struct tty_struct *tty = port->state->port.tty;
+
+	/*
+	 * check input.
+	 * checking JTAG flag is better to resolve the status test.
+	 * incount is NOT used for JTAG1 protocol.
+	 */
+
+	if (__dcc_getstatus() & DCC_STATUS_RX)
+	{
+
+		/* for JTAG 1 protocol, incount is always 1. */
+		ch = __dcc_getchar();
+
+		if (tty) {
+			tty_insert_flip_char(tty, ch, TTY_NORMAL);
+			port->icount.rx++;
+			tty_flip_buffer_push(tty);
+		}
+	}
+}
+
+static inline void
+dcc_overrun_chars(struct uart_port *port)
+{
+	port->icount.overrun++;
+}
+
+static inline void
+dcc_tx_chars(struct uart_port *port)
+{
+	struct circ_buf *xmit = &port->state->xmit;
+
+	if (port->x_char) {
+		dcc_transmit_x_char(port);
+		return; 
+	}
+
+	if (uart_circ_empty(xmit) || uart_tx_stopped(port))
+		return;
+
+	dcc_transmit_buffer(port);
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(port);
+}
+
+static void
+dcc_poll(struct work_struct *work)
+{
+	spin_lock(&dcc_port.lock);
+	
+	if (dcc_poll_state != DCC_POLL_RUN) {
+		dcc_poll_state = DCC_POLL_STOPPED;
+		goto dcc_poll_stop;
+	}
+
+	dcc_rx_chars(&dcc_port);
+	dcc_tx_chars(&dcc_port);
+
+	schedule_delayed_work(&dcc_poll_task, 1);
+
+dcc_poll_stop:
+	spin_unlock(&dcc_port.lock);
+}
+
+static unsigned int
+dcc_tx_empty(struct uart_port *port)
+{
+	return TIOCSER_TEMT;
+}
+
+static unsigned int
+dcc_get_mctrl(struct uart_port *port)
+{
+	return TIOCM_CTS | TIOCM_DSR | TIOCM_CD;
+}
+
+static int
+dcc_startup(struct uart_port *port)
+{
+	/* Initialize the work, and shcedule it. */
+	INIT_DELAYED_WORK(&dcc_poll_task, dcc_poll);
+	spin_lock(&port->lock);
+	if (dcc_poll_state != DCC_POLL_RUN)
+		dcc_poll_state = DCC_POLL_RUN;
+	schedule_delayed_work(&dcc_poll_task, 1);
+	spin_unlock(&port->lock);
+
+	return 0;
+}
+
+static void
+dcc_shutdown(struct uart_port *port)
+{
+	spin_lock(&port->lock);
+	dcc_poll_state = DCC_POLL_STOP;
+	spin_unlock(&port->lock);
+}
+
+static void
+dcc_set_termios(struct uart_port *port, struct ktermios *termios,
+		   struct ktermios *old)
+{
+	unsigned int baud, quot;
+
+	/*
+	 * We don't support parity, stop bits, or anything other
+	 * than 8 bits, so clear these termios flags.
+	 */
+	termios->c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | CREAD);
+	termios->c_cflag |= CS8;
+
+	/*
+	 * We don't appear to support any error conditions either.
+	 */
+	termios->c_iflag &= ~(INPCK | IGNPAR | IGNBRK | BRKINT);
+
+	/*
+	 * Ask the core to calculate the divisor for us.
+	 */
+	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); 
+	quot = uart_get_divisor(port, baud);
+
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+}
+
+static const char *
+dcc_type(struct uart_port *port)
+{
+	return port->type == PORT_DCC_JTAG1 ? "DCC" : NULL;
+}
+
+static int
+dcc_request_port(struct uart_port *port)
+{
+	return 0;
+}
+
+/*
+ * Configure/autoconfigure the port.
+ */
+static void
+dcc_config_port(struct uart_port *port, int flags)
+{
+	if (flags & UART_CONFIG_TYPE) {
+		port->type = PORT_DCC_JTAG1;
+		dcc_request_port(port);
+	}
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int
+dcc_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+	int ret = 0;
+	if (ser->type != PORT_UNKNOWN && ser->type != PORT_DCC_JTAG1)
+		ret = -EINVAL;
+	if (ser->irq < 0 || ser->irq >= NR_IRQS)
+		ret = -EINVAL;
+	if (ser->baud_base < 9600)
+		ret = -EINVAL;
+	return ret;
+}
+
+/* dummy operation handlers for uart_ops */
+static void
+dcc_dummy_ops(struct uart_port *port)
+{
+}
+static void
+dcc_dummy_ops_ui(struct uart_port *port, unsigned int ui)
+{
+}
+static void
+dcc_dummy_ops_i(struct uart_port *port, int i)
+{
+}
+
+static struct uart_ops dcc_pops = {
+	.tx_empty	= dcc_tx_empty,
+	.set_mctrl	= dcc_dummy_ops_ui,
+	.get_mctrl	= dcc_get_mctrl,
+	.stop_tx	= dcc_dummy_ops,
+	.start_tx	= dcc_start_tx,
+	.stop_rx	= dcc_dummy_ops,
+	.enable_ms	= dcc_dummy_ops,
+	.break_ctl	= dcc_dummy_ops_i,
+	.startup	= dcc_startup,
+	.shutdown	= dcc_shutdown,
+	.set_termios	= dcc_set_termios,
+	.type		= dcc_type,
+	.release_port	= dcc_dummy_ops,
+	.request_port	= dcc_request_port,
+	.config_port	= dcc_config_port,
+	.verify_port	= dcc_verify_port,
+};
+
+static struct uart_port dcc_port = {
+	.membase	= (char*)0x12345678,	/* we need these garbages */
+	.mapbase	= 0x12345678,		/* for serial_core.c */
+	.iotype		= UPIO_MEM,	
+#ifdef DCC_IRQ_USED
+	.irq		= DCC_IRQ,
+#else
+	.irq		= 0,
+#endif
+	.uartclk	= 14745600,
+	.fifosize	= 0,
+	.ops		= &dcc_pops,
+	.flags		= UPF_BOOT_AUTOCONF,
+	.line		= 0,
+};
+
+#ifdef CONFIG_SERIAL_DCC_CONSOLE
+static void
+dcc_console_write(struct console *co, const char *s, unsigned int count)
+{
+	uart_console_write(&dcc_port, s, count, dcc_putchar);
+}
+
+static int __init
+dcc_console_setup(struct console *co, char *options)
+{
+	struct uart_port *port = &dcc_port;
+	int baud = 9600;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+
+	if (options)
+		uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+	return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver dcc_reg;
+static struct console dcc_console = {
+	.name		= SERIAL_DCC_NAME,
+	.write		= dcc_console_write,
+	.device		= uart_console_device,
+	.setup		= dcc_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+	.data		= &dcc_reg,
+};
+
+static int __init
+dcc_console_init(void)
+{
+	register_console(&dcc_console);
+	return 0;
+}
+console_initcall(dcc_console_init);
+
+#define DCC_CONSOLE	&dcc_console
+#else
+#define DCC_CONSOLE	NULL
+#endif
+static struct uart_driver dcc_reg = {
+	.owner		= THIS_MODULE,
+	.driver_name	= SERIAL_DCC_NAME,
+	.dev_name	= SERIAL_DCC_NAME,
+	.major		= SERIAL_DCC_MAJOR,
+	.minor		= SERIAL_DCC_MINOR,
+	.nr		= UART_NR,
+	.cons		= DCC_CONSOLE,
+};
+
+static int __init
+dcc_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "DCC: JTAG1 Serial emulation driver driver $Revision: 1.1 $\n");
+
+	ret = uart_register_driver(&dcc_reg);
+
+	if (ret)
+		return ret;
+
+	uart_add_one_port(&dcc_reg, &dcc_port);
+
+	return 0;
+}
+
+__initcall(dcc_init);
+
+MODULE_DESCRIPTION("DCC(JTAG1) JTAG debugger console emulation driver");
+MODULE_AUTHOR("Hyok S. Choi <[email-masked]>");
+MODULE_SUPPORTED_DEVICE(SERIAL_DCC_NAME);
+MODULE_LICENSE("GPL");
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 563e234..a360c3a 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -196,6 +196,9 @@
 /* High Speed UART for Medfield */
 #define PORT_MFD	95
 
+/* DCC(JTAG) emulation port types */
+#define PORT_DCC_JTAG1	96
+
 #ifdef __KERNEL__
 
 #include <linux/compiler.h>
-- 
1.7.1