All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/9] Add MIPS EJTAG Fast Debug Channel TTY driver
@ 2015-01-29 11:14 ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper, Greg Kroah-Hartman, Jiri Slaby, Jason Wessel,
	kgdb-bugreport

This patchset adds a TTY, console, and KGDB driver for the MIPS Fast
Debug Channel (FDC) hardware, for communicating with a debugger via an
EJTAG probe. 16 TTY ports are created per FDC device, corresponding to
the 16 FDC channels. Each VPE usually has its own FDC instance.

This patchset depends on my recent CDMM bus patchset (the FDC is in the
per-CPU CDMM region), and my recent MIPS timer & perf counter IRQ
sharing patchset (the FDC IRQ is a local CPU IRQ which may similarly
share CPU IRQ lines with the other local IRQs).

Patches 1 to 6 add the necessary architecture bits for the FDC
interrupt, and a workaround in the MIPS idle code to avoid the wait
instruction on certain cores if FDC driver is enabled.

Finally patches 7-9 add the main TTY/console driver, wire up some early
console code, and implement KGDB operations & magic sysrq.

Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Jason Wessel <jason.wessel@windriver.com>
Cc: linux-mips@linux-mips.org
Cc: kgdb-bugreport@lists.sourceforge.net

James Hogan (9):
  MIPS: Add architectural FDC IRQ fields
  MIPS: Read CPU IRQ line that FDC to routed to
  irqchip: mips-gic: Don't treat FDC IRQ as percpu devid
  irqchip: mips-gic: Add function for retrieving FDC IRQ
  MIPS: Malta: Implement get_c0_fdc_int()
  MIPS: idle: Workaround wait + FDC problems
  tty: Add MIPS EJTAG Fast Debug Channel TTY driver
  MIPS, ttyFDC: Add early FDC console support
  ttyFDC: Implement KGDB IO operations.

 arch/mips/include/asm/cdmm.h     |   11 +
 arch/mips/include/asm/irq.h      |    3 +
 arch/mips/include/asm/mipsregs.h |    4 +
 arch/mips/kernel/idle.c          |   13 +-
 arch/mips/kernel/setup.c         |    2 +
 arch/mips/kernel/traps.c         |   11 +
 arch/mips/mti-malta/malta-time.c |   16 +
 drivers/irqchip/irq-mips-gic.c   |   38 +-
 drivers/tty/Kconfig              |   47 ++
 drivers/tty/Makefile             |    1 +
 drivers/tty/mips_ejtag_fdc.c     | 1303 ++++++++++++++++++++++++++++++++++++++
 include/linux/irqchip/mips-gic.h |    1 +
 12 files changed, 1443 insertions(+), 7 deletions(-)
 create mode 100644 drivers/tty/mips_ejtag_fdc.c

-- 
2.0.5


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 0/9] Add MIPS EJTAG Fast Debug Channel TTY driver
@ 2015-01-29 11:14 ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper, Greg Kroah-Hartman, Jiri Slaby, Jason Wessel,
	kgdb-bugreport

This patchset adds a TTY, console, and KGDB driver for the MIPS Fast
Debug Channel (FDC) hardware, for communicating with a debugger via an
EJTAG probe. 16 TTY ports are created per FDC device, corresponding to
the 16 FDC channels. Each VPE usually has its own FDC instance.

This patchset depends on my recent CDMM bus patchset (the FDC is in the
per-CPU CDMM region), and my recent MIPS timer & perf counter IRQ
sharing patchset (the FDC IRQ is a local CPU IRQ which may similarly
share CPU IRQ lines with the other local IRQs).

Patches 1 to 6 add the necessary architecture bits for the FDC
interrupt, and a workaround in the MIPS idle code to avoid the wait
instruction on certain cores if FDC driver is enabled.

Finally patches 7-9 add the main TTY/console driver, wire up some early
console code, and implement KGDB operations & magic sysrq.

Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Jason Wessel <jason.wessel@windriver.com>
Cc: linux-mips@linux-mips.org
Cc: kgdb-bugreport@lists.sourceforge.net

James Hogan (9):
  MIPS: Add architectural FDC IRQ fields
  MIPS: Read CPU IRQ line that FDC to routed to
  irqchip: mips-gic: Don't treat FDC IRQ as percpu devid
  irqchip: mips-gic: Add function for retrieving FDC IRQ
  MIPS: Malta: Implement get_c0_fdc_int()
  MIPS: idle: Workaround wait + FDC problems
  tty: Add MIPS EJTAG Fast Debug Channel TTY driver
  MIPS, ttyFDC: Add early FDC console support
  ttyFDC: Implement KGDB IO operations.

 arch/mips/include/asm/cdmm.h     |   11 +
 arch/mips/include/asm/irq.h      |    3 +
 arch/mips/include/asm/mipsregs.h |    4 +
 arch/mips/kernel/idle.c          |   13 +-
 arch/mips/kernel/setup.c         |    2 +
 arch/mips/kernel/traps.c         |   11 +
 arch/mips/mti-malta/malta-time.c |   16 +
 drivers/irqchip/irq-mips-gic.c   |   38 +-
 drivers/tty/Kconfig              |   47 ++
 drivers/tty/Makefile             |    1 +
 drivers/tty/mips_ejtag_fdc.c     | 1303 ++++++++++++++++++++++++++++++++++++++
 include/linux/irqchip/mips-gic.h |    1 +
 12 files changed, 1443 insertions(+), 7 deletions(-)
 create mode 100644 drivers/tty/mips_ejtag_fdc.c

-- 
2.0.5

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/9] MIPS: Add architectural FDC IRQ fields
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

Add architectural field definitions relating to the Fast Debug Channel
(FDC) interrupt, namely the pending bit in Cause and the field in
IntCtl to specify which CPU IRQ line the FDC interrupt is routed to.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
 arch/mips/include/asm/mipsregs.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h
index 2969ceaecfd3..9de5a3221c01 100644
--- a/arch/mips/include/asm/mipsregs.h
+++ b/arch/mips/include/asm/mipsregs.h
@@ -428,6 +428,8 @@
  *
  * Refer to your MIPS R4xx0 manual, chapter 5 for explanation.
  */
+#define INTCTLB_IPFDC		23
+#define INTCTLF_IPFDC		(_ULCAST_(7) << INTCTLB_IPFDC)
 #define INTCTLB_IPPCI		26
 #define INTCTLF_IPPCI		(_ULCAST_(7) << INTCTLB_IPPCI)
 #define INTCTLB_IPTI		29
@@ -458,6 +460,8 @@
 #define	 CAUSEF_IP6		(_ULCAST_(1)   << 14)
 #define	 CAUSEB_IP7		15
 #define	 CAUSEF_IP7		(_ULCAST_(1)   << 15)
+#define	 CAUSEB_FDCI		21
+#define	 CAUSEF_FDCI		(_ULCAST_(1)   << 21)
 #define	 CAUSEB_IV		23
 #define	 CAUSEF_IV		(_ULCAST_(1)   << 23)
 #define	 CAUSEB_PCI		26
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 1/9] MIPS: Add architectural FDC IRQ fields
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

Add architectural field definitions relating to the Fast Debug Channel
(FDC) interrupt, namely the pending bit in Cause and the field in
IntCtl to specify which CPU IRQ line the FDC interrupt is routed to.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
 arch/mips/include/asm/mipsregs.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h
index 2969ceaecfd3..9de5a3221c01 100644
--- a/arch/mips/include/asm/mipsregs.h
+++ b/arch/mips/include/asm/mipsregs.h
@@ -428,6 +428,8 @@
  *
  * Refer to your MIPS R4xx0 manual, chapter 5 for explanation.
  */
+#define INTCTLB_IPFDC		23
+#define INTCTLF_IPFDC		(_ULCAST_(7) << INTCTLB_IPFDC)
 #define INTCTLB_IPPCI		26
 #define INTCTLF_IPPCI		(_ULCAST_(7) << INTCTLB_IPPCI)
 #define INTCTLB_IPTI		29
@@ -458,6 +460,8 @@
 #define	 CAUSEF_IP6		(_ULCAST_(1)   << 14)
 #define	 CAUSEB_IP7		15
 #define	 CAUSEF_IP7		(_ULCAST_(1)   << 15)
+#define	 CAUSEB_FDCI		21
+#define	 CAUSEF_FDCI		(_ULCAST_(1)   << 21)
 #define	 CAUSEB_IV		23
 #define	 CAUSEF_IV		(_ULCAST_(1)   << 23)
 #define	 CAUSEB_PCI		26
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 2/9] MIPS: Read CPU IRQ line that FDC to routed to
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

Read the CPU IRQ line reportedly used for the Fast Debug Channel (FDC)
interrupt from the IntCtl register and store it in cp0_fdc_irq where
platform implementations of the new weak platform function
get_c0_fdc_int() can refer to it.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
 arch/mips/include/asm/irq.h |  3 +++
 arch/mips/kernel/traps.c    | 11 +++++++++++
 2 files changed, 14 insertions(+)

