summary refs log tree commit diff
path: root/drivers/tty
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/Kconfig18
-rw-r--r--drivers/tty/Makefile1
-rw-r--r--drivers/tty/mips_ejtag_fdc.c1126
3 files changed, 1145 insertions, 0 deletions
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index b24aa010f68c..39469ca4231c 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -419,4 +419,22 @@ config DA_CONSOLE
 	help
 	  This enables a console on a Dash channel.
 
+config MIPS_EJTAG_FDC_TTY
+	bool "MIPS EJTAG Fast Debug Channel TTY"
+	depends on MIPS_CDMM
+	help
+	  This enables a TTY and console on the MIPS EJTAG Fast Debug Channels,
+	  if they are present. This can be useful when working with an EJTAG
+	  probe which supports it, to get console output and a login prompt via
+	  EJTAG without needing to connect a serial cable.
+
+	  TTY devices are named e.g. ttyFDC3c2 (for FDC channel 2 of the FDC on
+	  CPU3).
+
+	  The console can be enabled with console=fdc1 (for FDC channel 1 on all
+	  CPUs). Do not use the console unless there is a debug probe attached
+	  to drain the FDC TX FIFO.
+
+	  If unsure, say N.
+
 endif # TTY
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 58ad1c05b7f8..5817e2397463 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK)		+= synclink.o
 obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o
 obj-$(CONFIG_GOLDFISH_TTY)	+= goldfish.o
 obj-$(CONFIG_DA_TTY)		+= metag_da.o
+obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
 
 obj-y += ipwireless/
diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c
new file mode 100644
index 000000000000..51672cfe7e45
--- /dev/null
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -0,0 +1,1126 @@
+/*
+ * TTY driver for MIPS EJTAG Fast Debug Channels.
+ *
+ * Copyright (C) 2007-2015 Imagination Technologies Ltd
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for more
+ * details.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/serial.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/uaccess.h>
+
+#include <asm/cdmm.h>
+#include <asm/irq.h>
+
+/* Register offsets */
+#define REG_FDACSR	0x00	/* FDC Access Control and Status Register */
+#define REG_FDCFG	0x08	/* FDC Configuration Register */
+#define REG_FDSTAT	0x10	/* FDC Status Register */
+#define REG_FDRX	0x18	/* FDC Receive Register */
+#define REG_FDTX(N)	(0x20+0x8*(N))	/* FDC Transmit Register n (0..15) */
+
+/* Register fields */
+
+#define REG_FDCFG_TXINTTHRES_SHIFT	18
+#define REG_FDCFG_TXINTTHRES		(0x3 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_DISABLED	(0x0 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_EMPTY	(0x1 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_NOTFULL	(0x2 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_NEAREMPTY	(0x3 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_SHIFT	16
+#define REG_FDCFG_RXINTTHRES		(0x3 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_DISABLED	(0x0 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_FULL	(0x1 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_NOTEMPTY	(0x2 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_NEARFULL	(0x3 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_TXFIFOSIZE_SHIFT	8
+#define REG_FDCFG_TXFIFOSIZE		(0xff << REG_FDCFG_TXFIFOSIZE_SHIFT)
+#define REG_FDCFG_RXFIFOSIZE_SHIFT	0
+#define REG_FDCFG_RXFIFOSIZE		(0xff << REG_FDCFG_RXFIFOSIZE_SHIFT)
+
+#define REG_FDSTAT_TXCOUNT_SHIFT	24
+#define REG_FDSTAT_TXCOUNT		(0xff << REG_FDSTAT_TXCOUNT_SHIFT)
+#define REG_FDSTAT_RXCOUNT_SHIFT	16
+#define REG_FDSTAT_RXCOUNT		(0xff << REG_FDSTAT_RXCOUNT_SHIFT)
+#define REG_FDSTAT_RXCHAN_SHIFT		4
+#define REG_FDSTAT_RXCHAN		(0xf << REG_FDSTAT_RXCHAN_SHIFT)
+#define REG_FDSTAT_RXE			BIT(3)	/* Rx Empty */
+#define REG_FDSTAT_RXF			BIT(2)	/* Rx Full */
+#define REG_FDSTAT_TXE			BIT(1)	/* Tx Empty */
+#define REG_FDSTAT_TXF			BIT(0)	/* Tx Full */
+
+#define NUM_TTY_CHANNELS     16
+
+#define RX_BUF_SIZE 1024
+
+/*
+ * When the IRQ is unavailable, the FDC state must be polled for incoming data
+ * and space becoming available in TX FIFO.
+ */
+#define FDC_TTY_POLL (HZ / 50)
+
+struct mips_ejtag_fdc_tty;
+
+/**
+ * struct mips_ejtag_fdc_tty_port - Wrapper struct for FDC tty_port.
+ * @port:		TTY port data
+ * @driver:		TTY driver.
+ * @rx_lock:		Lock for rx_buf.
+ *			This protects between the hard interrupt and user
+ *			context. It's also held during read SWITCH operations.
+ * @rx_buf:		Read buffer.
+ * @xmit_lock:		Lock for xmit_*, and port.xmit_buf.
+ *			This protects between user context and kernel thread.
+ *			It is used from chars_in_buffer()/write_room() TTY
+ *			callbacks which are used during wait operations, so a
+ *			mutex is unsuitable.
+ * @xmit_cnt:		Size of xmit buffer contents.
+ * @xmit_head:		Head of xmit buffer where data is written.
+ * @xmit_tail:		Tail of xmit buffer where data is read.
+ * @xmit_empty:		Completion for xmit buffer being empty.
+ */
+struct mips_ejtag_fdc_tty_port {
+	struct tty_port			 port;
+	struct mips_ejtag_fdc_tty	*driver;
+	raw_spinlock_t			 rx_lock;
+	void				*rx_buf;
+	spinlock_t			 xmit_lock;
+	unsigned int			 xmit_cnt;
+	unsigned int			 xmit_head;
+	unsigned int			 xmit_tail;
+	struct completion		 xmit_empty;
+};
+
+/**
+ * struct mips_ejtag_fdc_tty - Driver data for FDC as a whole.
+ * @dev:		FDC device (for dev_*() logging).
+ * @driver:		TTY driver.
+ * @cpu:		CPU number for this FDC.
+ * @fdc_name:		FDC name (not for base of channel names).
+ * @driver_name:	Base of driver name.
+ * @ports:		Per-channel data.
+ * @waitqueue:		Wait queue for waiting for TX data, or for space in TX
+ *			FIFO.
+ * @lock:		Lock to protect FDCFG (interrupt enable).
+ * @thread:		KThread for writing out data to FDC.
+ * @reg:		FDC registers.
+ * @tx_fifo:		TX FIFO size.
+ * @xmit_size:		Size of each port's xmit buffer.
+ * @xmit_total:		Total number of bytes (from all ports) to transmit.
+ * @xmit_next:		Next port number to transmit from (round robin).
+ * @xmit_full:		Indicates TX FIFO is full, we're waiting for space.
+ * @irq:		IRQ number (negative if no IRQ).
+ * @removing:		Indicates the device is being removed and @poll_timer
+ *			should not be restarted.
+ * @poll_timer:		Timer for polling for interrupt events when @irq < 0.
+ */
+struct mips_ejtag_fdc_tty {
+	struct device			*dev;
+	struct tty_driver		*driver;
+	unsigned int			 cpu;
+	char				 fdc_name[16];
+	char				 driver_name[16];
+	struct mips_ejtag_fdc_tty_port	 ports[NUM_TTY_CHANNELS];
+	wait_queue_head_t		 waitqueue;
+	raw_spinlock_t			 lock;
+	struct task_struct		*thread;
+
+	void __iomem			*reg;
+	u8				 tx_fifo;
+
+	unsigned int			 xmit_size;
+	atomic_t			 xmit_total;
+	unsigned int			 xmit_next;
+	bool				 xmit_full;
+
+	int				 irq;
+	bool				 removing;
+	struct timer_list		 poll_timer;
+};
+
+/* Hardware access */
+
+static inline void mips_ejtag_fdc_write(struct mips_ejtag_fdc_tty *priv,
+					unsigned int offs, unsigned int data)
+{
+	iowrite32(data, priv->reg + offs);
+}
+
+static inline unsigned int mips_ejtag_fdc_read(struct mips_ejtag_fdc_tty *priv,
+					       unsigned int offs)
+{
+	return ioread32(priv->reg + offs);
+}
+
+/* Encoding of byte stream in FDC words */
+
+/**
+ * struct fdc_word - FDC word encoding some number of bytes of data.
+ * @word:		Raw FDC word.
+ * @bytes:		Number of bytes encoded by @word.
+ */
+struct fdc_word {
+	u32		word;
+	unsigned int	bytes;
+};
+
+/*
+ * This is a compact encoding which allows every 1 byte, 2 byte, and 3 byte
+ * sequence to be encoded in a single word, while allowing the majority of 4
+ * byte sequences (including all ASCII and common binary data) to be encoded in
+ * a single word too.
+ *    _______________________ _____________
+ *   |       FDC Word        |             |
+ *   |31-24|23-16|15-8 | 7-0 |    Bytes    |
+ *   |_____|_____|_____|_____|_____________|
+ *   |     |     |     |     |             |
+ *   |0x80 |0x80 |0x80 |  WW | WW          |
+ *   |0x81 |0x81 |  XX |  WW | WW XX       |
+ *   |0x82 |  YY |  XX |  WW | WW XX YY    |
+ *   |  ZZ |  YY |  XX |  WW | WW XX YY ZZ |
+ *   |_____|_____|_____|_____|_____________|
+ *
+ * Note that the 4-byte encoding can only be used where none of the other 3
+ * encodings match, otherwise it must fall back to the 3 byte encoding.
+ */
+
+/* ranges >= 1 && sizes[0] >= 1 */
+static struct fdc_word mips_ejtag_fdc_encode(const char **ptrs,
+					     unsigned int *sizes,
+					     unsigned int ranges)
+{
+	struct fdc_word word = { 0, 0 };
+	const char **ptrs_end = ptrs + ranges;
+
+	for (; ptrs < ptrs_end; ++ptrs) {
+		const char *ptr = *(ptrs++);
+		const char *end = ptr + *(sizes++);
+
+		for (; ptr < end; ++ptr) {
+			word.word |= (u8)*ptr << (8*word.bytes);
+			++word.bytes;
+			if (word.bytes == 4)
+				goto done;
+		}
+	}
+done:
+	/* Choose the appropriate encoding */
+	switch (word.bytes) {
+	case 4:
+		/* 4 byte encoding, but don't match the 1-3 byte encodings */
+		if ((word.word >> 8) != 0x808080 &&
+		    (word.word >> 16) != 0x8181 &&
+		    (word.word >> 24) != 0x82)
+			break;
+		/* Fall back to a 3 byte encoding */
+		word.bytes = 3;
+		word.word &= 0x00ffffff;
+	case 3:
+		/* 3 byte encoding */
+		word.word |= 0x82000000;
+		break;
+	case 2:
+		/* 2 byte encoding */
+		word.word |= 0x81810000;
+		break;
+	case 1:
+		/* 1 byte encoding */
+		word.word |= 0x80808000;
+		break;
+	}
+	return word;
+}
+
+static unsigned int mips_ejtag_fdc_decode(u32 word, char *buf)
+{
+	buf[0] = (u8)word;
+	word >>= 8;
+	if (word == 0x808080)
+		return 1;
+	buf[1] = (u8)word;
+	word >>= 8;
+	if (word == 0x8181)
+		return 2;
+	buf[2] = (u8)word;
+	word >>= 8;
+	if (word == 0x82)
+		return 3;
+	buf[3] = (u8)word;
+	return 4;
+}
+
+/* Console operations */
+
+/**
+ * struct mips_ejtag_fdc_console - Wrapper struct for FDC consoles.
+ * @cons:		Console object.
+ * @tty_drv:		TTY driver associated with this console.
+ * @lock:		Lock to protect concurrent access to other fields.
+ *			This is raw because it may be used very early.
+ * @initialised:	Whether the console is initialised.
+ * @regs:		Registers base address for each CPU.
+ */
+struct mips_ejtag_fdc_console {
+	struct console		 cons;
+	struct tty_driver	*tty_drv;
+	raw_spinlock_t		 lock;
+	bool			 initialised;
+	void __iomem		*regs[NR_CPUS];
+};
+
+/* Low level console write shared by early console and normal console */
+static void mips_ejtag_fdc_console_write(struct console *c, const char *s,
+					 unsigned int count)
+{
+	struct mips_ejtag_fdc_console *cons =
+		container_of(c, struct mips_ejtag_fdc_console, cons);
+	void __iomem *regs;
+	struct fdc_word word;
+	unsigned long flags;
+	unsigned int i, buf_len, cpu;
+	bool done_cr = false;
+	char buf[4];
+	const char *buf_ptr = buf;
+	/* Number of bytes of input data encoded up to each byte in buf */
+	u8 inc[4];
+
+	local_irq_save(flags);
+	cpu = smp_processor_id();
+	regs = cons->regs[cpu];
+	/* First console output on this CPU? */
+	if (!regs) {
+		regs = mips_cdmm_early_probe(0xfd);
+		cons->regs[cpu] = regs;
+	}
+	/* Already tried and failed to find FDC on this CPU? */
+	if (IS_ERR(regs))
+		goto out;
+	while (count) {
+		/*
+		 * Copy the next few characters to a buffer so we can inject
+		 * carriage returns before newlines.
+		 */
+		for (buf_len = 0, i = 0; buf_len < 4 && i < count; ++buf_len) {
+			if (s[i] == '\n' && !done_cr) {
+				buf[buf_len] = '\r';
+				done_cr = true;
+			} else {
+				buf[buf_len] = s[i];
+				done_cr = false;
+				++i;
+			}
+			inc[buf_len] = i;
+		}
+		word = mips_ejtag_fdc_encode(&buf_ptr, &buf_len, 1);
+		count -= inc[word.bytes - 1];
+		s += inc[word.bytes - 1];
+
+		/* Busy wait until there's space in fifo */
+		while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF)
+			;
+		iowrite32(word.word, regs + REG_FDTX(c->index));
+	}
+out:
+	local_irq_restore(flags);
+}
+
+static struct tty_driver *mips_ejtag_fdc_console_device(struct console *c,
+							int *index)
+{
+	struct mips_ejtag_fdc_console *cons =
+		container_of(c, struct mips_ejtag_fdc_console, cons);
+
+	*index = c->index;
+	return cons->tty_drv;
+}
+
+/* Initialise an FDC console (early or normal */
+static int __init mips_ejtag_fdc_console_init(struct mips_ejtag_fdc_console *c)
+{
+	void __iomem *regs;
+	unsigned long flags;
+	int ret = 0;
+
+	raw_spin_lock_irqsave(&c->lock, flags);
+	/* Don't init twice */
+	if (c->initialised)
+		goto out;
+	/* Look for the FDC device */
+	regs = mips_cdmm_early_probe(0xfd);
+	if (IS_ERR(regs)) {
+		ret = PTR_ERR(regs);
+		goto out;
+	}
+
+	c->initialised = true;
+	c->regs[smp_processor_id()] = regs;
+	register_console(&c->cons);
+out:
+	raw_spin_unlock_irqrestore(&c->lock, flags);
+	return ret;
+}
+
+static struct mips_ejtag_fdc_console mips_ejtag_fdc_con = {
+	.cons	= {
+		.name	= "fdc",
+		.write	= mips_ejtag_fdc_console_write,
+		.device	= mips_ejtag_fdc_console_device,
+		.flags	= CON_PRINTBUFFER,
+		.index	= -1,
+	},
+	.lock	= __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_con.lock),
+};
+
+/* TTY RX/TX operations */
+
+/**
+ * mips_ejtag_fdc_put_chan() - Write out a block of channel data.
+ * @priv:	Pointer to driver private data.
+ * @chan:	Channel number.
+ *
+ * Write a single block of data out to the debug adapter. If the circular buffer
+ * is wrapped then only the first block is written.
+ *
+ * Returns:	The number of bytes that were written.
+ */
+static unsigned int mips_ejtag_fdc_put_chan(struct mips_ejtag_fdc_tty *priv,
+					    unsigned int chan)
+{
+	struct mips_ejtag_fdc_tty_port *dport;
+	struct tty_struct *tty;
+	const char *ptrs[2];
+	unsigned int sizes[2] = { 0 };
+	struct fdc_word word = { .bytes = 0 };
+	unsigned long flags;
+
+	dport = &priv->ports[chan];
+	spin_lock(&dport->xmit_lock);
+	if (dport->xmit_cnt) {
+		ptrs[0] = dport->port.xmit_buf + dport->xmit_tail;
+		sizes[0] = min_t(unsigned int,
+				 priv->xmit_size - dport->xmit_tail,
+				 dport->xmit_cnt);
+		ptrs[1] = dport->port.xmit_buf;
+		sizes[1] = dport->xmit_cnt - sizes[0];
+		word = mips_ejtag_fdc_encode(ptrs, sizes, 1 + !!sizes[1]);
+
+		dev_dbg(priv->dev, "%s%u: out %08x: \"%*pE%*pE\"\n",
+			priv->driver_name, chan, word.word,
+			min_t(int, word.bytes, sizes[0]), ptrs[0],
+			max_t(int, 0, word.bytes - sizes[0]), ptrs[1]);
+
+		local_irq_save(flags);
+		/* Maybe we raced with the console and TX FIFO is full */
+		if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF)
+			word.bytes = 0;
+		else
+			mips_ejtag_fdc_write(priv, REG_FDTX(chan), word.word);
+		local_irq_restore(flags);
+
+		dport->xmit_cnt -= word.bytes;
+		if (!dport->xmit_cnt) {
+			/* Reset pointers to avoid wraps */
+			dport->xmit_head = 0;
+			dport->xmit_tail = 0;
+			complete(&dport->xmit_empty);
+		} else {
+			dport->xmit_tail += word.bytes;
+			if (dport->xmit_tail >= priv->xmit_size)
+				dport->xmit_tail -= priv->xmit_size;
+		}
+		atomic_sub(word.bytes, &priv->xmit_total);
+	}
+	spin_unlock(&dport->xmit_lock);
+
+	/* If we've made more data available, wake up tty */
+	if (sizes[0] && word.bytes) {
+		tty = tty_port_tty_get(&dport->port);
+		if (tty) {
+			tty_wakeup(tty);
+			tty_kref_put(tty);
+		}
+	}
+
+	return word.bytes;
+}
+
+/**
+ * mips_ejtag_fdc_put() - Kernel thread to write out channel data to FDC.
+ * @arg:	Driver pointer.
+ *
+ * This kernel thread runs while @priv->xmit_total != 0, and round robins the
+ * channels writing out blocks of buffered data to the FDC TX FIFO.
+ */
+static int mips_ejtag_fdc_put(void *arg)
+{
+	struct mips_ejtag_fdc_tty *priv = arg;
+	struct mips_ejtag_fdc_tty_port *dport;
+	unsigned int ret;
+	u32 cfg;
+
+	__set_current_state(TASK_RUNNING);
+	while (!kthread_should_stop()) {
+		/* Wait for data to actually write */
+		wait_event_interruptible(priv->waitqueue,
+					 atomic_read(&priv->xmit_total) ||
+					 kthread_should_stop());
+		if (kthread_should_stop())
+			break;
+
+		/* Wait for TX FIFO space to write data */
+		raw_spin_lock_irq(&priv->lock);
+		if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF) {
+			priv->xmit_full = true;
+			if (priv->irq >= 0) {
+				/* Enable TX interrupt */
+				cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+				cfg &= ~REG_FDCFG_TXINTTHRES;
+				cfg |= REG_FDCFG_TXINTTHRES_NOTFULL;
+				mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+			}
+		}
+		raw_spin_unlock_irq(&priv->lock);
+		wait_event_interruptible(priv->waitqueue,
+					 !(mips_ejtag_fdc_read(priv, REG_FDSTAT)
+					   & REG_FDSTAT_TXF) ||
+					 kthread_should_stop());
+		if (kthread_should_stop())
+			break;
+
+		/* Find next channel with data to output */
+		for (;;) {
+			dport = &priv->ports[priv->xmit_next];
+			spin_lock(&dport->xmit_lock);
+			ret = dport->xmit_cnt;
+			spin_unlock(&dport->xmit_lock);
+			if (ret)
+				break;
+			/* Round robin */
+			++priv->xmit_next;
+			if (priv->xmit_next >= NUM_TTY_CHANNELS)
+				priv->xmit_next = 0;
+		}
+
+		/* Try writing data to the chosen channel */
+		ret = mips_ejtag_fdc_put_chan(priv, priv->xmit_next);
+
+		/*
+		 * If anything was output, move on to the next channel so as not
+		 * to starve other channels.
+		 */
+		if (ret) {
+			++priv->xmit_next;
+			if (priv->xmit_next >= NUM_TTY_CHANNELS)
+				priv->xmit_next = 0;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * mips_ejtag_fdc_handle() - Handle FDC events.
+ * @priv:	Pointer to driver private data.
+ *
+ * Handle FDC events, such as new incoming data which needs draining out of the
+ * RX FIFO and feeding into the appropriate TTY ports, and space becoming
+ * available in the TX FIFO which would allow more data to be written out.
+ */
+static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv)
+{
+	struct mips_ejtag_fdc_tty_port *dport;
+	unsigned int stat, channel, data, cfg, i, flipped;
+	int len;
+	char buf[4];
+
+	for (;;) {
+		/* Find which channel the next FDC word is destined for */
+		stat = mips_ejtag_fdc_read(priv, REG_FDSTAT);
+		if (stat & REG_FDSTAT_RXE)
+			break;
+		channel = (stat & REG_FDSTAT_RXCHAN) >> REG_FDSTAT_RXCHAN_SHIFT;
+		dport = &priv->ports[channel];
+
+		/* Read out the FDC word, decode it, and pass to tty layer */
+		raw_spin_lock(&dport->rx_lock);
+		data = mips_ejtag_fdc_read(priv, REG_FDRX);
+
+		/* Check the port isn't being shut down */
+		if (!dport->rx_buf)
+			goto unlock;
+
+		len = mips_ejtag_fdc_decode(data, buf);
+		dev_dbg(priv->dev, "%s%u: in  %08x: \"%*pE\"\n",
+			priv->driver_name, channel, data, len, buf);
+
+		flipped = 0;
+		for (i = 0; i < len; ++i)
+			flipped += tty_insert_flip_char(&dport->port, buf[i],
+							TTY_NORMAL);
+		if (flipped)
+			tty_flip_buffer_push(&dport->port);
+unlock:
+		raw_spin_unlock(&dport->rx_lock);
+	}
+
+	/* If TX FIFO no longer full we may be able to write more data */
+	raw_spin_lock(&priv->lock);
+	if (priv->xmit_full && !(stat & REG_FDSTAT_TXF)) {
+		priv->xmit_full = false;
+
+		/* Disable TX interrupt */
+		cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+		cfg &= ~REG_FDCFG_TXINTTHRES;
+		cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+		mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+
+		/* Wait the kthread so it can try writing more data */
+		wake_up_interruptible(&priv->waitqueue);
+	}
+	raw_spin_unlock(&priv->lock);
+}
+
+/**
+ * mips_ejtag_fdc_isr() - Interrupt handler.
+ * @irq:	IRQ number.
+ * @dev_id:	Pointer to driver private data.
+ *
+ * This is the interrupt handler, used when interrupts are enabled.
+ *
+ * It simply triggers the common FDC handler code.
+ *
+ * Returns:	IRQ_HANDLED if an FDC interrupt was pending.
+ *		IRQ_NONE otherwise.
+ */
+static irqreturn_t mips_ejtag_fdc_isr(int irq, void *dev_id)
+{
+	struct mips_ejtag_fdc_tty *priv = dev_id;
+
+	/*
+	 * We're not using proper per-cpu IRQs, so we must be careful not to
+	 * handle IRQs on CPUs we're not interested in.
+	 *
+	 * Ideally proper per-cpu IRQ handlers could be used, but that doesn't
+	 * fit well with the whole sharing of the main CPU IRQ lines. When we
+	 * have something with a GIC that routes the FDC IRQs (i.e. no sharing
+	 * between handlers) then support could be added more easily.
+	 */
+	if (smp_processor_id() != priv->cpu)
+		return IRQ_NONE;
+
+	/* If no FDC interrupt pending, it wasn't for us */
+	if (!(read_c0_cause() & CAUSEF_FDCI))
+		return IRQ_NONE;
+
+	mips_ejtag_fdc_handle(priv);
+	return IRQ_HANDLED;
+}
+
+/**
+ * mips_ejtag_fdc_tty_timer() - Poll FDC for incoming data.
+ * @opaque:	Pointer to driver private data.
+ *
+ * This is the timer handler for when interrupts are disabled and polling the
+ * FDC state is required.
+ *
+ * It simply triggers the common FDC handler code and arranges for further
+ * polling.
+ */
+static void mips_ejtag_fdc_tty_timer(unsigned long opaque)
+{
+	struct mips_ejtag_fdc_tty *priv = (void *)opaque;
+
+	mips_ejtag_fdc_handle(priv);
+	if (!priv->removing)
+		mod_timer_pinned(&priv->poll_timer, jiffies + FDC_TTY_POLL);
+}
+
+/* TTY Port operations */
+
+static int mips_ejtag_fdc_tty_port_activate(struct tty_port *port,
+					    struct tty_struct *tty)
+{
+	struct mips_ejtag_fdc_tty_port *dport =
+		container_of(port, struct mips_ejtag_fdc_tty_port, port);
+	void *rx_buf;
+
+	/* Allocate the buffer we use for writing data */
+	if (tty_port_alloc_xmit_buf(port) < 0)
+		goto err;
+
+	/* Allocate the buffer we use for reading data */
+	rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL);
+	if (!rx_buf)
+		goto err_free_xmit;
+
+	raw_spin_lock_irq(&dport->rx_lock);
+	dport->rx_buf = rx_buf;
+	raw_spin_unlock_irq(&dport->rx_lock);
+
+	return 0;
+err_free_xmit:
+	tty_port_free_xmit_buf(port);
+err:
+	return -ENOMEM;
+}
+
+static void mips_ejtag_fdc_tty_port_shutdown(struct tty_port *port)
+{
+	struct mips_ejtag_fdc_tty_port *dport =
+		container_of(port, struct mips_ejtag_fdc_tty_port, port);
+	struct mips_ejtag_fdc_tty *priv = dport->driver;
+	void *rx_buf;
+	unsigned int count;
+
+	spin_lock(&dport->xmit_lock);
+	count = dport->xmit_cnt;
+	spin_unlock(&dport->xmit_lock);
+	if (count) {
+		/*
+		 * There's still data to write out, so wake and wait for the
+		 * writer thread to drain the buffer.
+		 */
+		wake_up_interruptible(&priv->waitqueue);
+		wait_for_completion(&dport->xmit_empty);
+	}
+
+	/* Null the read buffer (timer could still be running!) */
+	raw_spin_lock_irq(&dport->rx_lock);
+	rx_buf = dport->rx_buf;
+	dport->rx_buf = NULL;
+	raw_spin_unlock_irq(&dport->rx_lock);
+	/* Free the read buffer */
+	kfree(rx_buf);
+
+	/* Free the write buffer */
+	tty_port_free_xmit_buf(port);
+}
+
+static const struct tty_port_operations mips_ejtag_fdc_tty_port_ops = {
+	.activate	= mips_ejtag_fdc_tty_port_activate,
+	.shutdown	= mips_ejtag_fdc_tty_port_shutdown,
+};
+
+/* TTY operations */
+
+static int mips_ejtag_fdc_tty_install(struct tty_driver *driver,
+				      struct tty_struct *tty)
+{
+	struct mips_ejtag_fdc_tty *priv = driver->driver_state;
+
+	tty->driver_data = &priv->ports[tty->index];
+	return tty_port_install(&priv->ports[tty->index].port, driver, tty);
+}
+
+static int mips_ejtag_fdc_tty_open(struct tty_struct *tty, struct file *filp)
+{
+	return tty_port_open(tty->port, tty, filp);
+}
+
+static void mips_ejtag_fdc_tty_close(struct tty_struct *tty, struct file *filp)
+{
+	return tty_port_close(tty->port, tty, filp);
+}
+
+static void mips_ejtag_fdc_tty_hangup(struct tty_struct *tty)
+{
+	struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+	struct mips_ejtag_fdc_tty *priv = dport->driver;
+
+	/* Drop any data in the xmit buffer */
+	spin_lock(&dport->xmit_lock);
+	if (dport->xmit_cnt) {
+		atomic_sub(dport->xmit_cnt, &priv->xmit_total);
+		dport->xmit_cnt = 0;
+		dport->xmit_head = 0;
+		dport->xmit_tail = 0;
+		complete(&dport->xmit_empty);
+	}
+	spin_unlock(&dport->xmit_lock);
+
+	tty_port_hangup(tty->port);
+}
+
+static int mips_ejtag_fdc_tty_write(struct tty_struct *tty,
+				    const unsigned char *buf, int total)
+{
+	int count, block;
+	struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+	struct mips_ejtag_fdc_tty *priv = dport->driver;
+
+	/*
+	 * Write to output buffer.
+	 *
+	 * The reason that we asynchronously write the buffer is because if we
+	 * were to write the buffer synchronously then because the channels are
+	 * per-CPU the buffer would be written to the channel of whatever CPU
+	 * we're running on.
+	 *
+	 * What we actually want to happen is have all input and output done on
+	 * one CPU.
+	 */
+	spin_lock(&dport->xmit_lock);
+	/* Work out how many bytes we can write to the xmit buffer */
+	total = min(total, (int)(priv->xmit_size - dport->xmit_cnt));
+	atomic_add(total, &priv->xmit_total);
+	dport->xmit_cnt += total;
+	/* Write the actual bytes (may need splitting if it wraps) */
+	for (count = total; count; count -= block) {
+		block = min(count, (int)(priv->xmit_size - dport->xmit_head));
+		memcpy(dport->port.xmit_buf + dport->xmit_head, buf, block);
+		dport->xmit_head += block;
+		if (dport->xmit_head >= priv->xmit_size)
+			dport->xmit_head -= priv->xmit_size;
+		buf += block;
+	}
+	count = dport->xmit_cnt;
+	/* Xmit buffer no longer empty? */
+	if (count)
+		reinit_completion(&dport->xmit_empty);
+	spin_unlock(&dport->xmit_lock);
+
+	/* Wake up the kthread */
+	if (total)
+		wake_up_interruptible(&priv->waitqueue);
+	return total;
+}
+
+static int mips_ejtag_fdc_tty_write_room(struct tty_struct *tty)
+{
+	struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+	struct mips_ejtag_fdc_tty *priv = dport->driver;
+	int room;
+
+	/* Report the space in the xmit buffer */
+	spin_lock(&dport->xmit_lock);
+	room = priv->xmit_size - dport->xmit_cnt;
+	spin_unlock(&dport->xmit_lock);
+
+	return room;
+}
+
+static int mips_ejtag_fdc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+	int chars;
+
+	/* Report the number of bytes in the xmit buffer */
+	spin_lock(&dport->xmit_lock);
+	chars = dport->xmit_cnt;
+	spin_unlock(&dport->xmit_lock);
+
+	return chars;
+}
+
+static const struct tty_operations mips_ejtag_fdc_tty_ops = {
+	.install		= mips_ejtag_fdc_tty_install,
+	.open			= mips_ejtag_fdc_tty_open,
+	.close			= mips_ejtag_fdc_tty_close,
+	.hangup			= mips_ejtag_fdc_tty_hangup,
+	.write			= mips_ejtag_fdc_tty_write,
+	.write_room		= mips_ejtag_fdc_tty_write_room,
+	.chars_in_buffer	= mips_ejtag_fdc_tty_chars_in_buffer,
+};
+
+static int mips_ejtag_fdc_tty_probe(struct mips_cdmm_device *dev)
+{
+	int ret, nport;
+	struct mips_ejtag_fdc_tty_port *dport;
+	struct mips_ejtag_fdc_tty *priv;
+	struct tty_driver *driver;
+	unsigned int cfg, tx_fifo;
+
+	priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->cpu = dev->cpu;
+	priv->dev = &dev->dev;
+	mips_cdmm_set_drvdata(dev, priv);
+	atomic_set(&priv->xmit_total, 0);
+	raw_spin_lock_init(&priv->lock);
+
+	priv->reg = devm_ioremap_nocache(priv->dev, dev->res.start,
+					 resource_size(&dev->res));
+	if (!priv->reg) {
+		dev_err(priv->dev, "ioremap failed for resource %pR\n",
+			&dev->res);
+		return -ENOMEM;
+	}
+
+	cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+	tx_fifo = (cfg & REG_FDCFG_TXFIFOSIZE) >> REG_FDCFG_TXFIFOSIZE_SHIFT;
+	/* Disable interrupts */
+	cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+	cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+	cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+	mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+
+	/* Make each port's xmit FIFO big enough to fill FDC TX FIFO */
+	priv->xmit_size = min(tx_fifo * 4, (unsigned int)SERIAL_XMIT_SIZE);
+
+	driver = tty_alloc_driver(NUM_TTY_CHANNELS, TTY_DRIVER_REAL_RAW);
+	if (IS_ERR(driver))
+		return PTR_ERR(driver);
+	priv->driver = driver;
+
+	driver->driver_name = "ejtag_fdc";
+	snprintf(priv->fdc_name, sizeof(priv->fdc_name), "ttyFDC%u", dev->cpu);
+	snprintf(priv->driver_name, sizeof(priv->driver_name), "%sc",
+		 priv->fdc_name);
+	driver->name = priv->driver_name;
+	driver->major = 0; /* Auto-allocate */
+	driver->minor_start = 0;
+	driver->type = TTY_DRIVER_TYPE_SERIAL;
+	driver->subtype = SERIAL_TYPE_NORMAL;
+	driver->init_termios = tty_std_termios;
+	driver->init_termios.c_cflag |= CLOCAL;
+	driver->driver_state = priv;
+
+	tty_set_operations(driver, &mips_ejtag_fdc_tty_ops);
+	for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
+		dport = &priv->ports[nport];
+		dport->driver = priv;
+		tty_port_init(&dport->port);
+		dport->port.ops = &mips_ejtag_fdc_tty_port_ops;
+		raw_spin_lock_init(&dport->rx_lock);
+		spin_lock_init(&dport->xmit_lock);
+		/* The xmit buffer starts empty, i.e. completely written */
+		init_completion(&dport->xmit_empty);
+		complete(&dport->xmit_empty);
+	}
+
+	/* Set up the console */
+	mips_ejtag_fdc_con.regs[dev->cpu] = priv->reg;
+	if (dev->cpu == 0)
+		mips_ejtag_fdc_con.tty_drv = driver;
+
+	init_waitqueue_head(&priv->waitqueue);
+	priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name);
+	if (IS_ERR(priv->thread)) {
+		ret = PTR_ERR(priv->thread);
+		dev_err(priv->dev, "Couldn't create kthread (%d)\n", ret);
+		goto err_destroy_ports;
+	}
+	/*
+	 * Bind the writer thread to the right CPU so it can't migrate.
+	 * The channels are per-CPU and we want all channel I/O to be on a
+	 * single predictable CPU.
+	 */
+	kthread_bind(priv->thread, dev->cpu);
+	wake_up_process(priv->thread);
+
+	/* Look for an FDC IRQ */
+	priv->irq = -1;
+	if (get_c0_fdc_int)
+		priv->irq = get_c0_fdc_int();
+
+	/* Try requesting the IRQ */
+	if (priv->irq >= 0) {
+		/*
+		 * IRQF_SHARED, IRQF_NO_SUSPEND: The FDC IRQ may be shared with
+		 * other local interrupts such as the timer which sets
+		 * IRQF_TIMER (including IRQF_NO_SUSPEND).
+		 *
+		 * IRQF_NO_THREAD: The FDC IRQ isn't individually maskable so it
+		 * cannot be deferred and handled by a thread on RT kernels. For
+		 * this reason any spinlocks used from the ISR are raw.
+		 */
+		ret = devm_request_irq(priv->dev, priv->irq, mips_ejtag_fdc_isr,
+				       IRQF_PERCPU | IRQF_SHARED |
+				       IRQF_NO_THREAD | IRQF_NO_SUSPEND,
+				       priv->fdc_name, priv);
+		if (ret)
+			priv->irq = -1;
+	}
+	if (priv->irq >= 0) {
+		/* IRQ is usable, enable RX interrupt */
+		raw_spin_lock_irq(&priv->lock);
+		cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+		cfg &= ~REG_FDCFG_RXINTTHRES;
+		cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY;
+		mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+		raw_spin_unlock_irq(&priv->lock);
+	} else {
+		/* If we didn't get an usable IRQ, poll instead */
+		setup_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer,
+			    (unsigned long)priv);
+		priv->poll_timer.expires = jiffies + FDC_TTY_POLL;
+		/*
+		 * Always attach the timer to the right CPU. The channels are
+		 * per-CPU so all polling should be from a single CPU.
+		 */
+		add_timer_on(&priv->poll_timer, dev->cpu);
+
+		dev_info(priv->dev, "No usable IRQ, polling enabled\n");
+	}
+
+	ret = tty_register_driver(driver);
+	if (ret < 0) {
+		dev_err(priv->dev, "Couldn't install tty driver (%d)\n", ret);
+		goto err_stop_irq;
+	}
+
+	return 0;
+
+err_stop_irq:
+	if (priv->irq >= 0) {
+		raw_spin_lock_irq(&priv->lock);
+		cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+		/* Disable interrupts */
+		cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+		cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+		cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+		mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+		raw_spin_unlock_irq(&priv->lock);
+	} else {
+		priv->removing = true;
+		del_timer_sync(&priv->poll_timer);
+	}
+	kthread_stop(priv->thread);
+err_destroy_ports:
+	if (dev->cpu == 0)
+		mips_ejtag_fdc_con.tty_drv = NULL;
+	for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
+		dport = &priv->ports[nport];
+		tty_port_destroy(&dport->port);
+	}
+	put_tty_driver(priv->driver);
+	return ret;
+}
+
+static int mips_ejtag_fdc_tty_remove(struct mips_cdmm_device *dev)
+{
+	struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev);
+	struct mips_ejtag_fdc_tty_port *dport;
+	int nport;
+	unsigned int cfg;
+
+	if (priv->irq >= 0) {
+		raw_spin_lock_irq(&priv->lock);
+		cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+		/* Disable interrupts */
+		cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+		cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+		cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+		mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+		raw_spin_unlock_irq(&priv->lock);
+	} else {
+		priv->removing = true;
+		del_timer_sync(&priv->poll_timer);
+	}
+	kthread_stop(priv->thread);
+	if (dev->cpu == 0)
+		mips_ejtag_fdc_con.tty_drv = NULL;
+	tty_unregister_driver(priv->driver);
+	for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
+		dport = &priv->ports[nport];
+		tty_port_destroy(&dport->port);
+	}
+	put_tty_driver(priv->driver);
+	return 0;
+}
+
+static int mips_ejtag_fdc_tty_cpu_down(struct mips_cdmm_device *dev)
+{
+	struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev);
+	unsigned int cfg;
+
+	if (priv->irq >= 0) {
+		raw_spin_lock_irq(&priv->lock);
+		cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+		/* Disable interrupts */
+		cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+		cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+		cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+		mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+		raw_spin_unlock_irq(&priv->lock);
+	} else {
+		priv->removing = true;
+		del_timer_sync(&priv->poll_timer);
+	}
+	kthread_stop(priv->thread);
+
+	return 0;
+}
+
+static int mips_ejtag_fdc_tty_cpu_up(struct mips_cdmm_device *dev)
+{
+	struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev);
+	unsigned int cfg;
+	int ret = 0;
+
+	if (priv->irq >= 0) {
+		/*
+		 * IRQ is usable, enable RX interrupt
+		 * This must be before kthread is restarted, as kthread may
+		 * enable TX interrupt.
+		 */
+		raw_spin_lock_irq(&priv->lock);
+		cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+		cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+		cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+		cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY;
+		mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+		raw_spin_unlock_irq(&priv->lock);
+	} else {
+		/* Restart poll timer */
+		priv->removing = false;
+		add_timer_on(&priv->poll_timer, dev->cpu);
+	}
+
+	/* Restart the kthread */
+	priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name);
+	if (IS_ERR(priv->thread)) {
+		ret = PTR_ERR(priv->thread);
+		dev_err(priv->dev, "Couldn't re-create kthread (%d)\n", ret);
+		goto out;
+	}
+	/* Bind it back to the right CPU and set it off */
+	kthread_bind(priv->thread, dev->cpu);
+	wake_up_process(priv->thread);
+out:
+	return ret;
+}
+
+static struct mips_cdmm_device_id mips_ejtag_fdc_tty_ids[] = {
+	{ .type = 0xfd },
+	{ }
+};
+
+static struct mips_cdmm_driver mips_ejtag_fdc_tty_driver = {
+	.drv		= {
+		.name	= "mips_ejtag_fdc",
+	},
+	.probe		= mips_ejtag_fdc_tty_probe,
+	.remove		= mips_ejtag_fdc_tty_remove,
+	.cpu_down	= mips_ejtag_fdc_tty_cpu_down,
+	.cpu_up		= mips_ejtag_fdc_tty_cpu_up,
+	.id_table	= mips_ejtag_fdc_tty_ids,
+};
+module_mips_cdmm_driver(mips_ejtag_fdc_tty_driver);
+
+static int __init mips_ejtag_fdc_init_console(void)
+{
+	return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_con);
+}
+console_initcall(mips_ejtag_fdc_init_console);