diff --git a/arch/mips/include/asm/irq.h b/arch/mips/include/asm/irq.h
index 5a4e1bb8fb1b..f0db99f8defe 100644
--- a/arch/mips/include/asm/irq.h
+++ b/arch/mips/include/asm/irq.h
@@ -47,6 +47,9 @@ extern void free_irqno(unsigned int irq);
 extern int cp0_compare_irq;
 extern int cp0_compare_irq_shift;
 extern int cp0_perfcount_irq;
+extern int cp0_fdc_irq;
+
+extern int __weak get_c0_fdc_int(void);
 
 void arch_trigger_all_cpu_backtrace(bool);
 #define arch_trigger_all_cpu_backtrace arch_trigger_all_cpu_backtrace
diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c
index 9c109fd8ba99..290da81c0532 100644
--- a/arch/mips/kernel/traps.c
+++ b/arch/mips/kernel/traps.c
@@ -1920,6 +1920,12 @@ int cp0_compare_irq_shift;
 int cp0_perfcount_irq;
 EXPORT_SYMBOL_GPL(cp0_perfcount_irq);
 
+/*
+ * Fast debug channel IRQ or -1 if not present
+ */
+int cp0_fdc_irq;
+EXPORT_SYMBOL_GPL(cp0_fdc_irq);
+
 static int noulri;
 
 static int __init ulri_disable(char *s)
@@ -2001,15 +2007,20 @@ void per_cpu_trap_init(bool is_boot_cpu)
 	 *
 	 *  o read IntCtl.IPTI to determine the timer interrupt
 	 *  o read IntCtl.IPPCI to determine the performance counter interrupt
+	 *  o read IntCtl.IPFDC to determine the fast debug channel interrupt
 	 */
 	if (cpu_has_mips_r2) {
 		cp0_compare_irq_shift = CAUSEB_TI - CAUSEB_IP;
 		cp0_compare_irq = (read_c0_intctl() >> INTCTLB_IPTI) & 7;
 		cp0_perfcount_irq = (read_c0_intctl() >> INTCTLB_IPPCI) & 7;
+		cp0_fdc_irq = (read_c0_intctl() >> INTCTLB_IPFDC) & 7;
+		if (!cp0_fdc_irq)
+			cp0_fdc_irq = -1;
 	} else {
 		cp0_compare_irq = CP0_LEGACY_COMPARE_IRQ;
 		cp0_compare_irq_shift = CP0_LEGACY_PERFCNT_IRQ;
 		cp0_perfcount_irq = -1;
+		cp0_fdc_irq = -1;
 	}
 
 	if (!cpu_data[cpu].asid_cache)
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 2/9] MIPS: Read CPU IRQ line that FDC to routed to
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

Read the CPU IRQ line reportedly used for the Fast Debug Channel (FDC)
interrupt from the IntCtl register and store it in cp0_fdc_irq where
platform implementations of the new weak platform function
get_c0_fdc_int() can refer to it.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
 arch/mips/include/asm/irq.h |  3 +++
 arch/mips/kernel/traps.c    | 11 +++++++++++
 2 files changed, 14 insertions(+)

diff --git a/arch/mips/include/asm/irq.h b/arch/mips/include/asm/irq.h
index 5a4e1bb8fb1b..f0db99f8defe 100644
--- a/arch/mips/include/asm/irq.h
+++ b/arch/mips/include/asm/irq.h
@@ -47,6 +47,9 @@ extern void free_irqno(unsigned int irq);
 extern int cp0_compare_irq;
 extern int cp0_compare_irq_shift;
 extern int cp0_perfcount_irq;
+extern int cp0_fdc_irq;
+
+extern int __weak get_c0_fdc_int(void);
 
 void arch_trigger_all_cpu_backtrace(bool);
 #define arch_trigger_all_cpu_backtrace arch_trigger_all_cpu_backtrace
diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c
index 9c109fd8ba99..290da81c0532 100644
--- a/arch/mips/kernel/traps.c
+++ b/arch/mips/kernel/traps.c
@@ -1920,6 +1920,12 @@ int cp0_compare_irq_shift;
 int cp0_perfcount_irq;
 EXPORT_SYMBOL_GPL(cp0_perfcount_irq);
 
+/*
+ * Fast debug channel IRQ or -1 if not present
+ */
+int cp0_fdc_irq;
+EXPORT_SYMBOL_GPL(cp0_fdc_irq);
+
 static int noulri;
 
 static int __init ulri_disable(char *s)
@@ -2001,15 +2007,20 @@ void per_cpu_trap_init(bool is_boot_cpu)
 	 *
 	 *  o read IntCtl.IPTI to determine the timer interrupt
 	 *  o read IntCtl.IPPCI to determine the performance counter interrupt
+	 *  o read IntCtl.IPFDC to determine the fast debug channel interrupt
 	 */
 	if (cpu_has_mips_r2) {
 		cp0_compare_irq_shift = CAUSEB_TI - CAUSEB_IP;
 		cp0_compare_irq = (read_c0_intctl() >> INTCTLB_IPTI) & 7;
 		cp0_perfcount_irq = (read_c0_intctl() >> INTCTLB_IPPCI) & 7;
+		cp0_fdc_irq = (read_c0_intctl() >> INTCTLB_IPFDC) & 7;
+		if (!cp0_fdc_irq)
+			cp0_fdc_irq = -1;
 	} else {
 		cp0_compare_irq = CP0_LEGACY_COMPARE_IRQ;
 		cp0_compare_irq_shift = CP0_LEGACY_PERFCNT_IRQ;
 		cp0_perfcount_irq = -1;
+		cp0_fdc_irq = -1;
 	}
 
 	if (!cpu_data[cpu].asid_cache)
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 3/9] irqchip: mips-gic: Don't treat FDC IRQ as percpu devid
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper

Treat the Fast Debug Channel (FDC) interrupt the same as the timer and
performance counter interrupts. Like them, the FDC IRQ is also per-VPE,
and also doesn't use a per-CPU device ID yet. Per-CPU device IDs don't
seem to work with IRQF_SHARED which is needed for compatibility with
cores which don't route the FDC IRQ through the GIC. For hardware which
routes FDC IRQs through the GIC this is something that could be added
later.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: linux-mips@linux-mips.org
---
 drivers/irqchip/irq-mips-gic.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 1f12eaedc9d9..bcfead6e8ae6 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -591,15 +591,20 @@ static int gic_local_irq_domain_map(struct irq_domain *d, unsigned int virq,
 	 * of the MIPS kernel code does not use the percpu IRQ API for
 	 * the CP0 timer and performance counter interrupts.
 	 */
-	if (intr != GIC_LOCAL_INT_TIMER && intr != GIC_LOCAL_INT_PERFCTR) {
+	switch (intr) {
+	case GIC_LOCAL_INT_TIMER:
+	case GIC_LOCAL_INT_PERFCTR:
+	case GIC_LOCAL_INT_FDC:
+		irq_set_chip_and_handler(virq,
+					 &gic_all_vpes_local_irq_controller,
+					 handle_percpu_irq);
+		break;
+	default:
 		irq_set_chip_and_handler(virq,
 					 &gic_local_irq_controller,
 					 handle_percpu_devid_irq);
 		irq_set_percpu_devid(virq);
-	} else {
-		irq_set_chip_and_handler(virq,
-					 &gic_all_vpes_local_irq_controller,
-					 handle_percpu_irq);
+		break;
 	}
 
 	spin_lock_irqsave(&gic_lock, flags);
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 3/9] irqchip: mips-gic: Don't treat FDC IRQ as percpu devid
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper

Treat the Fast Debug Channel (FDC) interrupt the same as the timer and
performance counter interrupts. Like them, the FDC IRQ is also per-VPE,
and also doesn't use a per-CPU device ID yet. Per-CPU device IDs don't
seem to work with IRQF_SHARED which is needed for compatibility with
cores which don't route the FDC IRQ through the GIC. For hardware which
routes FDC IRQs through the GIC this is something that could be added
later.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: linux-mips@linux-mips.org
---
 drivers/irqchip/irq-mips-gic.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 1f12eaedc9d9..bcfead6e8ae6 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -591,15 +591,20 @@ static int gic_local_irq_domain_map(struct irq_domain *d, unsigned int virq,
 	 * of the MIPS kernel code does not use the percpu IRQ API for
 	 * the CP0 timer and performance counter interrupts.
 	 */
-	if (intr != GIC_LOCAL_INT_TIMER && intr != GIC_LOCAL_INT_PERFCTR) {
+	switch (intr) {
+	case GIC_LOCAL_INT_TIMER:
+	case GIC_LOCAL_INT_PERFCTR:
+	case GIC_LOCAL_INT_FDC:
+		irq_set_chip_and_handler(virq,
+					 &gic_all_vpes_local_irq_controller,
+					 handle_percpu_irq);
+		break;
+	default:
 		irq_set_chip_and_handler(virq,
 					 &gic_local_irq_controller,
 					 handle_percpu_devid_irq);
 		irq_set_percpu_devid(virq);
-	} else {
-		irq_set_chip_and_handler(virq,
-					 &gic_all_vpes_local_irq_controller,
-					 handle_percpu_irq);
+		break;
 	}
 
 	spin_lock_irqsave(&gic_lock, flags);
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 4/9] irqchip: mips-gic: Add function for retrieving FDC IRQ
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper

Add a function to the MIPS GIC driver for retrieving the Fast Debug
Channel (FDC) interrupt number, similar to the existing ones for the
timer and perf counter interrupts. This will be used by platform
implementations of get_c0_fdc_int() if a GIC is present.

A workaround exists for interAptiv and proAptiv which claim to be able
to route the FDC interrupt but don't seem to be able to in practice (at
least on Malta).

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: linux-mips@linux-mips.org
---
 drivers/irqchip/irq-mips-gic.c   | 23 +++++++++++++++++++++++
 include/linux/irqchip/mips-gic.h |  1 +
 2 files changed, 24 insertions(+)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index bcfead6e8ae6..00379550cd71 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -234,6 +234,29 @@ int gic_get_c0_perfcount_int(void)
 				  GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_PERFCTR));
 }
 
+int gic_get_c0_fdc_int(void)
+{
+	if (!gic_local_irq_is_routable(GIC_LOCAL_INT_FDC)) {
+		/* Is the FDC IRQ even present? */
+		if (cp0_fdc_irq < 0)
+			return -1;
+		return MIPS_CPU_IRQ_BASE + cp0_fdc_irq;
+	}
+
+	/*
+	 * Some cores claim the FDC is routable but it doesn't actually seem to
+	 * be connected.
+	 */
+	switch (current_cpu_type()) {
+	case CPU_INTERAPTIV:
+	case CPU_PROAPTIV:
+		return -1;
+	}
+
+	return irq_create_mapping(gic_irq_domain,
+				  GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_FDC));
+}
+
 static unsigned int gic_get_int(void)
 {
 	unsigned int i;
diff --git a/include/linux/irqchip/mips-gic.h b/include/linux/irqchip/mips-gic.h
index ff0e75f40ef5..8e9940040e6d 100644
--- a/include/linux/irqchip/mips-gic.h
+++ b/include/linux/irqchip/mips-gic.h
@@ -252,4 +252,5 @@ extern unsigned int plat_ipi_resched_int_xlate(unsigned int);
 extern unsigned int gic_get_timer_pending(void);
 extern int gic_get_c0_compare_int(void);
 extern int gic_get_c0_perfcount_int(void);
+extern int gic_get_c0_fdc_int(void);
 #endif /* __LINUX_IRQCHIP_MIPS_GIC_H */
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 4/9] irqchip: mips-gic: Add function for retrieving FDC IRQ
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper

Add a function to the MIPS GIC driver for retrieving the Fast Debug
Channel (FDC) interrupt number, similar to the existing ones for the
timer and perf counter interrupts. This will be used by platform
implementations of get_c0_fdc_int() if a GIC is present.

A workaround exists for interAptiv and proAptiv which claim to be able
to route the FDC interrupt but don't seem to be able to in practice (at
least on Malta).

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Andrew Bresticker <abrestic@chromium.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: linux-mips@linux-mips.org
---
 drivers/irqchip/irq-mips-gic.c   | 23 +++++++++++++++++++++++
 include/linux/irqchip/mips-gic.h |  1 +
 2 files changed, 24 insertions(+)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index bcfead6e8ae6..00379550cd71 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -234,6 +234,29 @@ int gic_get_c0_perfcount_int(void)
 				  GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_PERFCTR));
 }
 
+int gic_get_c0_fdc_int(void)
+{
+	if (!gic_local_irq_is_routable(GIC_LOCAL_INT_FDC)) {
+		/* Is the FDC IRQ even present? */
+		if (cp0_fdc_irq < 0)
+			return -1;
+		return MIPS_CPU_IRQ_BASE + cp0_fdc_irq;
+	}
+
+	/*
+	 * Some cores claim the FDC is routable but it doesn't actually seem to
+	 * be connected.
+	 */
+	switch (current_cpu_type()) {
+	case CPU_INTERAPTIV:
+	case CPU_PROAPTIV:
+		return -1;
+	}
+
+	return irq_create_mapping(gic_irq_domain,
+				  GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_FDC));
+}
+
 static unsigned int gic_get_int(void)
 {
 	unsigned int i;
diff --git a/include/linux/irqchip/mips-gic.h b/include/linux/irqchip/mips-gic.h
index ff0e75f40ef5..8e9940040e6d 100644
--- a/include/linux/irqchip/mips-gic.h
+++ b/include/linux/irqchip/mips-gic.h
@@ -252,4 +252,5 @@ extern unsigned int plat_ipi_resched_int_xlate(unsigned int);
 extern unsigned int gic_get_timer_pending(void);
 extern int gic_get_c0_compare_int(void);
 extern int gic_get_c0_perfcount_int(void);
+extern int gic_get_c0_fdc_int(void);
 #endif /* __LINUX_IRQCHIP_MIPS_GIC_H */
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 5/9] MIPS: Malta: Implement get_c0_fdc_int()
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

Implement the weak get_c0_fdc_int() function for Malta. The Fast Debug
Channel (FDC) interrupt is obtained mainly depending on whether a GIC is
present. Vectored external interrupt mode isn't yet supported.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
EIC mode not supported because I don't have anything to try it on (and
it isn't clear which interrupt priority level would get used).

It may make sense to have a common default implementation of
get_c0_fdc_int(), but this isn't done for the other local IRQs yet so
I've kept it like this for consistency.
---
 arch/mips/mti-malta/malta-time.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/arch/mips/mti-malta/malta-time.c b/arch/mips/mti-malta/malta-time.c
index ce02dbdedc62..7d4b86571564 100644
--- a/arch/mips/mti-malta/malta-time.c
+++ b/arch/mips/mti-malta/malta-time.c
@@ -115,6 +115,22 @@ void read_persistent_clock(struct timespec *ts)
 	ts->tv_nsec = 0;
 }
 
+int get_c0_fdc_int(void)
+{
+	int mips_cpu_fdc_irq;
+
+	if (cpu_has_veic)
+		mips_cpu_fdc_irq = -1;
+	else if (gic_present)
+		mips_cpu_fdc_irq = gic_get_c0_fdc_int();
+	else if (cp0_fdc_irq >= 0)
+		mips_cpu_fdc_irq = MIPS_CPU_IRQ_BASE + cp0_fdc_irq;
+	else
+		mips_cpu_fdc_irq = -1;
+
+	return mips_cpu_fdc_irq;
+}
+
 int get_c0_perfcount_int(void)
 {
 	if (cpu_has_veic) {
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 5/9] MIPS: Malta: Implement get_c0_fdc_int()
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

Implement the weak get_c0_fdc_int() function for Malta. The Fast Debug
Channel (FDC) interrupt is obtained mainly depending on whether a GIC is
present. Vectored external interrupt mode isn't yet supported.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
EIC mode not supported because I don't have anything to try it on (and
it isn't clear which interrupt priority level would get used).

It may make sense to have a common default implementation of
get_c0_fdc_int(), but this isn't done for the other local IRQs yet so
I've kept it like this for consistency.
---
 arch/mips/mti-malta/malta-time.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/arch/mips/mti-malta/malta-time.c b/arch/mips/mti-malta/malta-time.c
index ce02dbdedc62..7d4b86571564 100644
--- a/arch/mips/mti-malta/malta-time.c
+++ b/arch/mips/mti-malta/malta-time.c
@@ -115,6 +115,22 @@ void read_persistent_clock(struct timespec *ts)
 	ts->tv_nsec = 0;
 }
 
+int get_c0_fdc_int(void)
+{
+	int mips_cpu_fdc_irq;
+
+	if (cpu_has_veic)
+		mips_cpu_fdc_irq = -1;
+	else if (gic_present)
+		mips_cpu_fdc_irq = gic_get_c0_fdc_int();
+	else if (cp0_fdc_irq >= 0)
+		mips_cpu_fdc_irq = MIPS_CPU_IRQ_BASE + cp0_fdc_irq;
+	else
+		mips_cpu_fdc_irq = -1;
+
+	return mips_cpu_fdc_irq;
+}
+
 int get_c0_perfcount_int(void)
 {
 	if (cpu_has_veic) {
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 6/9] MIPS: idle: Workaround wait + FDC problems
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

On certain cores (namely proAptiv and P5600) incoming data via a Fast
Debug Channel (FDC) while the core is blocked on a wait instruction will
cause the wait not to wake up even when another interrupt is received.
This makes an idle target stop as soon as you send FDC data to it, until
the debug probe interrupts it and restarts the wait instruction.

This is worked around by avoiding using r4k_wait on these cores if
CONFIG_MIPS_EJTAG_FDC_TTY is enabled (which would imply the user intends
to use the FDC).

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
 arch/mips/kernel/idle.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/arch/mips/kernel/idle.c b/arch/mips/kernel/idle.c
index 0b9082b6b683..3db277ea9e8d 100644
--- a/arch/mips/kernel/idle.c
+++ b/arch/mips/kernel/idle.c
@@ -176,6 +176,17 @@ void __init check_wait(void)
 		cpu_wait = rm7k_wait_irqoff;
 		break;
 
+	case CPU_PROAPTIV:
+	case CPU_P5600:
+		/*
+		 * Incoming Fast Debug Channel (FDC) data during a wait
+		 * instruction causes the wait never to resume, even if an
+		 * interrupt is received. Avoid using wait at all if FDC data is
+		 * likely to be received.
+		 */
+		if (IS_ENABLED(CONFIG_MIPS_EJTAG_FDC_TTY))
+			break;
+		/* fall through */
 	case CPU_M14KC:
 	case CPU_M14KEC:
 	case CPU_24K:
@@ -183,8 +194,6 @@ void __init check_wait(void)
 	case CPU_1004K:
 	case CPU_1074K:
 	case CPU_INTERAPTIV:
-	case CPU_PROAPTIV:
-	case CPU_P5600:
 	case CPU_M5150:
 		cpu_wait = r4k_wait;
 		if (read_c0_config7() & MIPS_CONF7_WII)
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 6/9] MIPS: idle: Workaround wait + FDC problems
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips; +Cc: linux-kernel, James Hogan

On certain cores (namely proAptiv and P5600) incoming data via a Fast
Debug Channel (FDC) while the core is blocked on a wait instruction will
cause the wait not to wake up even when another interrupt is received.
This makes an idle target stop as soon as you send FDC data to it, until
the debug probe interrupts it and restarts the wait instruction.

This is worked around by avoiding using r4k_wait on these cores if
CONFIG_MIPS_EJTAG_FDC_TTY is enabled (which would imply the user intends
to use the FDC).

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
---
 arch/mips/kernel/idle.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/arch/mips/kernel/idle.c b/arch/mips/kernel/idle.c
index 0b9082b6b683..3db277ea9e8d 100644
--- a/arch/mips/kernel/idle.c
+++ b/arch/mips/kernel/idle.c
@@ -176,6 +176,17 @@ void __init check_wait(void)
 		cpu_wait = rm7k_wait_irqoff;
 		break;
 
+	case CPU_PROAPTIV:
+	case CPU_P5600:
+		/*
+		 * Incoming Fast Debug Channel (FDC) data during a wait
+		 * instruction causes the wait never to resume, even if an
+		 * interrupt is received. Avoid using wait at all if FDC data is
+		 * likely to be received.
+		 */
+		if (IS_ENABLED(CONFIG_MIPS_EJTAG_FDC_TTY))
+			break;
+		/* fall through */
 	case CPU_M14KC:
 	case CPU_M14KEC:
 	case CPU_24K:
@@ -183,8 +194,6 @@ void __init check_wait(void)
 	case CPU_1004K:
 	case CPU_1074K:
 	case CPU_INTERAPTIV:
-	case CPU_PROAPTIV:
-	case CPU_P5600:
 	case CPU_M5150:
 		cpu_wait = r4k_wait;
 		if (read_c0_config7() & MIPS_CONF7_WII)
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 7/9] tty: Add MIPS EJTAG Fast Debug Channel TTY driver
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Greg Kroah-Hartman, Jiri Slaby

Add TTY driver and consoles for the MIPS EJTAG Fast Debug Channel (FDC),
which is found on the per-CPU MIPS Common Device Mapped Memory (CDMM)
bus.

The FDC is a per-CPU device which is used to communicate with an EJTAG
probe. RX and TX FIFOs exist, containing 32-bits of data and 4-bit
channel numbers. 16 general data streams are implemented on this for TTY
and console use by encoding up to 4 bytes on each 32-bit FDC word.

The TTY devices are named e.g. /dev/ttyFDC3c2 for channel 2 of the FDC
attached to logical CPU 3.

These can be used for getting the kernel log, a login prompt, or as a
GDB remote transport, all over EJTAG and without needing a serial port.

It can have an interrupt to notify of when incoming data is available in
the RX FIFO or when the TX FIFO is no longer full. The detection of this
interrupt occurs in architecture / platform code, but it may be shared
with the timer and/or performance counter interrupt.

Due to the per-CPU nature of the hardware, all outgoing TTY data is
written out from a kthread which is pinned to the appropriate CPU.

The console is not bound to a specific CPU, so output will appear on the
chosen channel on whichever CPU the code is executing on. Enable with
e.g. console=fdc1 in kernel arguments. /dev/console is bound to the same
channel on the boot CPU's FDC if it exists.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: linux-mips@linux-mips.org
---
 drivers/tty/Kconfig          |   18 +
 drivers/tty/Makefile         |    1 +
 drivers/tty/mips_ejtag_fdc.c | 1126 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1145 insertions(+)
 create mode 100644 drivers/tty/mips_ejtag_fdc.c

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);
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 7/9] tty: Add MIPS EJTAG Fast Debug Channel TTY driver
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Greg Kroah-Hartman, Jiri Slaby

Add TTY driver and consoles for the MIPS EJTAG Fast Debug Channel (FDC),
which is found on the per-CPU MIPS Common Device Mapped Memory (CDMM)
bus.

The FDC is a per-CPU device which is used to communicate with an EJTAG
probe. RX and TX FIFOs exist, containing 32-bits of data and 4-bit
channel numbers. 16 general data streams are implemented on this for TTY
and console use by encoding up to 4 bytes on each 32-bit FDC word.

The TTY devices are named e.g. /dev/ttyFDC3c2 for channel 2 of the FDC
attached to logical CPU 3.

These can be used for getting the kernel log, a login prompt, or as a
GDB remote transport, all over EJTAG and without needing a serial port.

It can have an interrupt to notify of when incoming data is available in
the RX FIFO or when the TX FIFO is no longer full. The detection of this
interrupt occurs in architecture / platform code, but it may be shared
with the timer and/or performance counter interrupt.

Due to the per-CPU nature of the hardware, all outgoing TTY data is
written out from a kthread which is pinned to the appropriate CPU.

The console is not bound to a specific CPU, so output will appear on the
chosen channel on whichever CPU the code is executing on. Enable with
e.g. console=fdc1 in kernel arguments. /dev/console is bound to the same
channel on the boot CPU's FDC if it exists.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: linux-mips@linux-mips.org
---
 drivers/tty/Kconfig          |   18 +
 drivers/tty/Makefile         |    1 +
 drivers/tty/mips_ejtag_fdc.c | 1126 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1145 insertions(+)
 create mode 100644 drivers/tty/mips_ejtag_fdc.c

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);
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 8/9] MIPS, ttyFDC: Add early FDC console support
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Greg Kroah-Hartman, Jiri Slaby

Add support for early console of MIPS Fast Debug Channel (FDC) on
channel 1 with a call very early from the MIPS setup_arch().

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: linux-mips@linux-mips.org
---
 arch/mips/include/asm/cdmm.h | 11 +++++++++++
 arch/mips/kernel/setup.c     |  2 ++
 drivers/tty/Kconfig          | 13 +++++++++++++
 drivers/tty/mips_ejtag_fdc.c | 20 ++++++++++++++++++++
 4 files changed, 46 insertions(+)

diff --git a/arch/mips/include/asm/cdmm.h b/arch/mips/include/asm/cdmm.h
index b7d520f28d30..16e22ce9719f 100644
--- a/arch/mips/include/asm/cdmm.h
+++ b/arch/mips/include/asm/cdmm.h
@@ -84,4 +84,15 @@ void mips_cdmm_driver_unregister(struct mips_cdmm_driver *);
 	module_driver(__mips_cdmm_driver, mips_cdmm_driver_register, \
 			mips_cdmm_driver_unregister)
 
+/* drivers/tty/mips_ejtag_fdc.c */
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON
+int setup_early_fdc_console(void);
+#else
+static inline int setup_early_fdc_console(void)
+{
+	return -ENODEV;
+}
+#endif
+
 #endif /* __ASM_CDMM_H */
diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c
index 058929041368..be73c491182b 100644
--- a/arch/mips/kernel/setup.c
+++ b/arch/mips/kernel/setup.c
@@ -31,6 +31,7 @@
 #include <asm/bootinfo.h>
 #include <asm/bugs.h>
 #include <asm/cache.h>
+#include <asm/cdmm.h>
 #include <asm/cpu.h>
 #include <asm/sections.h>
 #include <asm/setup.h>
@@ -763,6 +764,7 @@ void __init setup_arch(char **cmdline_p)
 	cpu_probe();
 	prom_init();
 
+	setup_early_fdc_console();
 #ifdef CONFIG_EARLY_PRINTK
 	setup_early_printk();
 #endif
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 39469ca4231c..e0c18e5b7057 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -437,4 +437,17 @@ config MIPS_EJTAG_FDC_TTY
 
 	  If unsure, say N.
 
+config MIPS_EJTAG_FDC_EARLYCON
+	bool "Early FDC console"
+	depends on MIPS_EJTAG_FDC_TTY
+	help
+	  This registers a console on FDC channel 1 very early during boot (from
+	  MIPS arch code). This is useful for bring-up and debugging early boot
+	  issues.
+
+	  Do not enable unless there is a debug probe attached to drain the FDC
+	  TX FIFO.
+
+	  If unsure, say N.
+
 endif # TTY
diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c
index 51672cfe7e45..8d9bf6f90110 100644
--- a/drivers/tty/mips_ejtag_fdc.c
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -69,6 +69,9 @@
 #define REG_FDSTAT_TXE			BIT(1)	/* Tx Empty */
 #define REG_FDSTAT_TXF			BIT(0)	/* Tx Full */
 
+/* Default channel for the early console */
+#define CONSOLE_CHANNEL      1
+
 #define NUM_TTY_CHANNELS     16
 
 #define RX_BUF_SIZE 1024
@@ -1124,3 +1127,20 @@ 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);
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON
+static struct mips_ejtag_fdc_console mips_ejtag_fdc_earlycon = {
+	.cons	= {
+		.name	= "early_fdc",
+		.write	= mips_ejtag_fdc_console_write,
+		.flags	= CON_PRINTBUFFER | CON_BOOT,
+		.index	= CONSOLE_CHANNEL,
+	},
+	.lock	= __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_earlycon.lock),
+};
+
+int __init setup_early_fdc_console(void)
+{
+	return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon);
+}
+#endif
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 8/9] MIPS, ttyFDC: Add early FDC console support
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Greg Kroah-Hartman, Jiri Slaby

Add support for early console of MIPS Fast Debug Channel (FDC) on
channel 1 with a call very early from the MIPS setup_arch().

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: linux-mips@linux-mips.org
---
 arch/mips/include/asm/cdmm.h | 11 +++++++++++
 arch/mips/kernel/setup.c     |  2 ++
 drivers/tty/Kconfig          | 13 +++++++++++++
 drivers/tty/mips_ejtag_fdc.c | 20 ++++++++++++++++++++
 4 files changed, 46 insertions(+)

diff --git a/arch/mips/include/asm/cdmm.h b/arch/mips/include/asm/cdmm.h
index b7d520f28d30..16e22ce9719f 100644
--- a/arch/mips/include/asm/cdmm.h
+++ b/arch/mips/include/asm/cdmm.h
@@ -84,4 +84,15 @@ void mips_cdmm_driver_unregister(struct mips_cdmm_driver *);
 	module_driver(__mips_cdmm_driver, mips_cdmm_driver_register, \
 			mips_cdmm_driver_unregister)
 
+/* drivers/tty/mips_ejtag_fdc.c */
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON
+int setup_early_fdc_console(void);
+#else
+static inline int setup_early_fdc_console(void)
+{
+	return -ENODEV;
+}
+#endif
+
 #endif /* __ASM_CDMM_H */
diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c
index 058929041368..be73c491182b 100644
--- a/arch/mips/kernel/setup.c
+++ b/arch/mips/kernel/setup.c
@@ -31,6 +31,7 @@
 #include <asm/bootinfo.h>
 #include <asm/bugs.h>
 #include <asm/cache.h>
+#include <asm/cdmm.h>
 #include <asm/cpu.h>
 #include <asm/sections.h>
 #include <asm/setup.h>
@@ -763,6 +764,7 @@ void __init setup_arch(char **cmdline_p)
 	cpu_probe();
 	prom_init();
 
+	setup_early_fdc_console();
 #ifdef CONFIG_EARLY_PRINTK
 	setup_early_printk();
 #endif
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 39469ca4231c..e0c18e5b7057 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -437,4 +437,17 @@ config MIPS_EJTAG_FDC_TTY
 
 	  If unsure, say N.
 
+config MIPS_EJTAG_FDC_EARLYCON
+	bool "Early FDC console"
+	depends on MIPS_EJTAG_FDC_TTY
+	help
+	  This registers a console on FDC channel 1 very early during boot (from
+	  MIPS arch code). This is useful for bring-up and debugging early boot
+	  issues.
+
+	  Do not enable unless there is a debug probe attached to drain the FDC
+	  TX FIFO.
+
+	  If unsure, say N.
+
 endif # TTY
diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c
index 51672cfe7e45..8d9bf6f90110 100644
--- a/drivers/tty/mips_ejtag_fdc.c
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -69,6 +69,9 @@
 #define REG_FDSTAT_TXE			BIT(1)	/* Tx Empty */
 #define REG_FDSTAT_TXF			BIT(0)	/* Tx Full */
 
+/* Default channel for the early console */
+#define CONSOLE_CHANNEL      1
+
 #define NUM_TTY_CHANNELS     16
 
 #define RX_BUF_SIZE 1024
@@ -1124,3 +1127,20 @@ 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);
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON
+static struct mips_ejtag_fdc_console mips_ejtag_fdc_earlycon = {
+	.cons	= {
+		.name	= "early_fdc",
+		.write	= mips_ejtag_fdc_console_write,
+		.flags	= CON_PRINTBUFFER | CON_BOOT,
+		.index	= CONSOLE_CHANNEL,
+	},
+	.lock	= __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_earlycon.lock),
+};
+
+int __init setup_early_fdc_console(void)
+{
+	return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon);
+}
+#endif
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 9/9] ttyFDC: Implement KGDB IO operations.
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Greg Kroah-Hartman, Jiri Slaby,
	Jason Wessel, kgdb-bugreport

Implement KGDB IO operations for MIPS Fast Debug Channel (FDC). This can
be enabled via Kconfig, which also allows the channel number to be
chosen.

The magic sysrq hack is implemented in the TTY driver, detecting just ^C
for the KGDB channel, and ^O followed by a letter for the FDC console
channel.

The KGDB operations are reasonably efficient thanks to the flush
callback, with a 4 byte buffer being used in both directions to allow up
to 4 bytes to be encoded per FDC word. Reading of data for KGDB will
discard any data received on other channels, which clearly isn't ideal,
but given that there is a single FIFO shared between channels we can't
do much better.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Jason Wessel <jason.wessel@windriver.com>
Cc: linux-mips@linux-mips.org
Cc: kgdb-bugreport@lists.sourceforge.net
---
 drivers/tty/Kconfig          |  16 ++++
 drivers/tty/mips_ejtag_fdc.c | 169 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 179 insertions(+), 6 deletions(-)

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index e0c18e5b7057..c01f45095877 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -450,4 +450,20 @@ config MIPS_EJTAG_FDC_EARLYCON
 
 	  If unsure, say N.
 
+config MIPS_EJTAG_FDC_KGDB
+	bool "Use KGDB over an FDC channel"
+	depends on MIPS_EJTAG_FDC_TTY && KGDB
+	default y
+	help
+          This enables the use of KGDB over an FDC channel, allowing KGDB to be
+          used remotely or when a serial port isn't available.
+
+config MIPS_EJTAG_FDC_KGDB_CHAN
+	int "KGDB FDC channel"
+	depends on MIPS_EJTAG_FDC_KGDB
+	range 2 15
+	default 3
+	help
+	  FDC channel number to use for KGDB.
+
 endif # TTY
diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c
index 8d9bf6f90110..04d9e23d1ee1 100644
--- a/drivers/tty/mips_ejtag_fdc.c
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -17,9 +17,11 @@
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
+#include <linux/kgdb.h>
 #include <linux/kthread.h>
 #include <linux/sched.h>
 #include <linux/serial.h>
+#include <linux/serial_core.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
@@ -136,6 +138,8 @@ struct mips_ejtag_fdc_tty_port {
  * @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.
+ * @sysrq_pressed:	Whether the magic sysrq key combination has been
+ *			detected. See mips_ejtag_fdc_handle().
  */
 struct mips_ejtag_fdc_tty {
 	struct device			*dev;
@@ -159,6 +163,10 @@ struct mips_ejtag_fdc_tty {
 	int				 irq;
 	bool				 removing;
 	struct timer_list		 poll_timer;
+
+#ifdef CONFIG_MAGIC_SYSRQ
+	bool				 sysrq_pressed;
+#endif
 };
 
 /* Hardware access */
@@ -568,21 +576,47 @@ static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv)
 		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)
+		for (i = 0; i < len; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+			/* Support just Ctrl+C with KGDB channel */
+			if (channel == CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN) {
+				if (buf[i] == '\x03') { /* ^C */
+					handle_sysrq('g');
+					continue;
+				}
+			}
+#endif
+			/* Support Ctrl+O for console channel */
+			if (channel == mips_ejtag_fdc_con.cons.index) {
+				if (buf[i] == '\x0f') {	/* ^O */
+					priv->sysrq_pressed =
+						!priv->sysrq_pressed;
+					if (priv->sysrq_pressed)
+						continue;
+				} else if (priv->sysrq_pressed) {
+					handle_sysrq(buf[i]);
+					priv->sysrq_pressed = false;
+					continue;
+				}
+			}
+#endif /* CONFIG_MAGIC_SYSRQ */
+
+			/* Check the port isn't being shut down */
+			if (!dport->rx_buf)
+				continue;
+
 			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);
 	}
 
@@ -1144,3 +1178,126 @@ int __init setup_early_fdc_console(void)
 	return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon);
 }
 #endif
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+
+/* read buffer to allow decompaction */
+static unsigned int kgdbfdc_rbuflen;
+static unsigned int kgdbfdc_rpos;
+static char kgdbfdc_rbuf[4];
+
+/* write buffer to allow compaction */
+static unsigned int kgdbfdc_wbuflen;
+static char kgdbfdc_wbuf[4];
+
+static void __iomem *kgdbfdc_setup(void)
+{
+	void __iomem *regs;
+	unsigned int cpu;
+
+	/* Find address, piggy backing off console percpu regs */
+	cpu = smp_processor_id();
+	regs = mips_ejtag_fdc_con.regs[cpu];
+	/* First console output on this CPU? */
+	if (!regs) {
+		regs = mips_cdmm_early_probe(0xfd);
+		mips_ejtag_fdc_con.regs[cpu] = regs;
+	}
+	/* Already tried and failed to find FDC on this CPU? */
+	if (IS_ERR(regs))
+		return regs;
+
+	return regs;
+}
+
+/* read a character from the read buffer, filling from FDC RX FIFO */
+static int kgdbfdc_read_char(void)
+{
+	unsigned int stat, channel, data;
+	void __iomem *regs;
+
+	/* No more data, try and read another FDC word from RX FIFO */
+	if (kgdbfdc_rpos >= kgdbfdc_rbuflen) {
+		kgdbfdc_rpos = 0;
+		kgdbfdc_rbuflen = 0;
+
+		regs = kgdbfdc_setup();
+		if (IS_ERR(regs))
+			return NO_POLL_CHAR;
+
+		/* Read next word from KGDB channel */
+		do {
+			stat = ioread32(regs + REG_FDSTAT);
+
+			/* No data waiting? */
+			if (stat & REG_FDSTAT_RXE)
+				return NO_POLL_CHAR;
+
+			/* Read next word */
+			channel = (stat & REG_FDSTAT_RXCHAN) >>
+					REG_FDSTAT_RXCHAN_SHIFT;
+			data = ioread32(regs + REG_FDRX);
+		} while (channel != CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN);
+
+		/* Decode into rbuf */
+		kgdbfdc_rbuflen = mips_ejtag_fdc_decode(data, kgdbfdc_rbuf);
+	}
+	pr_devel("kgdbfdc r %c\n", kgdbfdc_rbuf[kgdbfdc_rpos]);
+	return kgdbfdc_rbuf[kgdbfdc_rpos++];
+}
+
+/* push an FDC word from write buffer to TX FIFO */
+static void kgdbfdc_push_one(void)
+{
+	const char *bufs[1] = { kgdbfdc_wbuf };
+	struct fdc_word word;
+	void __iomem *regs;
+	unsigned int i;
+
+	/* Construct a word from any data in buffer */
+	word = mips_ejtag_fdc_encode(bufs, &kgdbfdc_wbuflen, 1);
+	/* Relocate any remaining data to beginnning of buffer */
+	kgdbfdc_wbuflen -= word.bytes;
+	for (i = 0; i < kgdbfdc_wbuflen; ++i)
+		kgdbfdc_wbuf[i] = kgdbfdc_wbuf[i + word.bytes];
+
+	regs = kgdbfdc_setup();
+	if (IS_ERR(regs))
+		return;
+
+	/* Busy wait until there's space in fifo */
+	while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF)
+		;
+	iowrite32(word.word, regs + REG_FDTX(CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN));
+}
+
+/* flush the whole write buffer to the TX FIFO */
+static void kgdbfdc_flush(void)
+{
+	while (kgdbfdc_wbuflen)
+		kgdbfdc_push_one();
+}
+
+/* write a character into the write buffer, writing out if full */
+static void kgdbfdc_write_char(u8 chr)
+{
+	pr_devel("kgdbfdc w %c\n", chr);
+	kgdbfdc_wbuf[kgdbfdc_wbuflen++] = chr;
+	if (kgdbfdc_wbuflen >= sizeof(kgdbfdc_wbuf))
+		kgdbfdc_push_one();
+}
+
+static struct kgdb_io kgdbfdc_io_ops = {
+	.name		= "kgdbfdc",
+	.read_char	= kgdbfdc_read_char,
+	.write_char	= kgdbfdc_write_char,
+	.flush		= kgdbfdc_flush,
+};
+
+static int __init kgdbfdc_init(void)
+{
+	kgdb_register_io_module(&kgdbfdc_io_ops);
+	return 0;
+}
+early_initcall(kgdbfdc_init);
+#endif
-- 
2.0.5


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 9/9] ttyFDC: Implement KGDB IO operations.
@ 2015-01-29 11:14   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-01-29 11:14 UTC (permalink / raw)
  To: Ralf Baechle, linux-mips
  Cc: linux-kernel, James Hogan, Greg Kroah-Hartman, Jiri Slaby,
	Jason Wessel, kgdb-bugreport

Implement KGDB IO operations for MIPS Fast Debug Channel (FDC). This can
be enabled via Kconfig, which also allows the channel number to be
chosen.

The magic sysrq hack is implemented in the TTY driver, detecting just ^C
for the KGDB channel, and ^O followed by a letter for the FDC console
channel.

The KGDB operations are reasonably efficient thanks to the flush
callback, with a 4 byte buffer being used in both directions to allow up
to 4 bytes to be encoded per FDC word. Reading of data for KGDB will
discard any data received on other channels, which clearly isn't ideal,
but given that there is a single FIFO shared between channels we can't
do much better.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Jason Wessel <jason.wessel@windriver.com>
Cc: linux-mips@linux-mips.org
Cc: kgdb-bugreport@lists.sourceforge.net
---
 drivers/tty/Kconfig          |  16 ++++
 drivers/tty/mips_ejtag_fdc.c | 169 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 179 insertions(+), 6 deletions(-)

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index e0c18e5b7057..c01f45095877 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -450,4 +450,20 @@ config MIPS_EJTAG_FDC_EARLYCON
 
 	  If unsure, say N.
 
+config MIPS_EJTAG_FDC_KGDB
+	bool "Use KGDB over an FDC channel"
+	depends on MIPS_EJTAG_FDC_TTY && KGDB
+	default y
+	help
+          This enables the use of KGDB over an FDC channel, allowing KGDB to be
+          used remotely or when a serial port isn't available.
+
+config MIPS_EJTAG_FDC_KGDB_CHAN
+	int "KGDB FDC channel"
+	depends on MIPS_EJTAG_FDC_KGDB
+	range 2 15
+	default 3
+	help
+	  FDC channel number to use for KGDB.
+
 endif # TTY
diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c
index 8d9bf6f90110..04d9e23d1ee1 100644
--- a/drivers/tty/mips_ejtag_fdc.c
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -17,9 +17,11 @@
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
+#include <linux/kgdb.h>
 #include <linux/kthread.h>
 #include <linux/sched.h>
 #include <linux/serial.h>
+#include <linux/serial_core.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
@@ -136,6 +138,8 @@ struct mips_ejtag_fdc_tty_port {
  * @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.
+ * @sysrq_pressed:	Whether the magic sysrq key combination has been
+ *			detected. See mips_ejtag_fdc_handle().
  */
 struct mips_ejtag_fdc_tty {
 	struct device			*dev;
@@ -159,6 +163,10 @@ struct mips_ejtag_fdc_tty {
 	int				 irq;
 	bool				 removing;
 	struct timer_list		 poll_timer;
+
+#ifdef CONFIG_MAGIC_SYSRQ
+	bool				 sysrq_pressed;
+#endif
 };
 
 /* Hardware access */
@@ -568,21 +576,47 @@ static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv)
 		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)
+		for (i = 0; i < len; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+			/* Support just Ctrl+C with KGDB channel */
+			if (channel == CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN) {
+				if (buf[i] == '\x03') { /* ^C */
+					handle_sysrq('g');
+					continue;
+				}
+			}
+#endif
+			/* Support Ctrl+O for console channel */
+			if (channel == mips_ejtag_fdc_con.cons.index) {
+				if (buf[i] == '\x0f') {	/* ^O */
+					priv->sysrq_pressed =
+						!priv->sysrq_pressed;
+					if (priv->sysrq_pressed)
+						continue;
+				} else if (priv->sysrq_pressed) {
+					handle_sysrq(buf[i]);
+					priv->sysrq_pressed = false;
+					continue;
+				}
+			}
+#endif /* CONFIG_MAGIC_SYSRQ */
+
+			/* Check the port isn't being shut down */
+			if (!dport->rx_buf)
+				continue;
+
 			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);
 	}
 
@@ -1144,3 +1178,126 @@ int __init setup_early_fdc_console(void)
 	return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon);
 }
 #endif
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+
+/* read buffer to allow decompaction */
+static unsigned int kgdbfdc_rbuflen;
+static unsigned int kgdbfdc_rpos;
+static char kgdbfdc_rbuf[4];
+
+/* write buffer to allow compaction */
+static unsigned int kgdbfdc_wbuflen;
+static char kgdbfdc_wbuf[4];
+
+static void __iomem *kgdbfdc_setup(void)
+{
+	void __iomem *regs;
+	unsigned int cpu;
+
+	/* Find address, piggy backing off console percpu regs */
+	cpu = smp_processor_id();
+	regs = mips_ejtag_fdc_con.regs[cpu];
+	/* First console output on this CPU? */
+	if (!regs) {
+		regs = mips_cdmm_early_probe(0xfd);
+		mips_ejtag_fdc_con.regs[cpu] = regs;
+	}
+	/* Already tried and failed to find FDC on this CPU? */
+	if (IS_ERR(regs))
+		return regs;
+
+	return regs;
+}
+
+/* read a character from the read buffer, filling from FDC RX FIFO */
+static int kgdbfdc_read_char(void)
+{
+	unsigned int stat, channel, data;
+	void __iomem *regs;
+
+	/* No more data, try and read another FDC word from RX FIFO */
+	if (kgdbfdc_rpos >= kgdbfdc_rbuflen) {
+		kgdbfdc_rpos = 0;
+		kgdbfdc_rbuflen = 0;
+
+		regs = kgdbfdc_setup();
+		if (IS_ERR(regs))
+			return NO_POLL_CHAR;
+
+		/* Read next word from KGDB channel */
+		do {
+			stat = ioread32(regs + REG_FDSTAT);
+
+			/* No data waiting? */
+			if (stat & REG_FDSTAT_RXE)
+				return NO_POLL_CHAR;
+
+			/* Read next word */
+			channel = (stat & REG_FDSTAT_RXCHAN) >>
+					REG_FDSTAT_RXCHAN_SHIFT;
+			data = ioread32(regs + REG_FDRX);
+		} while (channel != CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN);
+
+		/* Decode into rbuf */
+		kgdbfdc_rbuflen = mips_ejtag_fdc_decode(data, kgdbfdc_rbuf);
+	}
+	pr_devel("kgdbfdc r %c\n", kgdbfdc_rbuf[kgdbfdc_rpos]);
+	return kgdbfdc_rbuf[kgdbfdc_rpos++];
+}
+
+/* push an FDC word from write buffer to TX FIFO */
+static void kgdbfdc_push_one(void)
+{
+	const char *bufs[1] = { kgdbfdc_wbuf };
+	struct fdc_word word;
+	void __iomem *regs;
+	unsigned int i;
+
+	/* Construct a word from any data in buffer */
+	word = mips_ejtag_fdc_encode(bufs, &kgdbfdc_wbuflen, 1);
+	/* Relocate any remaining data to beginnning of buffer */
+	kgdbfdc_wbuflen -= word.bytes;
+	for (i = 0; i < kgdbfdc_wbuflen; ++i)
+		kgdbfdc_wbuf[i] = kgdbfdc_wbuf[i + word.bytes];
+
+	regs = kgdbfdc_setup();
+	if (IS_ERR(regs))
+		return;
+
+	/* Busy wait until there's space in fifo */
+	while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF)
+		;
+	iowrite32(word.word, regs + REG_FDTX(CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN));
+}
+
+/* flush the whole write buffer to the TX FIFO */
+static void kgdbfdc_flush(void)
+{
+	while (kgdbfdc_wbuflen)
+		kgdbfdc_push_one();
+}
+
+/* write a character into the write buffer, writing out if full */
+static void kgdbfdc_write_char(u8 chr)
+{
+	pr_devel("kgdbfdc w %c\n", chr);
+	kgdbfdc_wbuf[kgdbfdc_wbuflen++] = chr;
+	if (kgdbfdc_wbuflen >= sizeof(kgdbfdc_wbuf))
+		kgdbfdc_push_one();
+}
+
+static struct kgdb_io kgdbfdc_io_ops = {
+	.name		= "kgdbfdc",
+	.read_char	= kgdbfdc_read_char,
+	.write_char	= kgdbfdc_write_char,
+	.flush		= kgdbfdc_flush,
+};
+
+static int __init kgdbfdc_init(void)
+{
+	kgdb_register_io_module(&kgdbfdc_io_ops);
+	return 0;
+}
+early_initcall(kgdbfdc_init);
+#endif
-- 
2.0.5

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH 0/9] Add MIPS EJTAG Fast Debug Channel TTY driver
@ 2015-02-25 11:06   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-02-25 11:06 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Jiri Slaby, Jason Wessel,
	kgdb-bugreport
  Cc: Ralf Baechle, linux-mips, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper

[-- Attachment #1: Type: text/plain, Size: 3146 bytes --]

Hi,

On 29/01/15 11:14, James Hogan wrote:
> This patchset adds a TTY, console, and KGDB driver for the MIPS Fast
> Debug Channel (FDC) hardware, for communicating with a debugger via an
> EJTAG probe. 16 TTY ports are created per FDC device, corresponding to
> the 16 FDC channels. Each VPE usually has its own FDC instance.

It'd be great to get these patches upstream for v4.1 via the MIPS tree
along with my other two patchsets it depends on ([v2] Add MIPS CDMM bus
support, and MIPS: Allow shared IRQ for timer & perf counter), so
Acks/Reviews from TTY maintainers in particular would be really
appreciated.

Btw, more info about the FDC can be found here:
http://www.linux-mips.org/wiki/FDC

And a git branch containing the latest version of all 3 patchsets
(basically just rebased on v4.0-rc1 to resolve conflicts) can be found
here:
git://git.kernel.org/pub/scm/linux/kernel/git/jhogan/mips.git fdc

Thanks
James

> 
> This patchset depends on my recent CDMM bus patchset (the FDC is in the
> per-CPU CDMM region), and my recent MIPS timer & perf counter IRQ
> sharing patchset (the FDC IRQ is a local CPU IRQ which may similarly
> share CPU IRQ lines with the other local IRQs).
> 
> Patches 1 to 6 add the necessary architecture bits for the FDC
> interrupt, and a workaround in the MIPS idle code to avoid the wait
> instruction on certain cores if FDC driver is enabled.
> 
> Finally patches 7-9 add the main TTY/console driver, wire up some early
> console code, and implement KGDB operations & magic sysrq.
> 
> Cc: Ralf Baechle <ralf@linux-mips.org>
> Cc: Andrew Bresticker <abrestic@chromium.org>
> Cc: Thomas Gleixner <tglx@linutronix.de>
> Cc: Jason Cooper <jason@lakedaemon.net>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jslaby@suse.cz>
> Cc: Jason Wessel <jason.wessel@windriver.com>
> Cc: linux-mips@linux-mips.org
> Cc: kgdb-bugreport@lists.sourceforge.net
> 
> James Hogan (9):
>   MIPS: Add architectural FDC IRQ fields
>   MIPS: Read CPU IRQ line that FDC to routed to
>   irqchip: mips-gic: Don't treat FDC IRQ as percpu devid
>   irqchip: mips-gic: Add function for retrieving FDC IRQ
>   MIPS: Malta: Implement get_c0_fdc_int()
>   MIPS: idle: Workaround wait + FDC problems
>   tty: Add MIPS EJTAG Fast Debug Channel TTY driver
>   MIPS, ttyFDC: Add early FDC console support
>   ttyFDC: Implement KGDB IO operations.
> 
>  arch/mips/include/asm/cdmm.h     |   11 +
>  arch/mips/include/asm/irq.h      |    3 +
>  arch/mips/include/asm/mipsregs.h |    4 +
>  arch/mips/kernel/idle.c          |   13 +-
>  arch/mips/kernel/setup.c         |    2 +
>  arch/mips/kernel/traps.c         |   11 +
>  arch/mips/mti-malta/malta-time.c |   16 +
>  drivers/irqchip/irq-mips-gic.c   |   38 +-
>  drivers/tty/Kconfig              |   47 ++
>  drivers/tty/Makefile             |    1 +
>  drivers/tty/mips_ejtag_fdc.c     | 1303 ++++++++++++++++++++++++++++++++++++++
>  include/linux/irqchip/mips-gic.h |    1 +
>  12 files changed, 1443 insertions(+), 7 deletions(-)
>  create mode 100644 drivers/tty/mips_ejtag_fdc.c
> 


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 0/9] Add MIPS EJTAG Fast Debug Channel TTY driver
@ 2015-02-25 11:06   ` James Hogan
  0 siblings, 0 replies; 25+ messages in thread
From: James Hogan @ 2015-02-25 11:06 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Jiri Slaby, Jason Wessel,
	kgdb-bugreport
  Cc: Ralf Baechle, linux-mips, Andrew Bresticker, Thomas Gleixner,
	Jason Cooper

[-- Attachment #1: Type: text/plain, Size: 3146 bytes --]

Hi,

On 29/01/15 11:14, James Hogan wrote:
> This patchset adds a TTY, console, and KGDB driver for the MIPS Fast
> Debug Channel (FDC) hardware, for communicating with a debugger via an
> EJTAG probe. 16 TTY ports are created per FDC device, corresponding to
> the 16 FDC channels. Each VPE usually has its own FDC instance.

It'd be great to get these patches upstream for v4.1 via the MIPS tree
along with my other two patchsets it depends on ([v2] Add MIPS CDMM bus
support, and MIPS: Allow shared IRQ for timer & perf counter), so
Acks/Reviews from TTY maintainers in particular would be really
appreciated.

Btw, more info about the FDC can be found here:
http://www.linux-mips.org/wiki/FDC

And a git branch containing the latest version of all 3 patchsets
(basically just rebased on v4.0-rc1 to resolve conflicts) can be found
here:
git://git.kernel.org/pub/scm/linux/kernel/git/jhogan/mips.git fdc

Thanks
James

> 
> This patchset depends on my recent CDMM bus patchset (the FDC is in the
> per-CPU CDMM region), and my recent MIPS timer & perf counter IRQ
> sharing patchset (the FDC IRQ is a local CPU IRQ which may similarly
> share CPU IRQ lines with the other local IRQs).
> 
> Patches 1 to 6 add the necessary architecture bits for the FDC
> interrupt, and a workaround in the MIPS idle code to avoid the wait
> instruction on certain cores if FDC driver is enabled.
> 
> Finally patches 7-9 add the main TTY/console driver, wire up some early
> console code, and implement KGDB operations & magic sysrq.
> 
> Cc: Ralf Baechle <ralf@linux-mips.org>
> Cc: Andrew Bresticker <abrestic@chromium.org>
> Cc: Thomas Gleixner <tglx@linutronix.de>
> Cc: Jason Cooper <jason@lakedaemon.net>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jslaby@suse.cz>
> Cc: Jason Wessel <jason.wessel@windriver.com>
> Cc: linux-mips@linux-mips.org
> Cc: kgdb-bugreport@lists.sourceforge.net
> 
> James Hogan (9):
>   MIPS: Add architectural FDC IRQ fields
>   MIPS: Read CPU IRQ line that FDC to routed to
>   irqchip: mips-gic: Don't treat FDC IRQ as percpu devid
>   irqchip: mips-gic: Add function for retrieving FDC IRQ
>   MIPS: Malta: Implement get_c0_fdc_int()
>   MIPS: idle: Workaround wait + FDC problems
>   tty: Add MIPS EJTAG Fast Debug Channel TTY driver
>   MIPS, ttyFDC: Add early FDC console support
>   ttyFDC: Implement KGDB IO operations.
> 
>  arch/mips/include/asm/cdmm.h     |   11 +
>  arch/mips/include/asm/irq.h      |    3 +
>  arch/mips/include/asm/mipsregs.h |    4 +
>  arch/mips/kernel/idle.c          |   13 +-
>  arch/mips/kernel/setup.c         |    2 +
>  arch/mips/kernel/traps.c         |   11 +
>  arch/mips/mti-malta/malta-time.c |   16 +
>  drivers/irqchip/irq-mips-gic.c   |   38 +-
>  drivers/tty/Kconfig              |   47 ++
>  drivers/tty/Makefile             |    1 +
>  drivers/tty/mips_ejtag_fdc.c     | 1303 ++++++++++++++++++++++++++++++++++++++
>  include/linux/irqchip/mips-gic.h |    1 +
>  12 files changed, 1443 insertions(+), 7 deletions(-)
>  create mode 100644 drivers/tty/mips_ejtag_fdc.c
> 


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 7/9] tty: Add MIPS EJTAG Fast Debug Channel TTY driver
  2015-01-29 11:14   ` James Hogan
  (?)
@ 2015-03-26 15:09   ` Greg Kroah-Hartman
  -1 siblings, 0 replies; 25+ messages in thread
From: Greg Kroah-Hartman @ 2015-03-26 15:09 UTC (permalink / raw)
  To: James Hogan; +Cc: Ralf Baechle, linux-mips, linux-kernel, Jiri Slaby

On Thu, Jan 29, 2015 at 11:14:12AM +0000, James Hogan wrote:
> Add TTY driver and consoles for the MIPS EJTAG Fast Debug Channel (FDC),
> which is found on the per-CPU MIPS Common Device Mapped Memory (CDMM)
> bus.
> 
> The FDC is a per-CPU device which is used to communicate with an EJTAG
> probe. RX and TX FIFOs exist, containing 32-bits of data and 4-bit
> channel numbers. 16 general data streams are implemented on this for TTY
> and console use by encoding up to 4 bytes on each 32-bit FDC word.
> 
> The TTY devices are named e.g. /dev/ttyFDC3c2 for channel 2 of the FDC
> attached to logical CPU 3.
> 
> These can be used for getting the kernel log, a login prompt, or as a
> GDB remote transport, all over EJTAG and without needing a serial port.
> 
> It can have an interrupt to notify of when incoming data is available in
> the RX FIFO or when the TX FIFO is no longer full. The detection of this
> interrupt occurs in architecture / platform code, but it may be shared
> with the timer and/or performance counter interrupt.
> 
> Due to the per-CPU nature of the hardware, all outgoing TTY data is
> written out from a kthread which is pinned to the appropriate CPU.
> 
> The console is not bound to a specific CPU, so output will appear on the
> chosen channel on whichever CPU the code is executing on. Enable with
> e.g. console=fdc1 in kernel arguments. /dev/console is bound to the same
> channel on the boot CPU's FDC if it exists.
> 
> Signed-off-by: James Hogan <james.hogan@imgtec.com>
> Cc: Ralf Baechle <ralf@linux-mips.org>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jslaby@suse.cz>
> Cc: linux-mips@linux-mips.org

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 8/9] MIPS, ttyFDC: Add early FDC console support
  2015-01-29 11:14   ` James Hogan
  (?)
@ 2015-03-26 15:09   ` Greg Kroah-Hartman
  -1 siblings, 0 replies; 25+ messages in thread
From: Greg Kroah-Hartman @ 2015-03-26 15:09 UTC (permalink / raw)
  To: James Hogan; +Cc: Ralf Baechle, linux-mips, linux-kernel, Jiri Slaby

On Thu, Jan 29, 2015 at 11:14:13AM +0000, James Hogan wrote:
> Add support for early console of MIPS Fast Debug Channel (FDC) on
> channel 1 with a call very early from the MIPS setup_arch().
> 
> Signed-off-by: James Hogan <james.hogan@imgtec.com>
> Cc: Ralf Baechle <ralf@linux-mips.org>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jslaby@suse.cz>
> Cc: linux-mips@linux-mips.org
> ---
>  arch/mips/include/asm/cdmm.h | 11 +++++++++++
>  arch/mips/kernel/setup.c     |  2 ++
>  drivers/tty/Kconfig          | 13 +++++++++++++
>  drivers/tty/mips_ejtag_fdc.c | 20 ++++++++++++++++++++
>  4 files changed, 46 insertions(+)

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 9/9] ttyFDC: Implement KGDB IO operations.
  2015-01-29 11:14   ` James Hogan
  (?)
@ 2015-03-26 15:09   ` Greg Kroah-Hartman
  -1 siblings, 0 replies; 25+ messages in thread
From: Greg Kroah-Hartman @ 2015-03-26 15:09 UTC (permalink / raw)
  To: James Hogan
  Cc: Ralf Baechle, linux-mips, linux-kernel, Jiri Slaby, Jason Wessel,
	kgdb-bugreport

On Thu, Jan 29, 2015 at 11:14:14AM +0000, James Hogan wrote:
> Implement KGDB IO operations for MIPS Fast Debug Channel (FDC). This can
> be enabled via Kconfig, which also allows the channel number to be
> chosen.
> 
> The magic sysrq hack is implemented in the TTY driver, detecting just ^C
> for the KGDB channel, and ^O followed by a letter for the FDC console
> channel.
> 
> The KGDB operations are reasonably efficient thanks to the flush
> callback, with a 4 byte buffer being used in both directions to allow up
> to 4 bytes to be encoded per FDC word. Reading of data for KGDB will
> discard any data received on other channels, which clearly isn't ideal,
> but given that there is a single FIFO shared between channels we can't
> do much better.
> 
> Signed-off-by: James Hogan <james.hogan@imgtec.com>
> Cc: Ralf Baechle <ralf@linux-mips.org>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jslaby@suse.cz>
> Cc: Jason Wessel <jason.wessel@windriver.com>
> Cc: linux-mips@linux-mips.org
> Cc: kgdb-bugreport@lists.sourceforge.net

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2015-03-26 15:09 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-01-29 11:14 [PATCH 0/9] Add MIPS EJTAG Fast Debug Channel TTY driver James Hogan
2015-01-29 11:14 ` James Hogan
2015-01-29 11:14 ` [PATCH 1/9] MIPS: Add architectural FDC IRQ fields James Hogan
2015-01-29 11:14   ` James Hogan
2015-01-29 11:14 ` [PATCH 2/9] MIPS: Read CPU IRQ line that FDC to routed to James Hogan
2015-01-29 11:14   ` James Hogan
2015-01-29 11:14 ` [PATCH 3/9] irqchip: mips-gic: Don't treat FDC IRQ as percpu devid James Hogan
2015-01-29 11:14   ` James Hogan
2015-01-29 11:14 ` [PATCH 4/9] irqchip: mips-gic: Add function for retrieving FDC IRQ James Hogan
2015-01-29 11:14   ` James Hogan
2015-01-29 11:14 ` [PATCH 5/9] MIPS: Malta: Implement get_c0_fdc_int() James Hogan
2015-01-29 11:14   ` James Hogan
2015-01-29 11:14 ` [PATCH 6/9] MIPS: idle: Workaround wait + FDC problems James Hogan
2015-01-29 11:14   ` James Hogan
2015-01-29 11:14 ` [PATCH 7/9] tty: Add MIPS EJTAG Fast Debug Channel TTY driver James Hogan
2015-01-29 11:14   ` James Hogan
2015-03-26 15:09   ` Greg Kroah-Hartman
2015-01-29 11:14 ` [PATCH 8/9] MIPS, ttyFDC: Add early FDC console support James Hogan
2015-01-29 11:14   ` James Hogan
2015-03-26 15:09   ` Greg Kroah-Hartman
2015-01-29 11:14 ` [PATCH 9/9] ttyFDC: Implement KGDB IO operations James Hogan
2015-01-29 11:14   ` James Hogan
2015-03-26 15:09   ` Greg Kroah-Hartman
2015-02-25 11:06 ` [PATCH 0/9] Add MIPS EJTAG Fast Debug Channel TTY driver James Hogan
2015-02-25 11:06   ` James Hogan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.