Linux Serial subsystem development
 help / color / mirror / Atom feed
* [PATCH 8/8] clocksource: xilinx_ttc: add OF_CLK support
From: Josh Cartwright @ 2012-10-31 19:56 UTC (permalink / raw)
  To: Grant Likely, Rob Herring, Russell King, Mike Turquette,
	John Stultz, Thomas Gleixner, Alan Cox, Greg Kroah-Hartman,
	John Linn, Michal Simek
  Cc: devicetree-discuss, linux-kernel, linux-arm-kernel, linux-serial,
	Michal Simek
In-Reply-To: <cover.1351721190.git.josh.cartwright@ni.com>

Add support for retrieving TTC configuration from device tree.  This
includes the ability to pull information about the driving clocks from
the of_clk bindings.

Signed-off-by: Josh Cartwright <josh.cartwright@ni.com>
---
 arch/arm/boot/dts/zynq-7000.dtsi |  53 ++++++++
 arch/arm/boot/dts/zynq-zc702.dts |  10 ++
 drivers/clocksource/xilinx_ttc.c | 273 ++++++++++++++++++++++-----------------
 3 files changed, 218 insertions(+), 118 deletions(-)

diff --git a/arch/arm/boot/dts/zynq-7000.dtsi b/arch/arm/boot/dts/zynq-7000.dtsi
index 5fb763f..9a2442c 100644
--- a/arch/arm/boot/dts/zynq-7000.dtsi
+++ b/arch/arm/boot/dts/zynq-7000.dtsi
@@ -109,5 +109,58 @@
 				};
 			};
 		};
+
+		ttc0: ttc0@f8001000 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "xlnx,ttc";
+			reg = <0xF8001000 0x1000>;
+			clocks = <&cpu_clk 3>;
+			clock-names = "cpu_1x";
+			clock-ranges;
+
+			ttc0_0: ttc0.0 {
+				status = "disabled";
+				reg = <0>;
+				interrupts = <0 10 4>;
+			};
+			ttc0_1: ttc0.1 {
+				status = "disabled";
+				reg = <1>;
+				interrupts = <0 11 4>;
+			};
+			ttc0_2: ttc0.2 {
+				status = "disabled";
+				reg = <2>;
+				interrupts = <0 12 4>;
+			};
+		};
+
+		ttc1: ttc0@f8002000 {
+			#interrupt-parent = <&intc>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "xlnx,ttc";
+			reg = <0xF8002000 0x1000>;
+			clocks = <&cpu_clk 3>;
+			clock-names = "cpu_1x";
+			clock-ranges;
+
+			ttc1_0: ttc1.0 {
+				status = "disabled";
+				reg = <0>;
+				interrupts = <0 37 4>;
+			};
+			ttc1_1: ttc1.1 {
+				status = "disabled";
+				reg = <1>;
+				interrupts = <0 38 4>;
+			};
+			ttc1_2: ttc1.2 {
+				status = "disabled";
+				reg = <2>;
+				interrupts = <0 39 4>;
+			};
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/zynq-zc702.dts b/arch/arm/boot/dts/zynq-zc702.dts
index 86f44d5..c772942 100644
--- a/arch/arm/boot/dts/zynq-zc702.dts
+++ b/arch/arm/boot/dts/zynq-zc702.dts
@@ -32,3 +32,13 @@
 &ps_clk {
 	clock-frequency = <33333330>;
 };
+
+&ttc0_0 {
+	status = "ok";
+	compatible = "xlnx,ttc-counter-clocksource";
+};
+
+&ttc0_1 {
+	status = "ok";
+	compatible = "xlnx,ttc-counter-clockevent";
+};
diff --git a/drivers/clocksource/xilinx_ttc.c b/drivers/clocksource/xilinx_ttc.c
index ff38b3e..a4718f7 100644
--- a/drivers/clocksource/xilinx_ttc.c
+++ b/drivers/clocksource/xilinx_ttc.c
@@ -23,30 +23,14 @@
 #include <linux/clocksource.h>
 #include <linux/clockchips.h>
 #include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
 
 #include <mach/zynq_soc.h>
 
-#define IRQ_TIMERCOUNTER0	42
-
-/*
- * This driver configures the 2 16-bit count-up timers as follows:
- *
- * T1: Timer 1, clocksource for generic timekeeping
- * T2: Timer 2, clockevent source for hrtimers
- * T3: Timer 3, <unused>
- *
- * The input frequency to the timer module for emulation is 2.5MHz which is
- * common to all the timer channels (T1, T2, and T3). With a pre-scaler of 32,
- * the timers are clocked at 78.125KHz (12.8 us resolution).
- *
- * The input frequency to the timer module in silicon will be 200MHz. With the
- * pre-scaler of 32, the timers are clocked at 6.25MHz (160ns resolution).
- */
-#define XTTCPSS_CLOCKSOURCE	0	/* Timer 1 as a generic timekeeping */
-#define XTTCPSS_CLOCKEVENT	1	/* Timer 2 as a clock event */
-
-#define XTTCPSS_TIMER_BASE		TTC0_BASE
-#define XTTCPCC_EVENT_TIMER_IRQ		(IRQ_TIMERCOUNTER0 + 1)
 /*
  * Timer Register Offset Definitions of Timer 1, Increment base address by 4
  * and use same offsets for Timer 2
@@ -63,9 +47,14 @@
 
 #define XTTCPSS_CNT_CNTRL_DISABLE_MASK	0x1
 
-/* Setup the timers to use pre-scaling */
-
-#define TIMER_RATE (PERIPHERAL_CLOCK_RATE / 32)
+/* Setup the timers to use pre-scaling, using a fixed value for now that will
+ * work across most input frequency, but it may need to be more dynamic
+ */
+#define PRESCALE_EXPONENT	11	/* 2 ^ PRESCALE_EXPONENT = PRESCALE */
+#define PRESCALE		2048	/* The exponent must match this */
+#define CLK_CNTRL_PRESCALE	((PRESCALE_EXPONENT - 1) << 1)
+#define CLK_CNTRL_PRESCALE_EN	1
+#define CNT_CNTRL_RESET		(1<<4)
 
 /**
  * struct xttcpss_timer - This definition defines local timer structure
@@ -73,11 +62,25 @@
  * @base_addr:	Base address of timer
  **/
 struct xttcpss_timer {
-	void __iomem *base_addr;
+	void __iomem	*base_addr;
+};
+
+struct xttcpss_timer_clocksource {
+	struct xttcpss_timer	xttc;
+	struct clocksource	cs;
 };
 
-static struct xttcpss_timer timers[2];
-static struct clock_event_device xttcpss_clockevent;
+#define to_xttcpss_timer_clksrc(x) \
+		container_of(x, struct xttcpss_timer_clocksource, cs)
+
+struct xttcpss_timer_clockevent {
+	struct xttcpss_timer		xttc;
+	struct clock_event_device	ce;
+	struct clk			*clk;
+};
+
+#define to_xttcpss_timer_clkevent(x) \
+		container_of(x, struct xttcpss_timer_clockevent, ce)
 
 /**
  * xttcpss_set_interval - Set the timer interval value
@@ -99,7 +102,7 @@ static void xttcpss_set_interval(struct xttcpss_timer *timer,
 
 	/* Reset the counter (0x10) so that it starts from 0, one-shot
 	   mode makes this needed for timing to be right. */
-	ctrl_reg |= 0x10;
+	ctrl_reg |= CNT_CNTRL_RESET;
 	ctrl_reg &= ~XTTCPSS_CNT_CNTRL_DISABLE_MASK;
 	__raw_writel(ctrl_reg, timer->base_addr + XTTCPSS_CNT_CNTRL_OFFSET);
 }
@@ -114,90 +117,31 @@ static void xttcpss_set_interval(struct xttcpss_timer *timer,
  **/
 static irqreturn_t xttcpss_clock_event_interrupt(int irq, void *dev_id)
 {
-	struct clock_event_device *evt = &xttcpss_clockevent;
-	struct xttcpss_timer *timer = dev_id;
+	struct xttcpss_timer_clockevent *xttce = dev_id;
+	struct xttcpss_timer *timer = &xttce->xttc;
 
 	/* Acknowledge the interrupt and call event handler */
 	__raw_writel(__raw_readl(timer->base_addr + XTTCPSS_ISR_OFFSET),
 			timer->base_addr + XTTCPSS_ISR_OFFSET);
 
-	evt->event_handler(evt);
+	xttce->ce.event_handler(&xttce->ce);
 
 	return IRQ_HANDLED;
 }
 
-static struct irqaction event_timer_irq = {
-	.name	= "xttcpss clockevent",
-	.flags	= IRQF_DISABLED | IRQF_TIMER,
-	.handler = xttcpss_clock_event_interrupt,
-};
-
-/**
- * xttcpss_timer_hardware_init - Initialize the timer hardware
- *
- * Initialize the hardware to start the clock source, get the clock
- * event timer ready to use, and hook up the interrupt.
- **/
-static void __init xttcpss_timer_hardware_init(void)
-{
-	/* Setup the clock source counter to be an incrementing counter
-	 * with no interrupt and it rolls over at 0xFFFF. Pre-scale
-	   it by 32 also. Let it start running now.
-	 */
-	timers[XTTCPSS_CLOCKSOURCE].base_addr = XTTCPSS_TIMER_BASE;
-
-	__raw_writel(0x0, timers[XTTCPSS_CLOCKSOURCE].base_addr +
-				XTTCPSS_IER_OFFSET);
-	__raw_writel(0x9, timers[XTTCPSS_CLOCKSOURCE].base_addr +
-				XTTCPSS_CLK_CNTRL_OFFSET);
-	__raw_writel(0x10, timers[XTTCPSS_CLOCKSOURCE].base_addr +
-				XTTCPSS_CNT_CNTRL_OFFSET);
-
-	/* Setup the clock event timer to be an interval timer which
-	 * is prescaled by 32 using the interval interrupt. Leave it
-	 * disabled for now.
-	 */
-
-	timers[XTTCPSS_CLOCKEVENT].base_addr = XTTCPSS_TIMER_BASE + 4;
-
-	__raw_writel(0x23, timers[XTTCPSS_CLOCKEVENT].base_addr +
-			XTTCPSS_CNT_CNTRL_OFFSET);
-	__raw_writel(0x9, timers[XTTCPSS_CLOCKEVENT].base_addr +
-			XTTCPSS_CLK_CNTRL_OFFSET);
-	__raw_writel(0x1, timers[XTTCPSS_CLOCKEVENT].base_addr +
-			XTTCPSS_IER_OFFSET);
-
-	/* Setup IRQ the clock event timer */
-	event_timer_irq.dev_id = &timers[XTTCPSS_CLOCKEVENT];
-	setup_irq(XTTCPCC_EVENT_TIMER_IRQ, &event_timer_irq);
-}
-
 /**
- * __raw_readl_cycles - Reads the timer counter register
+ * __xttc_clocksource_read - Reads the timer counter register
  *
  * returns: Current timer counter register value
  **/
-static cycle_t __raw_readl_cycles(struct clocksource *cs)
+static cycle_t __xttc_clocksource_read(struct clocksource *cs)
 {
-	struct xttcpss_timer *timer = &timers[XTTCPSS_CLOCKSOURCE];
+	struct xttcpss_timer *timer = &to_xttcpss_timer_clksrc(cs)->xttc;
 
 	return (cycle_t)__raw_readl(timer->base_addr +
 				XTTCPSS_COUNT_VAL_OFFSET);
 }
 
-
-/*
- * Instantiate and initialize the clock source structure
- */
-static struct clocksource clocksource_xttcpss = {
-	.name		= "xttcpss_timer1",
-	.rating		= 200,			/* Reasonable clock source */
-	.read		= __raw_readl_cycles,
-	.mask		= CLOCKSOURCE_MASK(16),
-	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
-};
-
-
 /**
  * xttcpss_set_next_event - Sets the time interval for next event
  *
@@ -209,7 +153,8 @@ static struct clocksource clocksource_xttcpss = {
 static int xttcpss_set_next_event(unsigned long cycles,
 					struct clock_event_device *evt)
 {
-	struct xttcpss_timer *timer = &timers[XTTCPSS_CLOCKEVENT];
+	struct xttcpss_timer_clockevent *xttce = to_xttcpss_timer_clkevent(evt);
+	struct xttcpss_timer *timer = &xttce->xttc;
 
 	xttcpss_set_interval(timer, cycles);
 	return 0;
@@ -224,12 +169,14 @@ static int xttcpss_set_next_event(unsigned long cycles,
 static void xttcpss_set_mode(enum clock_event_mode mode,
 					struct clock_event_device *evt)
 {
-	struct xttcpss_timer *timer = &timers[XTTCPSS_CLOCKEVENT];
+	struct xttcpss_timer_clockevent *xttce = to_xttcpss_timer_clkevent(evt);
+	struct xttcpss_timer *timer = &xttce->xttc;
 	u32 ctrl_reg;
 
 	switch (mode) {
 	case CLOCK_EVT_MODE_PERIODIC:
-		xttcpss_set_interval(timer, TIMER_RATE / HZ);
+		xttcpss_set_interval(timer,
+				     clk_get_rate(xttce->clk) / PRESCALE);
 		break;
 	case CLOCK_EVT_MODE_ONESHOT:
 	case CLOCK_EVT_MODE_UNUSED:
@@ -250,15 +197,99 @@ static void xttcpss_set_mode(enum clock_event_mode mode,
 	}
 }
 
-/*
- * Instantiate and initialize the clock event structure
- */
-static struct clock_event_device xttcpss_clockevent = {
-	.name		= "xttcpss_timer2",
-	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
-	.set_next_event	= xttcpss_set_next_event,
-	.set_mode	= xttcpss_set_mode,
-	.rating		= 200,
+static int __init zynq_ttc_setup_clocksource(struct device_node *np,
+					     void __iomem *base)
+{
+	struct xttcpss_timer_clocksource *ttccs;
+	struct clk *clk;
+	int err;
+	u32 reg;
+
+	ttccs = kzalloc(sizeof(*ttccs), GFP_KERNEL);
+	WARN_ON(!ttccs);
+
+	err = of_property_read_u32(np, "reg", &reg);
+	WARN_ON(err);
+
+	clk = of_clk_get_by_name(np, "cpu_1x");
+	WARN_ON(IS_ERR(clk));
+
+	err = clk_prepare_enable(clk);
+	WARN_ON(err);
+
+	ttccs->xttc.base_addr = base + reg * 4;
+
+	ttccs->cs.name = np->name;
+	ttccs->cs.rating = 200;
+	ttccs->cs.read = __xttc_clocksource_read;
+	ttccs->cs.mask = CLOCKSOURCE_MASK(16);
+	ttccs->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+
+	__raw_writel(0x0,  ttccs->xttc.base_addr + XTTCPSS_IER_OFFSET);
+	__raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,
+		     ttccs->xttc.base_addr + XTTCPSS_CLK_CNTRL_OFFSET);
+	__raw_writel(CNT_CNTRL_RESET,
+		     ttccs->xttc.base_addr + XTTCPSS_CNT_CNTRL_OFFSET);
+
+	err = clocksource_register_hz(&ttccs->cs, clk_get_rate(clk) / PRESCALE);
+	WARN_ON(err);
+
+	return 0;
+}
+
+static int __init zynq_ttc_setup_clockevent(struct device_node *np,
+					    void __iomem *base)
+{
+	struct xttcpss_timer_clockevent *ttcce;
+	int err, irq;
+	u32 reg;
+
+	ttcce = kzalloc(sizeof(*ttcce), GFP_KERNEL);
+	WARN_ON(!ttcce);
+
+	err = of_property_read_u32(np, "reg", &reg);
+	WARN_ON(err);
+
+	ttcce->xttc.base_addr = base + reg * 4;
+
+	ttcce->clk = of_clk_get_by_name(np, "cpu_1x");
+	WARN_ON(IS_ERR(ttcce->clk));
+
+	err = clk_prepare_enable(ttcce->clk);
+	WARN_ON(err);
+
+	irq = irq_of_parse_and_map(np, 0);
+	WARN_ON(!irq);
+
+	ttcce->ce.name = np->name;
+	ttcce->ce.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+	ttcce->ce.set_next_event = xttcpss_set_next_event;
+	ttcce->ce.set_mode = xttcpss_set_mode;
+	ttcce->ce.rating = 200;
+	ttcce->ce.irq = irq;
+
+	__raw_writel(0x23, ttcce->xttc.base_addr + XTTCPSS_CNT_CNTRL_OFFSET);
+	__raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,
+		     ttcce->xttc.base_addr + XTTCPSS_CLK_CNTRL_OFFSET);
+	__raw_writel(0x1,  ttcce->xttc.base_addr + XTTCPSS_IER_OFFSET);
+
+	err = request_irq(irq, xttcpss_clock_event_interrupt, IRQF_TIMER,
+			  np->name, ttcce);
+	WARN_ON(err);
+
+	clockevents_config_and_register(&ttcce->ce,
+					clk_get_rate(ttcce->clk) / PRESCALE,
+					1, 0xfffe);
+
+	return 0;
+}
+
+static const __initconst struct of_device_id zynq_ttc_match[] = {
+	{ .compatible = "xlnx,ttc-counter-clocksource",
+		.data = zynq_ttc_setup_clocksource, },
+	{ .compatible = "xlnx,ttc-counter-clockevent",
+		.data = zynq_ttc_setup_clockevent, },
+	{}
 };
 
 /**
@@ -269,21 +300,27 @@ static struct clock_event_device xttcpss_clockevent = {
  **/
 void __init xttcpss_timer_init(void)
 {
-	xttcpss_timer_hardware_init();
-	clocksource_register_hz(&clocksource_xttcpss, TIMER_RATE);
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "xlnx,ttc") {
+		struct device_node *np_chld;
+		void __iomem *base;
 
-	/* Calculate the parameters to allow the clockevent to operate using
-	   integer math
-	*/
-	clockevents_calc_mult_shift(&xttcpss_clockevent, TIMER_RATE, 4);
+		base = of_iomap(np, 0);
+		WARN_ON(!base);
 
-	xttcpss_clockevent.max_delta_ns =
-		clockevent_delta2ns(0xfffe, &xttcpss_clockevent);
-	xttcpss_clockevent.min_delta_ns =
-		clockevent_delta2ns(1, &xttcpss_clockevent);
+		for_each_available_child_of_node(np, np_chld) {
+			int (*cb)(struct device_node *np, void __iomem *base);
+			const struct of_device_id *match;
 
-	/* Indicate that clock event is on 1st CPU as SMP boot needs it */
+			match = of_match_node(zynq_ttc_match, np_chld);
+			if (match) {
+				int err;
 
-	xttcpss_clockevent.cpumask = cpumask_of(0);
-	clockevents_register_device(&xttcpss_clockevent);
+				cb = match->data;
+				err = cb(np_chld, base);
+				WARN_ON(err);
+			}
+		}
+	}
 }
-- 
1.8.0

^ permalink raw reply related

* Re: New serial card development
From: Matt Schulte @ 2012-10-31 21:55 UTC (permalink / raw)
  To: Greg KH; +Cc: Theodore Ts'o, Alan Cox, linux-serial
In-Reply-To: <CAJp1Oe7MMuk8TNBom2EDXx6UBdMV6qAE5fDdKTZ0o-329QSdaA@mail.gmail.com>

On Mon, Oct 29, 2012 at 3:04 PM, Matt Schulte
<matts@commtech-fastcom.com> wrote:
> On Tue, Oct 23, 2012 at 1:38 PM, Greg KH <greg@kroah.com> wrote:
>> On Tue, Oct 23, 2012 at 11:31:41AM -0500, Matt Schulte wrote:
>>> On Tue, Oct 23, 2012 at 11:27 AM, Matt Schulte
>>> <matts@commtech-fastcom.com> wrote:
>>> > On Fri, Oct 19, 2012 at 4:21 PM, Theodore Ts'o <tytso@mit.edu> wrote:
>>> >> Alan's advice to get your card working as a basic serial card is very
>>> >> good one.  Get basic functionality working, and then you can add the
>>> >> support for the extra bits later....
>>> >
>>> > I can see the logic in getting it working as a basic serial card
>>> > first.  I think at minimum I would still need to implement the extra
>>> > divisor calculations to get accurate bit rates.
>>> >
>>> > So when it works as a basic serial card, I assume you would want me to
>>> > use the default PCI IDs to keep it more generic.  Then would I add my
>>> > own PCI IDs and refer them back to the generic port?
>>> >
>
> I am working to get the Exar devices added to the 8250 driver.  I
> think I get most of what I need to make happen until I get to the
> detection of the EFR register.  Can someone explain to me what is
> going on here and where the Exar device is supposed to end up?  It
> seems to me that I would expect them to show up somewhere around :
>
> /*
>  * Exar uarts have EFR in a weird location
>  */
>
> But instead I am getting my UARTs detected as EFRv1, iir=3 and marked
> as type=ST16650.
>
> For reference the Exar chips have EFR located at an address of 1001
> (UART_XR_EFR).  There isn't anything special that has to be done to
> access this location, just look at 1001.

I was able to work my way through the funky EFR detection.  Hopefully
you guys will like what I did when you see the patch.

Next I need to know the channel number of a given UART port.  If I
have a 4 port UART I need to know that a given channel is 0,1,2 or 3.
If I have an 8 port UART I need to know if a given channel is
0,1,2,3,4,5,6 or 7.

The only way that I can find to get this number is using the idx
variable that is passed to the port's .setup operation.  Would I be
able to add a member to the uart_port structure so that I could store
this number inside my UARTs setup function?

Something like:

struct uart_port {
.
.
.
unsigned int channel;
};

pci_myuart_setup(struct serial_private *priv,
                const struct pciserial_board *board,
                struct uart_8250_port *port, int idx)
{
...
        port->port.channel = idx;
        return pci_default_setup(priv, board, port, idx);
}

Thank you,
Matt Schulte

^ permalink raw reply

* [PATCH 0/8] zynq COMMON_CLK support
From: Josh Cartwright @ 2012-10-31 22:06 UTC (permalink / raw)
  To: Grant Likely, Rob Herring, Russell King, Mike Turquette,
	John Stultz, Thomas Gleixner, Alan Cox, Greg Kroah-Hartman,
	John Linn, Michal Simek
  Cc: devicetree-discuss, linux-kernel, linux-arm-kernel, linux-serial,
	Michal Simek

This patchset implements COMMON_CLK support for the zynq.  At this
point, only the basic fundamental clocks are modelled, and only
passively; for rate calculation.  of_clk bindings are implemented to
allow specifying clock/peripheral relationships in the device tree.

Patch 1 and 2 are a followup to my early patch: "ARM: zynq: move ttc
timer code to drivers/clocksource".  Patch 1 moves the definition
sys_timer definition out of the ttc code, and into the zynq common code.
Patch 2 is the actual rename, and makefile cleanup.

Patch 3 adds a description of the second uart to zynq-ep107.dts.  I did
this pre-split (patch 4), because I felt it might make reviewing easier.

Patch 4 uses zynq-ep107.dts as a reference to create zynq-7000.dtsi,
which is intended to be a common dtsi snippet for inclusion in
describing zynq-7000 based boards.  zynq-zc702.dts is created as an
example consumer.  The zynq-ep107.dts file is removed entirely (it
describes, presumably, a board not available to consumers).

Patch 5 is the real meat; it adds an implementation of the clk models
for the PLLs, the CPU clock network, and basic (simplified) clk models
for the essential peripherals (UART and the TTC).

Patch 6 removes CONFIG_OF conditional code from the xilinx uart driver.
The zynq kernel requires CONFIG_OF, and this hardware is not currently
used on any other non CONFIG_OF platform.

Patch 7 adds support to the xilinx_uartps driver to allow getting clock
rate information form the device tree.

Patch 8 implements DT support for the ttc, including pulling clock tree
info.

---
There are some specific concerns that I had that I would like some
guidance on:

Two identical timers on the board have historically been statically
allocated to act as the system clocksource, and the clockevent_device.
With patch 8, this distinction is done in the device tree by tweaking
with the compatible properties of which of the timers you want used for
what purpose.

I feel, however, that this is an abuse of the device tree, which should
only be used to describe hardware, not to layout a policy on how the
hardware is used.

So, if it's not in the device tree, then where?  Do I go back to the
static allocation routine, such that the first matching ttc node in the
tree becomes the clockevent_device, and the second one a clocksource?
That seems like a hack.

Is it somehow possible to have all of the timers registered as both a
clocksource and a clockevent_device, and have some higher level logic
make the policy decision as to which timer is used for what?

An additional question regarding of_clk bindings:

	my_clock {
		#clock-cells = <0>;
		clock-output-names = "my_out_clock";
	};
	node_a {
		clocks = <&clk>;
		clock-names = "my_clock";
		clock-ranges;

		node_b {
			/* ... */
		};
	};

In this scenario, should I be expecting of_clk_get(node_b, 0) to
retrieve a handle to parent's consumed clock (due to clock-ranges)?  I
could make this work using of_clk_get_by_name(node_b, "my_clock"), but I
was somewhat surprised the former didn't work.

Thanks (and sorry for the novel),
   Josh

---
Josh Cartwright (8):
  ARM: zynq: move arm-specific sys_timer out of ttc
  ARM: zynq: move ttc timer code to drivers/clocksource
  ARM: zynq: dts: add description of the second uart
  ARM: zynq: dts: split up device tree
  ARM: zynq: add COMMON_CLK support
  serial: xilinx_uartps: kill CONFIG_OF conditional
  serial: xilinx_uartps: get clock rate info from dts
  clocksource: xilinx_ttc: add OF_CLK support

 .../devicetree/bindings/clock/zynq-7000.txt        |  55 ++++
 arch/arm/Kconfig                                   |   1 +
 arch/arm/boot/dts/Makefile                         |   1 +
 arch/arm/boot/dts/zynq-7000.dtsi                   | 166 ++++++++++
 arch/arm/boot/dts/zynq-ep107.dts                   |  63 ----
 arch/arm/boot/dts/zynq-zc702.dts                   |  44 +++
 arch/arm/mach-zynq/Makefile                        |   2 +-
 arch/arm/mach-zynq/common.c                        |  29 +-
 arch/arm/mach-zynq/timer.c                         | 298 -----------------
 drivers/clk/Makefile                               |   1 +
 drivers/clk/clk-zynq.c                             | 355 +++++++++++++++++++++
 drivers/clocksource/Makefile                       |   1 +
 drivers/clocksource/xilinx_ttc.c                   | 326 +++++++++++++++++++
 drivers/tty/serial/xilinx_uartps.c                 |  39 +--
 include/linux/clk/zynq.h                           |  24 ++
 .../common.h => include/linux/xilinx_ttc.h         |   8 +-
 16 files changed, 1022 insertions(+), 391 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/clock/zynq-7000.txt
 create mode 100644 arch/arm/boot/dts/zynq-7000.dtsi
 delete mode 100644 arch/arm/boot/dts/zynq-ep107.dts
 create mode 100644 arch/arm/boot/dts/zynq-zc702.dts
 delete mode 100644 arch/arm/mach-zynq/timer.c
 create mode 100644 drivers/clk/clk-zynq.c
 create mode 100644 drivers/clocksource/xilinx_ttc.c
 create mode 100644 include/linux/clk/zynq.h
 rename arch/arm/mach-zynq/common.h => include/linux/xilinx_ttc.h (81%)

-- 
1.8.0

^ permalink raw reply

* [PATCH -next] tty: of_serial: fix return value check in of_platform_serial_setup()
From: Wei Yongjun @ 2012-11-01  5:27 UTC (permalink / raw)
  To: alan, gregkh, grant.likely, rob.herring, seth.heasley
  Cc: yongjun_wei, linux-serial, devicetree-discuss

From: Wei Yongjun <yongjun_wei@trendmicro.com.cn>

In case of error, the function clk_get() returns ERR_PTR()
and never returns NULL. The NULL test in the return value
check should be replaced with IS_ERR().

dpatch engine is used to auto generate this patch.
(https://github.com/weiyj/dpatch)

Signed-off-by: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
---
 drivers/tty/serial/of_serial.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/of_serial.c b/drivers/tty/serial/of_serial.c
index 533ccfe..b9fdccb 100644
--- a/drivers/tty/serial/of_serial.c
+++ b/drivers/tty/serial/of_serial.c
@@ -66,10 +66,10 @@ static int __devinit of_platform_serial_setup(struct platform_device *ofdev,
 
 		/* Get clk rate through clk driver if present */
 		info->clk = clk_get(&ofdev->dev, NULL);
-		if (info->clk == NULL) {
+		if (IS_ERR(info->clk)) {
 			dev_warn(&ofdev->dev,
 				"clk or clock-frequency not defined\n");
-			return -ENODEV;
+			return PTR_ERR(info->clk);
 		}
 
 		clk_prepare_enable(info->clk);



^ permalink raw reply related

* Re: [PATCH] serial:ifx6x60:Prevent data transfer when IFX6x60 port is shutdown
From: Alan Cox @ 2012-11-01 11:48 UTC (permalink / raw)
  To: chao bi; +Cc: linux-serial, richardx.r.gorby, chuansheng.liu, jun.d.chen
In-Reply-To: <1351673647.4445.25.camel@bichao>

On Wed, 31 Oct 2012 16:54:07 +0800
chao bi <chao.bi@intel.com> wrote:

> 
> This patch is to implement following 2 places to avoid potential
> error when IFX6x60 port shutdown: 1) Clear Flag
> IFX_SPI_STATE_IO_AVAILABLE to disable data transfer when Modem port
> is shutdown; 2) Clear Flag IFX_SPI_STATE_IO_IN_PROGRESS and
> IFX_SPI_STATE_IO_READY when reopen port. This is because last port
> shutdown may happen when SPI/DMA transfer is in progress, if the last
> data transfer is not completed(for example due to modem reset), the
> Flag IFX_SPI_STATE_IO_IN_PROGRESS will be set forever, so when IFX
> port is activated again, IFX_SPI_STATE_IO_IN_PROGRESS will prevent
> transferring data forever. And if don't clear IFX_SPI_STATE_IO_READY,
> it may cause one more SPI frame transferring in spit there is not
> data need to be transfer.
> 
> cc: liu chuansheng <chuansheng.liu@intel.com>
> cc: Chen Jun <jun.d.chen@intel.com>
> Signed-off-by: channing <chao.bi@intel.com>


Acked-by: Alan Cox <alan@linux.intel.com>

^ permalink raw reply

* Re: [PATCH -next] tty: of_serial: fix return value check in of_platform_serial_setup()
From: Murali Karicheri @ 2012-11-01 14:00 UTC (permalink / raw)
  To: Wei Yongjun
  Cc: alan, gregkh, grant.likely, rob.herring, seth.heasley,
	yongjun_wei, linux-serial, devicetree-discuss
In-Reply-To: <CAPgLHd--dWeXNH_QVU34afv5Q1ju2MZJcwLKwyRr89+RvOTOBw@mail.gmail.com>

On 11/01/2012 01:27 AM, Wei Yongjun wrote:
> From: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
>
> In case of error, the function clk_get() returns ERR_PTR()
> and never returns NULL. The NULL test in the return value
> check should be replaced with IS_ERR().
>
> dpatch engine is used to auto generate this patch.
> (https://github.com/weiyj/dpatch)
>
> Signed-off-by: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
> ---
>   drivers/tty/serial/of_serial.c | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/tty/serial/of_serial.c b/drivers/tty/serial/of_serial.c
> index 533ccfe..b9fdccb 100644
> --- a/drivers/tty/serial/of_serial.c
> +++ b/drivers/tty/serial/of_serial.c
> @@ -66,10 +66,10 @@ static int __devinit of_platform_serial_setup(struct platform_device *ofdev,
>   
>   		/* Get clk rate through clk driver if present */
>   		info->clk = clk_get(&ofdev->dev, NULL);
> -		if (info->clk == NULL) {
> +		if (IS_ERR(info->clk)) {
>   			dev_warn(&ofdev->dev,
>   				"clk or clock-frequency not defined\n");
> -			return -ENODEV;
> +			return PTR_ERR(info->clk);
I think this is wrong. it should return -ENODEV;
Murali
>   		}
>   
>   		clk_prepare_enable(info->clk);
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-serial" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


^ permalink raw reply

* Re: [PATCH -next] tty: of_serial: fix return value check in of_platform_serial_setup()
From: Alan Cox @ 2012-11-01 14:48 UTC (permalink / raw)
  To: Murali Karicheri
  Cc: Wei Yongjun, gregkh, grant.likely, rob.herring, seth.heasley,
	yongjun_wei, linux-serial, devicetree-discuss
In-Reply-To: <50928060.3010806@ti.com>


> >   				"clk or clock-frequency not
> > defined\n");
> > -			return -ENODEV;
> > +			return PTR_ERR(info->clk);
> I think this is wrong. it should return -ENODEV;

Returning the clock error at least ensures the real reason for the fail
is reported. I think its better to use the PTR_ERR but I'd not class
either as wrong.

Alan

^ permalink raw reply

* [PATCH 1/2] moxa: dcd handling of CLOCAL is backwards
From: Alan Cox @ 2012-11-01 16:43 UTC (permalink / raw)
  To: greg, linux-serial

From: Alan Cox <alan@linux.intel.com>

We should do hangup on dcd loss if CLOCAL is false not true.

Signed-off-by: Alan Cox <alan@linux.intel.com>
Resolves-bug: https://bugzilla.kernel.org/show_bug.cgi?id=49911
---

 drivers/tty/moxa.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


diff --git a/drivers/tty/moxa.c b/drivers/tty/moxa.c
index 56e616b..9b57aae 100644
--- a/drivers/tty/moxa.c
+++ b/drivers/tty/moxa.c
@@ -1370,7 +1370,7 @@ static void moxa_new_dcdstate(struct moxa_port *p, u8 dcd)
         	p->DCDState = dcd;
         	spin_unlock_irqrestore(&p->port.lock, flags);
 		tty = tty_port_tty_get(&p->port);
-		if (tty && C_CLOCAL(tty) && !dcd)
+		if (tty && !C_CLOCAL(tty) && !dcd)
 			tty_hangup(tty);
 		tty_kref_put(tty);
 	}


^ permalink raw reply related

* [PATCH 2/2] ipwireless: don't oops if we run out of space
From: Alan Cox @ 2012-11-01 16:45 UTC (permalink / raw)
  To: greg, linux-serial
In-Reply-To: <20121101164336.31727.23069.stgit@bob.linux.org.uk>

From: Alan Cox <alan@linux.intel.com>

Resolves-bug: https://bugzilla.kernel.org/show_bug.cgi?id=49851
Signed-off-by: Alan Cox <alan@linux.intel.com>
---

 drivers/tty/ipwireless/network.c |    5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)


diff --git a/drivers/tty/ipwireless/network.c b/drivers/tty/ipwireless/network.c
index 57102e6..c0dfb64 100644
--- a/drivers/tty/ipwireless/network.c
+++ b/drivers/tty/ipwireless/network.c
@@ -352,6 +352,8 @@ static struct sk_buff *ipw_packet_received_skb(unsigned char *data,
 	}
 
 	skb = dev_alloc_skb(length + 4);
+	if (skb == NULL)
+		return NULL;
 	skb_reserve(skb, 2);
 	memcpy(skb_put(skb, length), data, length);
 
@@ -397,7 +399,8 @@ void ipwireless_network_packet_received(struct ipw_network *network,
 
 				/* Send the data to the ppp_generic module. */
 				skb = ipw_packet_received_skb(data, length);
-				ppp_input(network->ppp_channel, skb);
+				if (skb)
+					ppp_input(network->ppp_channel, skb);
 			} else
 				spin_unlock_irqrestore(&network->lock,
 						flags);


^ permalink raw reply related

* Re: New serial card development
From: Matt Schulte @ 2012-11-01 22:03 UTC (permalink / raw)
  To: Greg KH; +Cc: Theodore Ts'o, Alan Cox, linux-serial
In-Reply-To: <CAJp1Oe7Lk2q6v+5jrj2wD4cW=M9LX6kkia=AQnJKOiUEod5uRQ@mail.gmail.com>

On Wed, Oct 31, 2012 at 4:55 PM, Matt Schulte
<matts@commtech-fastcom.com> wrote:
> On Mon, Oct 29, 2012 at 3:04 PM, Matt Schulte
> <matts@commtech-fastcom.com> wrote:
>> On Tue, Oct 23, 2012 at 1:38 PM, Greg KH <greg@kroah.com> wrote:
>>> On Tue, Oct 23, 2012 at 11:31:41AM -0500, Matt Schulte wrote:
>>>> On Tue, Oct 23, 2012 at 11:27 AM, Matt Schulte
>>>> <matts@commtech-fastcom.com> wrote:
>>>> > On Fri, Oct 19, 2012 at 4:21 PM, Theodore Ts'o <tytso@mit.edu> wrote:
>>>> >> Alan's advice to get your card working as a basic serial card is very
>>>> >> good one.  Get basic functionality working, and then you can add the
>>>> >> support for the extra bits later....
>>>> >
>>>> > I can see the logic in getting it working as a basic serial card
>>>> > first.  I think at minimum I would still need to implement the extra
>>>> > divisor calculations to get accurate bit rates.
>>>> >
>>>> > So when it works as a basic serial card, I assume you would want me to
>>>> > use the default PCI IDs to keep it more generic.  Then would I add my
>>>> > own PCI IDs and refer them back to the generic port?

Hello, the next question that I have is how to handle the interrupts
on this UART.  It has a more complex interrupt scheme than a
traditional 8250 UART.  There is a global ISR register that tells you
which port had the interrupt and then there is another set of
registers that hold the actual interrupts.

In this situation where this UART needs extra code for the handling of
its interrupt, where is the best place to add this special interrupt
handler?  Directly to the 8250.c default handler or somewhere else?

Thank you,
Matt Schulte

^ permalink raw reply

* Re: New serial card development
From: Alan Cox @ 2012-11-01 22:26 UTC (permalink / raw)
  To: Matt Schulte; +Cc: Greg KH, Theodore Ts'o, linux-serial
In-Reply-To: <CAJp1Oe4cnWot=wgou91dea81hHNxo8RWOPwXJj3o+JL4zOwQpg@mail.gmail.com>

> Hello, the next question that I have is how to handle the interrupts
> on this UART.  It has a more complex interrupt scheme than a
> traditional 8250 UART.  There is a global ISR register that tells you
> which port had the interrupt and then there is another set of
> registers that hold the actual interrupts.

port->handle_irq is half of what you need, but you probably need to be
able to hook serial8250_interruot and provide your own alternative. Given
you don't need the irq chain handling either probably the bit that wants
to be hookable is the calls to serial_link_irq_chain and
serial_unlink_irq_chain in the startup/shutdown methods.

In yor case all that and all the irq scanning the chain becomes a simple
read of the port and call of the handlers (serial8250_handlee_irq(port,
iir)



Alan

^ permalink raw reply

* Re: [PATCH 8/8] clocksource: xilinx_ttc: add OF_CLK support
From: Josh Cartwright @ 2012-11-02  2:56 UTC (permalink / raw)
  To: Grant Likely, Rob Herring, Russell King, Mike Turquette,
	John Stultz, Thomas Gleixner, Alan Cox, Greg Kroah-Hartman,
	John Linn, Michal Simek
  Cc: devicetree-discuss, linux-kernel, linux-arm-kernel, linux-serial,
	Michal Simek
In-Reply-To: <0ea1ba71afb3f4551ac27a6dfa41bd97b63cef48.1351721190.git.josh.cartwright@ni.com>

On Wed, Oct 31, 2012 at 01:56:14PM -0600, Josh Cartwright wrote:
> Add support for retrieving TTC configuration from device tree.  This
> includes the ability to pull information about the driving clocks from
> the of_clk bindings.
> 
> Signed-off-by: Josh Cartwright <josh.cartwright@ni.com>
> ---
> diff --git a/drivers/clocksource/xilinx_ttc.c b/drivers/clocksource/xilinx_ttc.c
> index ff38b3e..a4718f7 100644
> --- a/drivers/clocksource/xilinx_ttc.c
> +++ b/drivers/clocksource/xilinx_ttc.c
> @@ -209,7 +153,8 @@ static struct clocksource clocksource_xttcpss = {
>  static int xttcpss_set_next_event(unsigned long cycles,
>  					struct clock_event_device *evt)
>  {
> -	struct xttcpss_timer *timer = &timers[XTTCPSS_CLOCKEVENT];
> +	struct xttcpss_timer_clockevent *xttce = to_xttcpss_timer_clkevent(evt);
> +	struct xttcpss_timer *timer = &xttce->xttc;
>  
>  	xttcpss_set_interval(timer, cycles);
>  	return 0;
> @@ -224,12 +169,14 @@ static int xttcpss_set_next_event(unsigned long cycles,
>  static void xttcpss_set_mode(enum clock_event_mode mode,
>  					struct clock_event_device *evt)
>  {
> -	struct xttcpss_timer *timer = &timers[XTTCPSS_CLOCKEVENT];
> +	struct xttcpss_timer_clockevent *xttce = to_xttcpss_timer_clkevent(evt);
> +	struct xttcpss_timer *timer = &xttce->xttc;
>  	u32 ctrl_reg;
>  
>  	switch (mode) {
>  	case CLOCK_EVT_MODE_PERIODIC:
> -		xttcpss_set_interval(timer, TIMER_RATE / HZ);
> +		xttcpss_set_interval(timer,
> +				     clk_get_rate(xttce->clk) / PRESCALE);

I discovered with further testing that the above calculation is broken;
calculated interval also needs to be divided by HZ.

(I'll post a v2; just wanted to get this out there in the slim chance
anyone's testing this ;)

  Josh

^ permalink raw reply

* [PATCH] serial: ifx6x60: Add module parameters to manage the modem status through sysfs.
From: Jun Chen @ 2012-11-02 16:15 UTC (permalink / raw)
  To: alan; +Cc: linux-serial, russ.gorby, chuansheng.liu, chao.bi, jun.d.chen


The Medfield Platform implements a recovery procedure consisting in an escalation
from simple and light recovery procedures to stronger ones with increased visibility
and impact to end-user.After platform find some problem from Modem,such as no response,
platform will try do modem warm reset.If several tries failed, platform will try to
do modem cold boot procedure.For Modem Cold Boot, AP is responsible to generate
blob (binary object containing PIN code and other necessary information).
Blob is stored in AP volatile memory. AP decides to read PIN code from cache instead of
prompting end-user, and sends it to modem as if end-user had entered it.

This patch add module parameters to manage the modem status through sysfs.
These function are:reset_modem,reset_ongoing,hangup_reasons,

Cc: Bi Chao <chao.bi@intel.com>
Cc: Liu chuansheng <chuansheng.liu@intel.com>
Signed-off-by: Chen Jun <jun.d.chen@intel.com>
---
 drivers/tty/serial/ifx6x60.c |  108 ++++++++++++++++++++++++++++++++++++++++++
 drivers/tty/serial/ifx6x60.h |    7 +++
 2 files changed, 115 insertions(+), 0 deletions(-)

diff --git a/drivers/tty/serial/ifx6x60.c b/drivers/tty/serial/ifx6x60.c
index e56ed33..50de856 100644
--- a/drivers/tty/serial/ifx6x60.c
+++ b/drivers/tty/serial/ifx6x60.c
@@ -72,6 +72,19 @@
 #define IFX_SPI_HEADER_0		(-1)
 #define IFX_SPI_HEADER_F		(-2)
 
+
+/* Delays for powering up/resetting the modem, ms */
+#define PO_INTERLINE_DELAY	1
+#define PO_POST_DELAY		200
+
+#define IFX_COLD_RESET_REQ	1
+
+#define IFX_MDM_PWR_ON	3
+#define IFX_MDM_RST_PMU	4
+
+static unsigned long  reset_ongoing;
+static unsigned long hangup_reasons;
+
 /* forward reference */
 static void ifx_spi_handle_srdy(struct ifx_spi_device *ifx_dev);
 
@@ -81,6 +94,42 @@ static struct tty_driver *tty_drv;
 static struct ifx_spi_device *saved_ifx_dev;
 static struct lock_class_key ifx_spi_key;
 
+
+/**
+ * do_modem_power - activity required to bring up modem
+ *
+ * Toggle gpios required to bring up modem power and start modem.
+ */
+static void do_modem_power(void)
+{
+
+	reset_ongoing = 1;
+
+
+	gpio_set_value(IFX_MDM_PWR_ON, 1);
+	mdelay(PO_INTERLINE_DELAY);
+	gpio_set_value(IFX_MDM_PWR_ON, 0);
+	msleep(PO_POST_DELAY);
+}
+
+/**
+ * do_modem_reset - activity required to reset modem
+ *
+ * Toggle gpios required to reset modem.
+ */
+static void do_modem_reset(void)
+{
+
+	reset_ongoing = 1;
+
+	gpio_set_value(IFX_MDM_RST_PMU, 0);
+	mdelay(PO_INTERLINE_DELAY);
+	gpio_set_value(IFX_MDM_RST_PMU, 1);
+	msleep(PO_POST_DELAY);
+}
+
+
+
 /* GPIO/GPE settings */
 
 /**
@@ -227,6 +276,7 @@ static void ifx_spi_timeout(unsigned long arg)
 	struct ifx_spi_device *ifx_dev = (struct ifx_spi_device *)arg;
 
 	dev_warn(&ifx_dev->spi_dev->dev, "*** SPI Timeout ***");
+	hangup_reasons |= HU_TIMEOUT;
 	ifx_spi_ttyhangup(ifx_dev);
 	mrdy_set_low(ifx_dev);
 	clear_bit(IFX_SPI_STATE_TIMER_PENDING, &ifx_dev->flags);
@@ -877,6 +927,7 @@ static irqreturn_t ifx_spi_reset_interrupt(int irq, void *dev)
 		}
 	} else {
 		/* exited reset */
+		reset_ongoing = 0 ;
 		clear_bit(MR_INPROGRESS, &ifx_dev->mdm_reset_state);
 		if (solreset) {
 			set_bit(MR_COMPLETE, &ifx_dev->mdm_reset_state);
@@ -1403,6 +1454,63 @@ static int __init ifx_spi_init(void)
 module_init(ifx_spi_init);
 module_exit(ifx_spi_exit);
 
+/*
+ * Module parameters to manage the modem status through sysfs
+ */
+
+/**
+ * reset_modem - modem reset command function
+ * @val: a reference to the string where the modem reset query is given
+ * @kp: an unused reference to the kernel parameter
+ */
+
+static int reset_modem(const char *val, const struct kernel_param *kp)
+{
+	unsigned long reset_request;
+
+	if (kstrtoul(val, 10, &reset_request) < 0)
+		return -EINVAL;
+
+	if (!saved_ifx_dev) {
+		dev_dbg(&saved_ifx_dev->spi_dev->dev,
+				"%s is called before probe\n", __func__);
+		return -ENODEV;
+	}
+
+	dev_dbg(&saved_ifx_dev->spi_dev->dev,
+					"%s requested !\n", __func__);
+
+	reset_ongoing = 1;
+
+	if (reset_request == IFX_COLD_RESET_REQ) {
+		/*  Need to add these action for modem cold reset:
+		* - Set the RESET_BB_N to low (better SIM protection)
+		* - Set the EXT1P35VREN field to low  during 20ms (V1P35CNT_W  register)
+		* - set the EXT1P35VREN field to high during 10ms (V1P35CNT_W  register)
+		*/
+	}
+
+	/* Perform a complete modem reset */
+	do_modem_reset();
+	do_modem_power();
+
+	return 0;
+}
+
+static struct kernel_param_ops reset_modem_ops = {
+	.set = reset_modem,
+};
+module_param_cb(reset_modem, &reset_modem_ops, NULL, 0644);
+
+
+module_param(reset_ongoing, ulong, S_IRUGO);
+MODULE_PARM_DESC(reset_ongoing,  "modem reset status module parameter");
+
+module_param(hangup_reasons, ulong, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(hangup_reasons, "modem hangup reasons module parameter ");
+
+
+
 MODULE_AUTHOR("Intel");
 MODULE_DESCRIPTION("IFX6x60 spi driver");
 MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/ifx6x60.h b/drivers/tty/serial/ifx6x60.h
index e8464ba..1ac36c4 100644
--- a/drivers/tty/serial/ifx6x60.h
+++ b/drivers/tty/serial/ifx6x60.h
@@ -66,6 +66,13 @@
 #define IFX_SPI_POWER_DATA_PENDING	1
 #define IFX_SPI_POWER_SRDY		2
 
+
+/* reasons for hanging up */
+#define	HU_TIMEOUT		1	/* spi timer out */
+#define	HU_RESET			2	/* modem initiative reset */
+#define	HU_COREDUMP		4	/* modem crash coredump */
+#define	HU_RORTUR			8	/* spi transfer error:ROR or TUR */
+
 struct ifx_spi_device {
 	/* Our SPI device */
 	struct spi_device *spi_dev;
-- 
1.7.4.1




^ permalink raw reply related

* Re: [PATCH 7/8] serial: xilinx_uartps: get clock rate info from dts
From: Lars-Peter Clausen @ 2012-11-02  9:20 UTC (permalink / raw)
  To: Josh Cartwright
  Cc: Grant Likely, Rob Herring, Russell King, Mike Turquette,
	John Stultz, Thomas Gleixner, Alan Cox, Greg Kroah-Hartman,
	John Linn, Michal Simek, devicetree-discuss, linux-kernel,
	linux-arm-kernel, linux-serial, Michal Simek
In-Reply-To: <23a299ee5e89cfd17bb9affd8fbb9f41b79cfd46.1351721190.git.josh.cartwright@ni.com>

On 10/31/2012 08:28 PM, Josh Cartwright wrote:
> Add support for specifying clock information for the uart clk via the
> device tree.  This eliminates the need to hardcode rates in the device
> tree.
> 
> Signed-off-by: Josh Cartwright <josh.cartwright@ni.com>
> ---
>  arch/arm/boot/dts/zynq-7000.dtsi   |  4 ++--
>  drivers/tty/serial/xilinx_uartps.c | 30 +++++++++++++++++-------------
>  2 files changed, 19 insertions(+), 15 deletions(-)
> 
> diff --git a/arch/arm/boot/dts/zynq-7000.dtsi b/arch/arm/boot/dts/zynq-7000.dtsi
> index bb3085c..5fb763f 100644
> --- a/arch/arm/boot/dts/zynq-7000.dtsi
> +++ b/arch/arm/boot/dts/zynq-7000.dtsi
> @@ -44,14 +44,14 @@
>  			compatible = "xlnx,xuartps";
>  			reg = <0xE0000000 0x1000>;
>  			interrupts = <0 27 4>;
> -			clock = <50000000>;
> +			clocks = <&uart_clk 0>;
>  		};
>  
>  		uart1: uart@e0001000 {
>  			compatible = "xlnx,xuartps";
>  			reg = <0xE0001000 0x1000>;
>  			interrupts = <0 50 4>;
> -			clock = <50000000>;
> +			clocks = <&uart_clk 0>;

Shouldn't this be <&uart_clk 1>?

>  		};
>  
>  		slcr: slcr@f8000000 {

^ permalink raw reply

* Re: [PATCH 5/8] ARM: zynq: add COMMON_CLK support
From: Lars-Peter Clausen @ 2012-11-02  9:33 UTC (permalink / raw)
  To: Josh Cartwright
  Cc: Grant Likely, Rob Herring, Russell King, Mike Turquette,
	John Stultz, Thomas Gleixner, Alan Cox, Greg Kroah-Hartman,
	John Linn, Michal Simek, devicetree-discuss, linux-kernel,
	linux-arm-kernel, linux-serial, Michal Simek
In-Reply-To: <94af5ee92c2d68f245eb902de74909aadf159be1.1351721190.git.josh.cartwright@ni.com>

On 10/31/2012 07:58 PM, Josh Cartwright wrote:
> [...]
> +#define PERIPH_CLK_CTRL_SRC(x)	(periph_clk_parent_map[((x)&3)>>4])
> +#define PERIPH_CLK_CTRL_DIV(x)	(((x)&0x3F00)>>8)

A few more spaces wouldn't hurt ;)

> [...]
> +static void __init zynq_periph_clk_setup(struct device_node *np)
> +{
> +	struct zynq_periph_clk *periph;
> +	const char *parent_names[3];
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	int err;
> +	u32 reg;
> +	int i;
> +
> +	err = of_property_read_u32(np, "reg", &reg);
> +	WARN_ON(err);

Shouldn't the function abort if a error happens somewhere? Continuing here
will lead to undefined behavior. Same is probably true for the other WARN_ONs.

> +
> +	periph = kzalloc(sizeof(*periph), GFP_KERNEL);
> +	WARN_ON(!periph);
> +
> +	periph->clk_ctrl = slcr_base + reg;
> +	spin_lock_init(&periph->clkact_lock);
> +
> +	init.name = np->name;
> +	init.ops = &zynq_periph_clk_ops;
> +	for (i = 0; i < ARRAY_SIZE(parent_names); i++)
> +		parent_names[i] = of_clk_get_parent_name(np, i);
> +	init.parent_names = parent_names;
> +	init.num_parents = ARRAY_SIZE(parent_names);
> +
> +	periph->hw.init = &init;
> +
> +	clk = clk_register(NULL, &periph->hw);
> +	WARN_ON(IS_ERR(clk));
> +
> +	err = of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +	WARN_ON(err);
> +
> +	for (i = 0; i < 2; i++) {

Not all of the peripheral clock generators have two output clocks. I think
it makes sense to use the number entries in clock-output-names here.

> +		const char *name;
> +
> +		err = of_property_read_string_index(np, "clock-output-names", i,
> +						    &name);
> +		WARN_ON(err);
> +
> +		periph->gates[i] = clk_register_gate(NULL, name, np->name, 0,
> +						     periph->clk_ctrl, i, 0,
> +						     &periph->clkact_lock);
> +		WARN_ON(IS_ERR(periph->gates[i]));
> +	}
> +
> +	periph->onecell_data.clks = periph->gates;
> +	periph->onecell_data.clk_num = i;
> +
> +	err = of_clk_add_provider(np, of_clk_src_onecell_get,
> +				  &periph->onecell_data);
> +	WARN_ON(err);
> +}
> [...]


^ permalink raw reply

* [PATCH 2/3] serial: i.MX: Make console support non optional
From: Sascha Hauer @ 2012-11-02  9:48 UTC (permalink / raw)
  To: linux-serial
  Cc: Alan Cox, Greg Kroah-Hartman, Grant Likely, linux-kernel,
	devicetree-discuss, linux-arm-kernel, kernel, Sascha Hauer
In-Reply-To: <1351849734-9836-1-git-send-email-s.hauer@pengutronix.de>

Traditionally console support is optional for serial drivers. This
makes it non optional for the i.MX driver since it's not worth
asking questions for a feature virtually every user of this driver
wants to have.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/tty/serial/Kconfig |   16 +---------------
 drivers/tty/serial/imx.c   |    8 +-------
 2 files changed, 2 insertions(+), 22 deletions(-)

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 4720b4b..920dd0d 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -515,26 +515,12 @@ config SERIAL_IMX
 	bool "IMX serial port support"
 	depends on ARCH_MXC
 	select SERIAL_CORE
+	select SERIAL_CORE_CONSOLE
 	select RATIONAL
 	help
 	  If you have a machine based on a Motorola IMX CPU you
 	  can enable its onboard serial port by enabling this option.
 
-config SERIAL_IMX_CONSOLE
-	bool "Console on IMX serial port"
-	depends on SERIAL_IMX
-	select SERIAL_CORE_CONSOLE
-	help
-	  If you have enabled the serial port on the Motorola IMX
-	  CPU you can make it the console by answering Y to this option.
-
-	  Even if you say Y here, the currently visible virtual console
-	  (/dev/tty0) will still be used as the system console by default, but
-	  you can alter that using a kernel command line option such as
-	  "console=ttySA0". (Try "man bootparam" or see the documentation of
-	  your boot loader (lilo or loadlin) about how to pass options to the
-	  kernel at boot time.)
-
 config SERIAL_UARTLITE
 	tristate "Xilinx uartlite serial port support"
 	depends on PPC32 || MICROBLAZE || MFD_TIMBERDALE
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index e309e8b..3a9337e 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -1192,7 +1192,6 @@ static struct uart_ops imx_pops = {
 
 static struct imx_port *imx_ports[UART_NR];
 
-#ifdef CONFIG_SERIAL_IMX_CONSOLE
 static void imx_console_putchar(struct uart_port *port, int ch)
 {
 	struct imx_port *sport = (struct imx_port *)port;
@@ -1348,11 +1347,6 @@ static struct console imx_console = {
 	.data		= &imx_reg,
 };
 
-#define IMX_CONSOLE	&imx_console
-#else
-#define IMX_CONSOLE	NULL
-#endif
-
 static struct uart_driver imx_reg = {
 	.owner          = THIS_MODULE,
 	.driver_name    = DRIVER_NAME,
@@ -1360,7 +1354,7 @@ static struct uart_driver imx_reg = {
 	.major          = SERIAL_IMX_MAJOR,
 	.minor          = MINOR_START,
 	.nr             = ARRAY_SIZE(imx_ports),
-	.cons           = IMX_CONSOLE,
+	.cons           = &imx_console,
 };
 
 static int serial_imx_suspend(struct platform_device *dev, pm_message_t state)
-- 
1.7.10.4

^ permalink raw reply related

* [PATCH 3/3] serial: i.MX: evaluate linux,stdout-path property
From: Sascha Hauer @ 2012-11-02  9:48 UTC (permalink / raw)
  To: linux-serial
  Cc: Alan Cox, Greg Kroah-Hartman, Grant Likely, linux-kernel,
	devicetree-discuss, linux-arm-kernel, kernel, Sascha Hauer
In-Reply-To: <1351849734-9836-1-git-send-email-s.hauer@pengutronix.de>

devicetrees may have the linux,stdout-path property to specify the
console. This patch adds support to the i.MX serial driver for this.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/tty/serial/imx.c |    4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 3a9337e..41be237 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -47,6 +47,7 @@
 #include <linux/slab.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/of_stdout.h>
 #include <linux/pinctrl/consumer.h>
 
 #include <asm/io.h>
@@ -1421,6 +1422,9 @@ static int serial_imx_probe_dt(struct imx_port *sport,
 
 	sport->devdata = of_id->data;
 
+	if (of_device_is_stdout_path(np))
+		add_preferred_console(imx_reg.cons->name, sport->port.line, 0);
+
 	return 0;
 }
 #else
-- 
1.7.10.4

^ permalink raw reply related

* [PATCH v3] linux,stdout-path helper
From: Sascha Hauer @ 2012-11-02  9:48 UTC (permalink / raw)
  To: linux-serial
  Cc: Alan Cox, Greg Kroah-Hartman, Grant Likely, linux-kernel,
	devicetree-discuss, linux-arm-kernel, kernel

The following adds a helper for matching the linux,stdout-path property
in the chosen node and makes use of it in the i.MX serial driver.

changes since v2:

- move helper to OF core and make it independent of serial devices

changes since v1:

- move it out of the i.MX serial driver and make it generic for serial
  devices.

----------------------------------------------------------------
Sascha Hauer (3):
      OF: Add helper for matching against linux,stdout-path
      serial: i.MX: Make console support non optional
      serial: i.MX: evaluate linux,stdout-path property

 drivers/of/Kconfig         |    3 +++
 drivers/of/Makefile        |    1 +
 drivers/of/of_stdout.c     |   27 +++++++++++++++++++++++++++
 drivers/tty/serial/Kconfig |   16 +---------------
 drivers/tty/serial/imx.c   |   12 +++++-------
 include/linux/of_stdout.h  |   24 ++++++++++++++++++++++++
 6 files changed, 61 insertions(+), 22 deletions(-)
 create mode 100644 drivers/of/of_stdout.c
 create mode 100644 include/linux/of_stdout.h

^ permalink raw reply

* [PATCH 1/3] OF: Add helper for matching against linux,stdout-path
From: Sascha Hauer @ 2012-11-02  9:48 UTC (permalink / raw)
  To: linux-serial
  Cc: Alan Cox, Greg Kroah-Hartman, Grant Likely, linux-kernel,
	devicetree-discuss, linux-arm-kernel, kernel, Sascha Hauer
In-Reply-To: <1351849734-9836-1-git-send-email-s.hauer@pengutronix.de>

devicetrees may have a linux,stdout-path property in the chosen
node describing the console device. This adds a helper function
to match a device against this property so a driver can call
add_preferred_console for a matching device.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/of/Kconfig        |    3 +++
 drivers/of/Makefile       |    1 +
 drivers/of/of_stdout.c    |   27 +++++++++++++++++++++++++++
 include/linux/of_stdout.h |   24 ++++++++++++++++++++++++
 4 files changed, 55 insertions(+)
 create mode 100644 drivers/of/of_stdout.c
 create mode 100644 include/linux/of_stdout.h

diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index dfba3e6..8574ebb 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -67,6 +67,9 @@ config OF_MDIO
 	help
 	  OpenFirmware MDIO bus (Ethernet PHY) accessors
 
+config OF_STDOUT
+	def_bool y
+
 config OF_PCI
 	def_tristate PCI
 	depends on PCI
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index e027f44..c9f3f2f 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_OF_I2C)	+= of_i2c.o
 obj-$(CONFIG_OF_NET)	+= of_net.o
 obj-$(CONFIG_OF_SELFTEST) += selftest.o
 obj-$(CONFIG_OF_MDIO)	+= of_mdio.o
+obj-$(CONFIG_OF_STDOUT) += of_stdout.o
 obj-$(CONFIG_OF_PCI)	+= of_pci.o
 obj-$(CONFIG_OF_PCI_IRQ)  += of_pci_irq.o
 obj-$(CONFIG_OF_MTD)	+= of_mtd.o
diff --git a/drivers/of/of_stdout.c b/drivers/of/of_stdout.c
new file mode 100644
index 0000000..c9e370e
--- /dev/null
+++ b/drivers/of/of_stdout.c
@@ -0,0 +1,27 @@
+/*
+ * OF helper for linux,stdoutpath property.
+ *
+ * This file is released under the GPLv2
+ */
+#include <linux/of_stdout.h>
+
+/**
+ * of_device_is_stdout_path - check if a device node matches the
+ *                            linux,stdout-path property
+ *
+ * Check if this device node matches the linux,stdout-path property
+ * in the chosen node. return true if yes, false otherwise.
+ */
+int of_device_is_stdout_path(struct device_node *dn)
+{
+	const char *name;
+
+	name = of_get_property(of_chosen, "linux,stdout-path", NULL);
+	if (name == NULL)
+		return 0;
+
+	if (dn == of_find_node_by_path(name))
+		return 1;
+
+	return 0;
+}
diff --git a/include/linux/of_stdout.h b/include/linux/of_stdout.h
new file mode 100644
index 0000000..0d80674
--- /dev/null
+++ b/include/linux/of_stdout.h
@@ -0,0 +1,24 @@
+/*
+ * OF helper for linux,stdoutpath property.
+ *
+ * This file is released under the GPLv2
+ */
+
+#ifndef __LINUX_OF_STDOUT_H
+#define __LINUX_OF_STDOUT_H
+
+#ifdef CONFIG_OF_STDOUT
+
+#include <linux/of.h>
+int of_device_is_stdout_path(struct device_node *dn);
+
+#else
+
+static inline int of_device_is_stdout_path(struct device_node *dn)
+{
+	return 0;
+}
+
+#endif
+
+#endif /* __LINUX_OF_STDOUT_H */
-- 
1.7.10.4


^ permalink raw reply related

* Re: [PATCH] serial: ifx6x60: Add module parameters to manage the modem status through sysfs.
From: Alan Cox @ 2012-11-02 11:29 UTC (permalink / raw)
  To: Jun Chen; +Cc: alan, linux-serial, russ.gorby, chuansheng.liu, chao.bi
In-Reply-To: <1351872943.4332.37.camel@chenjun-workstation>

> +	if (reset_request == IFX_COLD_RESET_REQ) {
> +		/*  Need to add these action for modem cold reset:
> +		* - Set the RESET_BB_N to low (better SIM protection)
> +		* - Set the EXT1P35VREN field to low  during 20ms (V1P35CNT_W  register)
> +		* - set the EXT1P35VREN field to high during 10ms (V1P35CNT_W  register)
> +		*/

You seem to have submitted an unfinished version ?

Also can you explain the locking on your new reset_ongoing variable and
hangup_reasons variable ? What stops the sysfs code being run in parallel
with the other bits or even twice at once.

Looking at it I get the impression that

a) the variable should be in the device structure not a static global
b) it is write only which seems pointless
c) you probably want a mutex around the actual reset processes so that
reset is single threaded in each case.
d) that mutex probably belongs in the device structure
 

The hangup reasons in this patch also appears to be write only ?

Alan

^ permalink raw reply

* [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
From: Peter Hurley @ 2012-11-02 12:16 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Stefan Richter, Alan Cox
  Cc: linux-kernel, devel, linux1394-devel, linux-serial, Peter Hurley
In-Reply-To: <cover.1351817601.git.peter@hurleysoftware.com>

This patch provides the kernel driver for high-speed TTY
communication over the IEEE 1394 bus.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
v2
 - Removes dependencies on tty_buffer internals
 - Handles ldisc data loss on hangup with timer delay
 - Buffers rx if flip buffers are full
 - Fixes flaky auto-connect
 - Moves fifo allocation to .activate - this saves memory on
   unused ports
 - Fixes 2 lockdep warnings on allocations w/ spinlocks held
 - Fixes all checkpatch warnings
 - Fixes potential line status write order inversion
 - Fixes completely bogus attempt at running delayed work
   (by using new mod_delayed_work())
 - Fixes potential deadlock when cancelling peer work
 - Respects CLOCAL for carrier state
 - Properly resets port state on shutdown
---
 drivers/staging/Kconfig             |    2 +
 drivers/staging/Makefile            |    1 +
 drivers/staging/fwserial/Kconfig    |    9 +
 drivers/staging/fwserial/Makefile   |    2 +
 drivers/staging/fwserial/TODO       |   37 +
 drivers/staging/fwserial/dma_fifo.c |  310 ++++
 drivers/staging/fwserial/dma_fifo.h |  130 ++
 drivers/staging/fwserial/fwserial.c | 2946 +++++++++++++++++++++++++++++++++++
 drivers/staging/fwserial/fwserial.h |  387 +++++
 9 files changed, 3824 insertions(+)
 create mode 100644 drivers/staging/fwserial/Kconfig
 create mode 100644 drivers/staging/fwserial/Makefile
 create mode 100644 drivers/staging/fwserial/TODO
 create mode 100644 drivers/staging/fwserial/dma_fifo.c
 create mode 100644 drivers/staging/fwserial/dma_fifo.h
 create mode 100644 drivers/staging/fwserial/fwserial.c
 create mode 100644 drivers/staging/fwserial/fwserial.h

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index d805eef..39696d6 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -144,4 +144,6 @@ source "drivers/staging/imx-drm/Kconfig"
 
 source "drivers/staging/dgrp/Kconfig"
 
+source "drivers/staging/fwserial/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 76e2ebd..5565d5f 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -64,3 +64,4 @@ obj-$(CONFIG_NET_VENDOR_SILICOM)	+= silicom/
 obj-$(CONFIG_CED1401)		+= ced1401/
 obj-$(CONFIG_DRM_IMX)		+= imx-drm/
 obj-$(CONFIG_DGRP)		+= dgrp/
+obj-$(CONFIG_FIREWIRE_SERIAL)	+= fwserial/
diff --git a/drivers/staging/fwserial/Kconfig b/drivers/staging/fwserial/Kconfig
new file mode 100644
index 0000000..580406c
--- /dev/null
+++ b/drivers/staging/fwserial/Kconfig
@@ -0,0 +1,9 @@
+config FIREWIRE_SERIAL
+       tristate "TTY over Firewire"
+       depends on FIREWIRE
+       help
+          This enables TTY over IEEE 1394, providing high-speed serial
+	  connectivity to cabled peers.
+
+	  To compile this driver as a module, say M here:  the module will
+	  be called firewire-serial.
diff --git a/drivers/staging/fwserial/Makefile b/drivers/staging/fwserial/Makefile
new file mode 100644
index 0000000..2170869
--- /dev/null
+++ b/drivers/staging/fwserial/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_FIREWIRE_SERIAL) += firewire-serial.o
+firewire-serial-objs := fwserial.o dma_fifo.o
diff --git a/drivers/staging/fwserial/TODO b/drivers/staging/fwserial/TODO
new file mode 100644
index 0000000..7269005
--- /dev/null
+++ b/drivers/staging/fwserial/TODO
@@ -0,0 +1,37 @@
+TODOs
+-----
+1. Implement retries for RCODE_BUSY, RCODE_NO_ACK and RCODE_SEND_ERROR
+   - I/O is handled asynchronously which presents some issues when error
+     conditions occur.
+2. Implement _robust_ console on top of this. The existing prototype console
+   driver is not ready for the big leagues yet.
+3. Expose means of controlling attach/detach of peers via sysfs. Include
+   GUID-to-port matching/whitelist/blacklist.
+
+-- Issues with firewire stack --
+1. This driver uses the same unregistered vendor id that the firewire core does
+     (0xd00d1e). Perhaps this could be exposed as a define in
+     firewire-constants.h?
+2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci
+   - otherwise how will this driver know the max size of address window to
+     open for one packet write?
+3. Maybe device_max_receive() and link_speed_to_max_payload() should be
+     taken up by the firewire core?
+4. To avoid dropping rx data while still limiting the maximum buffering,
+     the size of the AR context must be known. How to expose this to drivers?
+5. Explore if bigger AR context will reduce RCODE_BUSY responses
+   (or auto-grow to certain max size -- but this would require major surgery
+    as the current AR is contiguously mapped)
+
+-- Issues with TTY core --
+  1. Hack for alternate device name scheme
+     - because udev no longer allows device renaming, devices should have
+       their proper names on creation. This is an issue for creating the
+       fwloop<n> device with the fwtty<n> devices because although duplicating
+       roughly the same operations as tty_port_register_device() isn't difficult,
+       access to the tty_class & tty_fops is restricted in scope.
+
+       This is currently being worked around in create_loop_device() by
+       extracting the tty_class ptr and tty_fops ptr from the previously created
+       tty devices. Perhaps an add'l api can be added -- eg.,
+       tty_{port_}register_named_device().
diff --git a/drivers/staging/fwserial/dma_fifo.c b/drivers/staging/fwserial/dma_fifo.c
new file mode 100644
index 0000000..72aa053
--- /dev/null
+++ b/drivers/staging/fwserial/dma_fifo.c
@@ -0,0 +1,310 @@
+/*
+ * DMA-able FIFO implementation
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/bug.h>
+
+#include "dma_fifo.h"
+
+#ifdef DEBUG_TRACING
+#define df_trace(s, args...) pr_debug(s, ##args)
+#else
+#define df_trace(s, args...)
+#endif
+
+#define FAIL(fifo, condition, format...) ({				\
+	fifo->corrupt = !!(condition);					\
+	if (unlikely(fifo->corrupt)) {					\
+		__WARN_printf(format);					\
+	}								\
+	unlikely(fifo->corrupt);					\
+})
+
+/*
+ * private helper fn to determine if check is in open interval (lo,hi)
+ */
+static bool addr_check(unsigned check, unsigned lo, unsigned hi)
+{
+	return check - (lo + 1) < (hi - 1) - lo;
+}
+
+/**
+ * dma_fifo_init: initialize the fifo to a valid but inoperative state
+ * @fifo: address of in-place "struct dma_fifo" object
+ */
+void dma_fifo_init(struct dma_fifo *fifo)
+{
+	memset(fifo, 0, sizeof(*fifo));
+	INIT_LIST_HEAD(&fifo->pending);
+}
+
+/**
+ * dma_fifo_alloc - initialize and allocate dma_fifo
+ * @fifo: address of in-place "struct dma_fifo" object
+ * @size: 'apparent' size, in bytes, of fifo
+ * @align: dma alignment to maintain (should be at least cpu cache alignment),
+ *         must be power of 2
+ * @tx_limit: maximum # of bytes transmissable per dma (rounded down to
+ *            multiple of alignment, but at least align size)
+ * @open_limit: maximum # of outstanding dma transactions allowed
+ * @gfp_mask: get_free_pages mask, passed to kmalloc()
+ *
+ * The 'apparent' size will be rounded up to next greater aligned size.
+ * Returns 0 if no error, otherwise an error code
+ */
+int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
+		   int tx_limit, int open_limit, gfp_t gfp_mask)
+{
+	int capacity;
+
+	if (!is_power_of_2(align) || size < 0)
+		return -EINVAL;
+
+	size = round_up(size, align);
+	capacity = size + align * open_limit + align * DMA_FIFO_GUARD;
+	fifo->data = kmalloc(capacity, gfp_mask);
+	if (!fifo->data)
+		return -ENOMEM;
+
+	fifo->in = 0;
+	fifo->out = 0;
+	fifo->done = 0;
+	fifo->size = size;
+	fifo->avail = size;
+	fifo->align = align;
+	fifo->tx_limit = max_t(int, round_down(tx_limit, align), align);
+	fifo->open = 0;
+	fifo->open_limit = open_limit;
+	fifo->guard = size + align * open_limit;
+	fifo->capacity = capacity;
+	fifo->corrupt = 0;
+
+	return 0;
+}
+
+/**
+ * dma_fifo_free - frees the fifo
+ * @fifo: address of in-place "struct dma_fifo" to free
+ *
+ * Also reinits the fifo to a valid but inoperative state. This
+ * allows the fifo to be reused with a different target requiring
+ * different fifo parameters.
+ */
+void dma_fifo_free(struct dma_fifo *fifo)
+{
+	struct dma_pending *pending, *next;
+
+	if (fifo->data == NULL)
+		return;
+
+	list_for_each_entry_safe(pending, next, &fifo->pending, link)
+		list_del_init(&pending->link);
+	kfree(fifo->data);
+	fifo->data = NULL;
+}
+
+/**
+ * dma_fifo_reset - dumps the fifo contents and reinits for reuse
+ * @fifo: address of in-place "struct dma_fifo" to reset
+ */
+void dma_fifo_reset(struct dma_fifo *fifo)
+{
+	struct dma_pending *pending, *next;
+
+	if (fifo->data == NULL)
+		return;
+
+	list_for_each_entry_safe(pending, next, &fifo->pending, link)
+		list_del_init(&pending->link);
+	fifo->in = 0;
+	fifo->out = 0;
+	fifo->done = 0;
+	fifo->avail = fifo->size;
+	fifo->open = 0;
+	fifo->corrupt = 0;
+}
+
+/**
+ * dma_fifo_in - copies data into the fifo
+ * @fifo: address of in-place "struct dma_fifo" to write to
+ * @src: buffer to copy from
+ * @n: # of bytes to copy
+ *
+ * Returns the # of bytes actually copied, which can be less than requested if
+ * the fifo becomes full. If < 0, return is error code.
+ */
+int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n)
+{
+	int ofs, l;
+
+	if (fifo->data == NULL)
+		return -ENOENT;
+	if (fifo->corrupt)
+		return -ENXIO;
+
+	if (n > fifo->avail)
+		n = fifo->avail;
+	if (n <= 0)
+		return 0;
+
+	ofs = fifo->in % fifo->capacity;
+	l = min(n, fifo->capacity - ofs);
+	memcpy(fifo->data + ofs, src, l);
+	memcpy(fifo->data, src + l, n - l);
+
+	if (FAIL(fifo, addr_check(fifo->done, fifo->in, fifo->in + n) ||
+			fifo->avail < n,
+			"fifo corrupt: in:%u out:%u done:%u n:%d avail:%d",
+			fifo->in, fifo->out, fifo->done, n, fifo->avail))
+		return -ENXIO;
+
+	fifo->in += n;
+	fifo->avail -= n;
+
+	df_trace("in:%u out:%u done:%u n:%d avail:%d", fifo->in, fifo->out,
+		 fifo->done, n, fifo->avail);
+
+	return n;
+}
+
+/**
+ * dma_fifo_out_pend - gets address/len of next avail read and marks as pended
+ * @fifo: address of in-place "struct dma_fifo" to read from
+ * @pended: address of structure to fill with read address/len
+ *          The data/len fields will be NULL/0 if no dma is pended.
+ *
+ * Returns the # of used bytes remaining in fifo (ie, if > 0, more data
+ * remains in the fifo that was not pended). If < 0, return is error code.
+ */
+int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended)
+{
+	unsigned len, n, ofs, l, limit;
+
+	if (fifo->data == NULL)
+		return -ENOENT;
+	if (fifo->corrupt)
+		return -ENXIO;
+
+	pended->len = 0;
+	pended->data = NULL;
+	pended->out = fifo->out;
+
+	len = fifo->in - fifo->out;
+	if (!len)
+		return -ENODATA;
+	if (fifo->open == fifo->open_limit)
+		return -EAGAIN;
+
+	n = len;
+	ofs = fifo->out % fifo->capacity;
+	l = fifo->capacity - ofs;
+	limit = min_t(unsigned, l, fifo->tx_limit);
+	if (n > limit) {
+		n = limit;
+		fifo->out += limit;
+	} else if (ofs + n > fifo->guard) {
+		fifo->out += l;
+		fifo->in = fifo->out;
+	} else {
+		fifo->out += round_up(n, fifo->align);
+		fifo->in = fifo->out;
+	}
+
+	df_trace("in: %u out: %u done: %u n: %d len: %u avail: %d", fifo->in,
+		 fifo->out, fifo->done, n, len, fifo->avail);
+
+	pended->len = n;
+	pended->data = fifo->data + ofs;
+	pended->next = fifo->out;
+	list_add_tail(&pended->link, &fifo->pending);
+	++fifo->open;
+
+	if (FAIL(fifo, fifo->open > fifo->open_limit,
+			"past open limit:%d (limit:%d)",
+			fifo->open, fifo->open_limit))
+		return -ENXIO;
+	if (FAIL(fifo, fifo->out & (fifo->align - 1),
+			"fifo out unaligned:%u (align:%u)",
+			fifo->out, fifo->align))
+		return -ENXIO;
+
+	return len - n;
+}
+
+/**
+ * dma_fifo_out_complete - marks pended dma as completed
+ * @fifo: address of in-place "struct dma_fifo" which was read from
+ * @complete: address of structure for previously pended dma to mark completed
+ */
+int dma_fifo_out_complete(struct dma_fifo *fifo, struct dma_pending *complete)
+{
+	struct dma_pending *pending, *next, *tmp;
+
+	if (fifo->data == NULL)
+		return -ENOENT;
+	if (fifo->corrupt)
+		return -ENXIO;
+	if (list_empty(&fifo->pending) && fifo->open == 0)
+		return -EINVAL;
+
+	if (FAIL(fifo, list_empty(&fifo->pending) != (fifo->open == 0),
+			"pending list disagrees with open count:%d",
+			fifo->open))
+		return -ENXIO;
+
+	tmp = complete->data;
+	*tmp = *complete;
+	list_replace(&complete->link, &tmp->link);
+	dp_mark_completed(tmp);
+
+	/* Only update the fifo in the original pended order */
+	list_for_each_entry_safe(pending, next, &fifo->pending, link) {
+		if (!dp_is_completed(pending)) {
+			df_trace("still pending: saved out: %u len: %d",
+				 pending->out, pending->len);
+			break;
+		}
+
+		if (FAIL(fifo, pending->out != fifo->done ||
+				addr_check(fifo->in, fifo->done, pending->next),
+				"in:%u out:%u done:%u saved:%u next:%u",
+				fifo->in, fifo->out, fifo->done, pending->out,
+				pending->next))
+			return -ENXIO;
+
+		list_del_init(&pending->link);
+		fifo->done = pending->next;
+		fifo->avail += pending->len;
+		--fifo->open;
+
+		df_trace("in: %u out: %u done: %u len: %u avail: %d", fifo->in,
+			 fifo->out, fifo->done, pending->len, fifo->avail);
+	}
+
+	if (FAIL(fifo, fifo->open < 0, "open dma:%d < 0", fifo->open))
+		return -ENXIO;
+	if (FAIL(fifo, fifo->avail > fifo->size, "fifo avail:%d > size:%d",
+			fifo->avail, fifo->size))
+		return -ENXIO;
+
+	return 0;
+}
diff --git a/drivers/staging/fwserial/dma_fifo.h b/drivers/staging/fwserial/dma_fifo.h
new file mode 100644
index 0000000..a113fe1
--- /dev/null
+++ b/drivers/staging/fwserial/dma_fifo.h
@@ -0,0 +1,130 @@
+/*
+ * DMA-able FIFO interface
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _DMA_FIFO_H_
+#define _DMA_FIFO_H_
+
+/**
+ * The design basis for the DMA FIFO is to provide an output side that
+ * complies with the streaming DMA API design that can be DMA'd from directly
+ * (without additional copying), coupled with an input side that maintains a
+ * logically consistent 'apparent' size (ie, bytes in + bytes avail is static
+ * for the lifetime of the FIFO).
+ *
+ * DMA output transactions originate on a cache line boundary and can be
+ * variably-sized. DMA output transactions can be retired out-of-order but
+ * the FIFO will only advance the output in the original input sequence.
+ * This means the FIFO will eventually stall if a transaction is never retired.
+ *
+ * Chunking the output side into cache line multiples means that some FIFO
+ * memory is unused. For example, if all the avail input has been pended out,
+ * then the in and out markers are re-aligned to the next cache line.
+ * The maximum possible waste is
+ *     (cache line alignment - 1) * (max outstanding dma transactions)
+ * This potential waste requires additional hidden capacity within the FIFO
+ * to be able to accept input while the 'apparent' size has not been reached.
+ *
+ * Additional cache lines (ie, guard area) are used to minimize DMA
+ * fragmentation when wrapping at the end of the FIFO. Input is allowed into the
+ * guard area, but the in and out FIFO markers are wrapped when DMA is pended.
+ */
+
+#define DMA_FIFO_GUARD 3   /* # of cache lines to reserve for the guard area */
+
+struct dma_fifo {
+	unsigned	 in;
+	unsigned	 out;		/* updated when dma is pended         */
+	unsigned	 done;		/* updated upon dma completion        */
+	struct {
+		unsigned corrupt:1;
+	};
+	int		 size;		/* 'apparent' size of fifo	      */
+	int		 guard;		/* ofs of guard area		      */
+	int		 capacity;	/* size + reserved                    */
+	int		 avail;		/* # of unused bytes in fifo          */
+	unsigned	 align;		/* must be power of 2                 */
+	int		 tx_limit;	/* max # of bytes per dma transaction */
+	int		 open_limit;	/* max # of outstanding allowed       */
+	int		 open;		/* # of outstanding dma transactions  */
+	struct list_head pending;	/* fifo markers for outstanding dma   */
+	void		 *data;
+};
+
+struct dma_pending {
+	struct list_head link;
+	void		 *data;
+	unsigned	 len;
+	unsigned         next;
+	unsigned         out;
+};
+
+static inline void dp_mark_completed(struct dma_pending *dp)
+{
+	dp->data += 1;
+}
+
+static inline bool dp_is_completed(struct dma_pending *dp)
+{
+	return (unsigned long)dp->data & 1UL;
+}
+
+extern void dma_fifo_init(struct dma_fifo *fifo);
+extern int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
+			  int tx_limit, int open_limit, gfp_t gfp_mask);
+extern void dma_fifo_free(struct dma_fifo *fifo);
+extern void dma_fifo_reset(struct dma_fifo *fifo);
+extern int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n);
+extern int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended);
+extern int dma_fifo_out_complete(struct dma_fifo *fifo,
+				 struct dma_pending *complete);
+
+/* returns the # of used bytes in the fifo */
+static inline int dma_fifo_level(struct dma_fifo *fifo)
+{
+	return fifo->size - fifo->avail;
+}
+
+/* returns the # of bytes ready for output in the fifo */
+static inline int dma_fifo_out_level(struct dma_fifo *fifo)
+{
+	return fifo->in - fifo->out;
+}
+
+/* returns the # of unused bytes in the fifo */
+static inline int dma_fifo_avail(struct dma_fifo *fifo)
+{
+	return fifo->avail;
+}
+
+/* returns true if fifo has max # of outstanding dmas */
+static inline bool dma_fifo_busy(struct dma_fifo *fifo)
+{
+	return fifo->open == fifo->open_limit;
+}
+
+/* changes the max size of dma returned from dma_fifo_out_pend() */
+static inline int dma_fifo_change_tx_limit(struct dma_fifo *fifo, int tx_limit)
+{
+	tx_limit = round_down(tx_limit, fifo->align);
+	fifo->tx_limit = max_t(int, tx_limit, fifo->align);
+	return 0;
+}
+
+#endif /* _DMA_FIFO_H_ */
diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c
new file mode 100644
index 0000000..5d4d64a
--- /dev/null
+++ b/drivers/staging/fwserial/fwserial.c
@@ -0,0 +1,2946 @@
+/*
+ * FireWire Serial driver
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/rculist.h>
+#include <linux/workqueue.h>
+#include <linux/ratelimit.h>
+#include <linux/bug.h>
+#include <linux/uaccess.h>
+
+#include "fwserial.h"
+
+#define be32_to_u64(hi, lo)  ((u64)be32_to_cpu(hi) << 32 | be32_to_cpu(lo))
+
+#define LINUX_VENDOR_ID   0xd00d1eU  /* same id used in card root directory   */
+#define FWSERIAL_VERSION  0x00e81cU  /* must be unique within LINUX_VENDOR_ID */
+
+/* configurable options */
+static int num_ttys = 4;	    /* # of std ttys to create per fw_card    */
+				    /* - doubles as loopback port index       */
+static bool auto_connect = true;    /* try to VIRT_CABLE to every peer        */
+static bool create_loop_dev = true; /* create a loopback device for each card */
+bool limit_bw;			    /* limit async bandwidth to 20% of max    */
+
+module_param_named(ttys, num_ttys, int, S_IRUGO | S_IWUSR);
+module_param_named(auto, auto_connect, bool, S_IRUGO | S_IWUSR);
+module_param_named(loop, create_loop_dev, bool, S_IRUGO | S_IWUSR);
+module_param(limit_bw, bool, S_IRUGO | S_IWUSR);
+
+/*
+ * Threshold below which the tty is woken for writing
+ * - should be equal to WAKEUP_CHARS in drivers/tty/n_tty.c because
+ *   even if the writer is woken, n_tty_poll() won't set POLLOUT until
+ *   our fifo is below this level
+ */
+#define WAKEUP_CHARS             256
+
+/**
+ * fwserial_list: list of every fw_serial created for each fw_card
+ * See discussion in fwserial_probe.
+ */
+static LIST_HEAD(fwserial_list);
+static DEFINE_MUTEX(fwserial_list_mutex);
+
+/**
+ * port_table: array of tty ports allocated to each fw_card
+ *
+ * tty ports are allocated during probe when an fw_serial is first
+ * created for a given fw_card. Ports are allocated in a contiguous block,
+ * each block consisting of 'num_ports' ports.
+ */
+static struct fwtty_port *port_table[MAX_TOTAL_PORTS];
+static DEFINE_MUTEX(port_table_lock);
+static bool port_table_corrupt;
+#define FWTTY_INVALID_INDEX  MAX_TOTAL_PORTS
+
+/* total # of tty ports created per fw_card */
+static int num_ports;
+
+/* slab used as pool for struct fwtty_transactions */
+static struct kmem_cache *fwtty_txn_cache;
+
+struct fwtty_transaction;
+typedef void (*fwtty_transaction_cb)(struct fw_card *card, int rcode,
+				     void *data, size_t length,
+				     struct fwtty_transaction *txn);
+
+struct fwtty_transaction {
+	struct fw_transaction      fw_txn;
+	fwtty_transaction_cb       callback;
+	struct fwtty_port	   *port;
+	union {
+		struct dma_pending dma_pended;
+	};
+};
+
+#define to_device(a, b)			(a->b)
+#define fwtty_err(p, s, v...)		dev_err(to_device(p, device), s, ##v)
+#define fwtty_info(p, s, v...)		dev_info(to_device(p, device), s, ##v)
+#define fwtty_notice(p, s, v...)	dev_notice(to_device(p, device), s, ##v)
+#define fwtty_dbg(p, s, v...)		\
+		dev_dbg(to_device(p, device), "%s: " s, __func__, ##v)
+#define fwtty_err_ratelimited(p, s, v...) \
+		dev_err_ratelimited(to_device(p, device), s, ##v)
+
+#ifdef DEBUG
+static inline void debug_short_write(struct fwtty_port *port, int c, int n)
+{
+	int avail;
+
+	if (n < c) {
+		spin_lock_bh(&port->lock);
+		avail = dma_fifo_avail(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+		fwtty_dbg(port, "short write: avail:%d req:%d wrote:%d",
+			  avail, c, n);
+	}
+}
+#else
+#define debug_short_write(port, c, n)
+#endif
+
+static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card,
+						     int generation, int id);
+
+#ifdef FWTTY_PROFILING
+
+static void profile_fifo_avail(struct fwtty_port *port, unsigned *stat)
+{
+	spin_lock_bh(&port->lock);
+	profile_size_distrib(stat, dma_fifo_avail(&port->tx_fifo));
+	spin_unlock_bh(&port->lock);
+}
+
+static void dump_profile(struct seq_file *m, struct stats *stats)
+{
+	/* for each stat, print sum of 0 to 2^k, then individually */
+	int k = 4;
+	unsigned sum;
+	int j;
+	char t[10];
+
+	snprintf(t, 10, "< %d", 1 << k);
+	seq_printf(m, "\n%14s  %6s", " ", t);
+	for (j = k + 1; j < DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", 1 << j);
+
+	++k;
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->reads[j];
+	seq_printf(m, "\n%14s: %6d", "reads", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->reads[j]);
+
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->writes[j];
+	seq_printf(m, "\n%14s: %6d", "writes", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->writes[j]);
+
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->txns[j];
+	seq_printf(m, "\n%14s: %6d", "txns", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->txns[j]);
+
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->unthrottle[j];
+	seq_printf(m, "\n%14s: %6d", "avail @ unthr", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->unthrottle[j]);
+}
+
+#else
+#define profile_fifo_avail(port, stat)
+#define dump_profile(m, stats)
+#endif
+
+/* Returns the max receive packet size for the given card */
+static inline int device_max_receive(struct fw_device *fw_device)
+{
+	return 1 <<  (clamp_t(int, fw_device->max_rec, 8U, 13U) + 1);
+}
+
+static void fwtty_log_tx_error(struct fwtty_port *port, int rcode)
+{
+	switch (rcode) {
+	case RCODE_SEND_ERROR:
+		fwtty_err_ratelimited(port, "card busy");
+		break;
+	case RCODE_ADDRESS_ERROR:
+		fwtty_err_ratelimited(port, "bad unit addr or write length");
+		break;
+	case RCODE_DATA_ERROR:
+		fwtty_err_ratelimited(port, "failed rx");
+		break;
+	case RCODE_NO_ACK:
+		fwtty_err_ratelimited(port, "missing ack");
+		break;
+	case RCODE_BUSY:
+		fwtty_err_ratelimited(port, "remote busy");
+		break;
+	default:
+		fwtty_err_ratelimited(port, "failed tx: %d", rcode);
+	}
+}
+
+static void fwtty_txn_constructor(void *this)
+{
+	struct fwtty_transaction *txn = this;
+
+	init_timer(&txn->fw_txn.split_timeout_timer);
+}
+
+static void fwtty_common_callback(struct fw_card *card, int rcode,
+				  void *payload, size_t len, void *cb_data)
+{
+	struct fwtty_transaction *txn = cb_data;
+	struct fwtty_port *port = txn->port;
+
+	if (port && rcode != RCODE_COMPLETE)
+		fwtty_log_tx_error(port, rcode);
+	if (txn->callback)
+		txn->callback(card, rcode, payload, len, txn);
+	kmem_cache_free(fwtty_txn_cache, txn);
+}
+
+static int fwtty_send_data_async(struct fwtty_peer *peer, int tcode,
+				 unsigned long long addr, void *payload,
+				 size_t len, fwtty_transaction_cb callback,
+				 struct fwtty_port *port)
+{
+	struct fwtty_transaction *txn;
+	int generation;
+
+	txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC);
+	if (!txn)
+		return -ENOMEM;
+
+	txn->callback = callback;
+	txn->port = port;
+
+	generation = peer->generation;
+	smp_rmb();
+	fw_send_request(peer->serial->card, &txn->fw_txn, tcode,
+			peer->node_id, generation, peer->speed, addr, payload,
+			len, fwtty_common_callback, txn);
+	return 0;
+}
+
+static void fwtty_send_txn_async(struct fwtty_peer *peer,
+				 struct fwtty_transaction *txn, int tcode,
+				 unsigned long long addr, void *payload,
+				 size_t len, fwtty_transaction_cb callback,
+				 struct fwtty_port *port)
+{
+	int generation;
+
+	txn->callback = callback;
+	txn->port = port;
+
+	generation = peer->generation;
+	smp_rmb();
+	fw_send_request(peer->serial->card, &txn->fw_txn, tcode,
+			peer->node_id, generation, peer->speed, addr, payload,
+			len, fwtty_common_callback, txn);
+}
+
+
+static void __fwtty_restart_tx(struct fwtty_port *port)
+{
+	int len, avail;
+
+	len = dma_fifo_out_level(&port->tx_fifo);
+	if (len)
+		schedule_delayed_work(&port->drain, 0);
+	avail = dma_fifo_avail(&port->tx_fifo);
+
+	fwtty_dbg(port, "fifo len: %d avail: %d", len, avail);
+}
+
+static void fwtty_restart_tx(struct fwtty_port *port)
+{
+	spin_lock_bh(&port->lock);
+	__fwtty_restart_tx(port);
+	spin_unlock_bh(&port->lock);
+}
+
+/**
+ * fwtty_update_port_status - decodes & dispatches line status changes
+ *
+ * Note: in loopback, the port->lock is being held. Only use functions that
+ * don't attempt to reclaim the port->lock.
+ */
+static void fwtty_update_port_status(struct fwtty_port *port, unsigned status)
+{
+	unsigned delta;
+	struct tty_struct *tty;
+
+	/* simulated LSR/MSR status from remote */
+	status &= ~MCTRL_MASK;
+	delta = (port->mstatus ^ status) & ~MCTRL_MASK;
+	delta &= ~(status & TIOCM_RNG);
+	port->mstatus = status;
+
+	if (delta & TIOCM_RNG)
+		++port->icount.rng;
+	if (delta & TIOCM_DSR)
+		++port->icount.dsr;
+	if (delta & TIOCM_CAR)
+		++port->icount.dcd;
+	if (delta & TIOCM_CTS)
+		++port->icount.cts;
+
+	fwtty_dbg(port, "status: %x delta: %x", status, delta);
+
+	if (delta & TIOCM_CAR) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty && !C_CLOCAL(tty)) {
+			if (status & TIOCM_CAR)
+				wake_up_interruptible(&port->port.open_wait);
+			else
+				schedule_work(&port->hangup);
+		}
+		tty_kref_put(tty);
+	}
+
+	if (delta & TIOCM_CTS) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty && C_CRTSCTS(tty)) {
+			if (tty->hw_stopped) {
+				if (status & TIOCM_CTS) {
+					tty->hw_stopped = 0;
+					if (port->loopback)
+						__fwtty_restart_tx(port);
+					else
+						fwtty_restart_tx(port);
+				}
+			} else {
+				if (~status & TIOCM_CTS)
+					tty->hw_stopped = 1;
+			}
+		}
+		tty_kref_put(tty);
+
+	} else if (delta & OOB_TX_THROTTLE) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty) {
+			if (tty->hw_stopped) {
+				if (~status & OOB_TX_THROTTLE) {
+					tty->hw_stopped = 0;
+					if (port->loopback)
+						__fwtty_restart_tx(port);
+					else
+						fwtty_restart_tx(port);
+				}
+			} else {
+				if (status & OOB_TX_THROTTLE)
+					tty->hw_stopped = 1;
+			}
+		}
+		tty_kref_put(tty);
+	}
+
+	if (delta & (UART_LSR_BI << 24)) {
+		if (status & (UART_LSR_BI << 24)) {
+			port->break_last = jiffies;
+			schedule_delayed_work(&port->emit_breaks, 0);
+		} else {
+			/* run emit_breaks one last time (if pending) */
+			mod_delayed_work(system_wq, &port->emit_breaks, 0);
+		}
+	}
+
+	if (delta & (TIOCM_DSR | TIOCM_CAR | TIOCM_CTS | TIOCM_RNG))
+		wake_up_interruptible(&port->port.delta_msr_wait);
+}
+
+/**
+ * __fwtty_port_line_status - generate 'line status' for indicated port
+ *
+ * This function returns a remote 'MSR' state based on the local 'MCR' state,
+ * as if a null modem cable was attached. The actual status is a mangling
+ * of TIOCM_* bits suitable for sending to a peer's status_addr.
+ *
+ * Note: caller must be holding port lock
+ */
+static unsigned __fwtty_port_line_status(struct fwtty_port *port)
+{
+	unsigned status = 0;
+
+	/* TODO: add module param to tie RNG to DTR as well */
+
+	if (port->mctrl & TIOCM_DTR)
+		status |= TIOCM_DSR | TIOCM_CAR;
+	if (port->mctrl & TIOCM_RTS)
+		status |= TIOCM_CTS;
+	if (port->mctrl & OOB_RX_THROTTLE)
+		status |= OOB_TX_THROTTLE;
+	/* emulate BRK as add'l line status */
+	if (port->break_ctl)
+		status |= UART_LSR_BI << 24;
+
+	return status;
+}
+
+/**
+ * __fwtty_write_port_status - send the port line status to peer
+ *
+ * Note: caller must be holding the port lock.
+ */
+static int __fwtty_write_port_status(struct fwtty_port *port)
+{
+	struct fwtty_peer *peer;
+	int err = -ENOENT;
+	unsigned status = __fwtty_port_line_status(port);
+
+	rcu_read_lock();
+	peer = rcu_dereference(port->peer);
+	if (peer) {
+		err = fwtty_send_data_async(peer, TCODE_WRITE_QUADLET_REQUEST,
+					    peer->status_addr, &status,
+					    sizeof(status), NULL, port);
+	}
+	rcu_read_unlock();
+
+	return err;
+}
+
+/**
+ * fwtty_write_port_status - same as above but locked by port lock
+ */
+static int fwtty_write_port_status(struct fwtty_port *port)
+{
+	int err;
+
+	spin_lock_bh(&port->lock);
+	err = __fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+	return err;
+}
+
+static void __fwtty_throttle(struct fwtty_port *port, struct tty_struct *tty)
+{
+	unsigned old;
+
+	old = port->mctrl;
+	port->mctrl |= OOB_RX_THROTTLE;
+	if (C_CRTSCTS(tty))
+		port->mctrl &= ~TIOCM_RTS;
+	if (~old & OOB_RX_THROTTLE)
+		__fwtty_write_port_status(port);
+}
+
+/**
+ * fwtty_do_hangup - wait for ldisc to deliver all pending rx; only then hangup
+ *
+ * When the remote has finished tx, and all in-flight rx has been received and
+ * and pushed to the flip buffer, the remote may close its device. This will
+ * drop DTR on the remote which will drop carrier here. Typically, the tty is
+ * hung up when carrier is dropped or lost.
+ *
+ * However, there is a race between the hang up and the line discipline
+ * delivering its data to the reader. A hangup will cause the ldisc to flush
+ * (ie., clear) the read buffer and flip buffer. Because of firewire's
+ * relatively high throughput, the ldisc frequently lags well behind the driver,
+ * resulting in lost data (which has already been received and written to
+ * the flip buffer) when the remote closes its end.
+ *
+ * Unfortunately, since the flip buffer offers no direct method for determining
+ * if it holds data, ensuring the ldisc has delivered all data is problematic.
+ */
+
+/* FIXME: drop this workaround when __tty_hangup waits for ldisc completion */
+static void fwtty_do_hangup(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(work, hangup);
+	struct tty_struct *tty;
+
+	schedule_timeout_uninterruptible(msecs_to_jiffies(50));
+
+	tty = tty_port_tty_get(&port->port);
+	if (tty)
+		tty_vhangup(tty);
+	tty_kref_put(tty);
+}
+
+
+static void fwtty_emit_breaks(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(to_delayed_work(work), emit_breaks);
+	struct tty_struct *tty;
+	static const char buf[16];
+	unsigned long now = jiffies;
+	unsigned long elapsed = now - port->break_last;
+	int n, t, c, brk = 0;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return;
+
+	/* generate breaks at the line rate (but at least 1) */
+	n = (elapsed * port->cps) / HZ + 1;
+	port->break_last = now;
+
+	fwtty_dbg(port, "sending %d brks", n);
+
+	while (n) {
+		t = min(n, 16);
+		c = tty_insert_flip_string_fixed_flag(tty, buf, TTY_BREAK, t);
+		n -= c;
+		brk += c;
+		if (c < t)
+			break;
+	}
+	tty_flip_buffer_push(tty);
+
+	tty_kref_put(tty);
+
+	if (port->mstatus & (UART_LSR_BI << 24))
+		schedule_delayed_work(&port->emit_breaks, FREQ_BREAKS);
+	port->icount.brk += brk;
+}
+
+static void fwtty_pushrx(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(work, push);
+	struct tty_struct *tty;
+	struct buffered_rx *buf, *next;
+	int n, c = 0;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return;
+
+	spin_lock_bh(&port->lock);
+	list_for_each_entry_safe(buf, next, &port->buf_list, list) {
+		n = tty_insert_flip_string_fixed_flag(tty, buf->data,
+						      TTY_NORMAL, buf->n);
+		c += n;
+		port->buffered -= n;
+		if (n < buf->n) {
+			if (n > 0) {
+				memmove(buf->data, buf->data + n, buf->n - n);
+				buf->n -= n;
+			}
+			__fwtty_throttle(port, tty);
+			break;
+		} else {
+			list_del(&buf->list);
+			kfree(buf);
+		}
+	}
+	if (c > 0)
+		tty_flip_buffer_push(tty);
+
+	if (list_empty(&port->buf_list))
+		clear_bit(BUFFERING_RX, &port->flags);
+	spin_unlock_bh(&port->lock);
+
+	tty_kref_put(tty);
+}
+
+static int fwtty_buffer_rx(struct fwtty_port *port, unsigned char *d, size_t n)
+{
+	struct buffered_rx *buf;
+	size_t size = (n + sizeof(struct buffered_rx) + 0xFF) & ~0xFF;
+
+	if (port->buffered + n > HIGH_WATERMARK)
+		return 0;
+	buf = kmalloc(size, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+	INIT_LIST_HEAD(&buf->list);
+	buf->n = n;
+	memcpy(buf->data, d, n);
+
+	spin_lock_bh(&port->lock);
+	list_add_tail(&buf->list, &port->buf_list);
+	port->buffered += n;
+	if (port->buffered > port->stats.watermark)
+		port->stats.watermark = port->buffered;
+	set_bit(BUFFERING_RX, &port->flags);
+	spin_unlock_bh(&port->lock);
+
+	return n;
+}
+
+static int fwtty_rx(struct fwtty_port *port, unsigned char *data, size_t len)
+{
+	struct tty_struct *tty;
+	int c, n = len;
+	unsigned lsr;
+	int err = 0;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return -ENOENT;
+
+	fwtty_dbg(port, "%d", n);
+	profile_size_distrib(port->stats.reads, n);
+
+	if (port->write_only) {
+		n = 0;
+		goto out;
+	}
+
+	/* disregard break status; breaks are generated by emit_breaks work */
+	lsr = (port->mstatus >> 24) & ~UART_LSR_BI;
+
+	if (port->overrun)
+		lsr |= UART_LSR_OE;
+
+	if (lsr & UART_LSR_OE)
+		++port->icount.overrun;
+
+	lsr &= port->status_mask;
+	if (lsr & ~port->ignore_mask & UART_LSR_OE) {
+		if (!tty_insert_flip_char(tty, 0, TTY_OVERRUN)) {
+			err = -EIO;
+			goto out;
+		}
+	}
+	port->overrun = false;
+
+	if (lsr & port->ignore_mask & ~UART_LSR_OE) {
+		/* TODO: don't drop SAK and Magic SysRq here */
+		n = 0;
+		goto out;
+	}
+
+	if (!test_bit(BUFFERING_RX, &port->flags)) {
+		c = tty_insert_flip_string_fixed_flag(tty, data, TTY_NORMAL, n);
+		if (c > 0)
+			tty_flip_buffer_push(tty);
+		n -= c;
+
+		if (n) {
+			/* start buffering and throttling */
+			n -= fwtty_buffer_rx(port, &data[c], n);
+
+			spin_lock_bh(&port->lock);
+			__fwtty_throttle(port, tty);
+			spin_unlock_bh(&port->lock);
+		}
+	} else
+		n -= fwtty_buffer_rx(port, data, n);
+
+	if (n) {
+		port->overrun = true;
+		err = -EIO;
+	}
+
+out:
+	tty_kref_put(tty);
+
+	port->icount.rx += len;
+	port->stats.lost += n;
+	return err;
+}
+
+/**
+ * fwtty_port_handler - bus address handler for port reads/writes
+ * @parameters: fw_address_callback_t as specified by firewire core interface
+ *
+ * This handler is responsible for handling inbound read/write dma from remotes.
+ */
+static void fwtty_port_handler(struct fw_card *card,
+			       struct fw_request *request,
+			       int tcode, int destination, int source,
+			       int generation,
+			       unsigned long long addr,
+			       void *data, size_t len,
+			       void *callback_data)
+{
+	struct fwtty_port *port = callback_data;
+	struct fwtty_peer *peer;
+	int err;
+	int rcode;
+
+	/* Only accept rx from the peer virtual-cabled to this port */
+	rcu_read_lock();
+	peer = __fwserial_peer_by_node_id(card, generation, source);
+	rcu_read_unlock();
+	if (!peer || peer != rcu_access_pointer(port->peer)) {
+		rcode = RCODE_ADDRESS_ERROR;
+		fwtty_err_ratelimited(port, "ignoring unauthenticated data");
+		goto respond;
+	}
+
+	switch (tcode) {
+	case TCODE_WRITE_QUADLET_REQUEST:
+		if (addr != port->rx_handler.offset || len != 4)
+			rcode = RCODE_ADDRESS_ERROR;
+		else {
+			fwtty_update_port_status(port, *(unsigned *)data);
+			rcode = RCODE_COMPLETE;
+		}
+		break;
+
+	case TCODE_WRITE_BLOCK_REQUEST:
+		if (addr != port->rx_handler.offset + 4 ||
+		    len > port->rx_handler.length - 4) {
+			rcode = RCODE_ADDRESS_ERROR;
+		} else {
+			err = fwtty_rx(port, data, len);
+			switch (err) {
+			case 0:
+				rcode = RCODE_COMPLETE;
+				break;
+			case -EIO:
+				rcode = RCODE_DATA_ERROR;
+				break;
+			default:
+				rcode = RCODE_CONFLICT_ERROR;
+				break;
+			}
+		}
+		break;
+
+	default:
+		rcode = RCODE_TYPE_ERROR;
+	}
+
+respond:
+	fw_send_response(card, request, rcode);
+}
+
+/**
+ * fwtty_tx_complete - callback for tx dma
+ * @data: ignored, has no meaning for write txns
+ * @length: ignored, has no meaning for write txns
+ *
+ * The writer must be woken here if the fifo has been emptied because it
+ * may have slept if chars_in_buffer was != 0
+ */
+static void fwtty_tx_complete(struct fw_card *card, int rcode,
+			      void *data, size_t length,
+			      struct fwtty_transaction *txn)
+{
+	struct fwtty_port *port = txn->port;
+	struct tty_struct *tty;
+	int len;
+
+	fwtty_dbg(port, "rcode: %d", rcode);
+
+	switch (rcode) {
+	case RCODE_COMPLETE:
+		spin_lock_bh(&port->lock);
+		dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended);
+		len = dma_fifo_level(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+
+		port->icount.tx += txn->dma_pended.len;
+		break;
+
+	default:
+		/* TODO: implement retries */
+		spin_lock_bh(&port->lock);
+		dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended);
+		len = dma_fifo_level(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+
+		port->stats.dropped += txn->dma_pended.len;
+	}
+
+	if (len < WAKEUP_CHARS) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty) {
+			tty_wakeup(tty);
+			tty_kref_put(tty);
+		}
+	}
+}
+
+static int fwtty_tx(struct fwtty_port *port, bool drain)
+{
+	struct fwtty_peer *peer;
+	struct fwtty_transaction *txn;
+	struct tty_struct *tty;
+	int n, len;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return -ENOENT;
+
+	rcu_read_lock();
+	peer = rcu_dereference(port->peer);
+	if (!peer) {
+		n = -EIO;
+		goto out;
+	}
+
+	if (test_and_set_bit(IN_TX, &port->flags)) {
+		n = -EALREADY;
+		goto out;
+	}
+
+	/* try to write as many dma transactions out as possible */
+	n = -EAGAIN;
+	while (!tty->stopped && !tty->hw_stopped &&
+			!test_bit(STOP_TX, &port->flags)) {
+		txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC);
+		if (!txn) {
+			n = -ENOMEM;
+			break;
+		}
+
+		spin_lock_bh(&port->lock);
+		n = dma_fifo_out_pend(&port->tx_fifo, &txn->dma_pended);
+		spin_unlock_bh(&port->lock);
+
+		fwtty_dbg(port, "out: %u rem: %d", txn->dma_pended.len, n);
+
+		if (n < 0) {
+			kmem_cache_free(fwtty_txn_cache, txn);
+			if (n == -EAGAIN)
+				++port->stats.tx_stall;
+			else if (n == -ENODATA)
+				profile_size_distrib(port->stats.txns, 0);
+			else {
+				++port->stats.fifo_errs;
+				fwtty_err_ratelimited(port, "fifo err: %d", n);
+			}
+			break;
+		}
+
+		profile_size_distrib(port->stats.txns, txn->dma_pended.len);
+
+		fwtty_send_txn_async(peer, txn, TCODE_WRITE_BLOCK_REQUEST,
+				     peer->fifo_addr, txn->dma_pended.data,
+				     txn->dma_pended.len, fwtty_tx_complete,
+				     port);
+		++port->stats.sent;
+
+		/*
+		 * Stop tx if the 'last view' of the fifo is empty or if
+		 * this is the writer and there's not enough data to bother
+		 */
+		if (n == 0 || (!drain && n < WRITER_MINIMUM))
+			break;
+	}
+
+	if (n >= 0 || n == -EAGAIN || n == -ENOMEM || n == -ENODATA) {
+		spin_lock_bh(&port->lock);
+		len = dma_fifo_out_level(&port->tx_fifo);
+		if (len) {
+			unsigned long delay = (n == -ENOMEM) ? HZ : 1;
+			schedule_delayed_work(&port->drain, delay);
+		}
+		len = dma_fifo_level(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+
+		/* wakeup the writer */
+		if (drain && len < WAKEUP_CHARS)
+			tty_wakeup(tty);
+	}
+
+	clear_bit(IN_TX, &port->flags);
+	wake_up_interruptible(&port->wait_tx);
+
+out:
+	rcu_read_unlock();
+	tty_kref_put(tty);
+	return n;
+}
+
+static void fwtty_drain_tx(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(to_delayed_work(work), drain);
+
+	fwtty_tx(port, true);
+}
+
+static void fwtty_write_xchar(struct fwtty_port *port, char ch)
+{
+	struct fwtty_peer *peer;
+
+	++port->stats.xchars;
+
+	fwtty_dbg(port, "%02x", ch);
+
+	rcu_read_lock();
+	peer = rcu_dereference(port->peer);
+	if (peer) {
+		fwtty_send_data_async(peer, TCODE_WRITE_BLOCK_REQUEST,
+				      peer->fifo_addr, &ch, sizeof(ch),
+				      NULL, port);
+	}
+	rcu_read_unlock();
+}
+
+struct fwtty_port *fwtty_port_get(unsigned index)
+{
+	struct fwtty_port *port;
+
+	if (index >= MAX_TOTAL_PORTS)
+		return NULL;
+
+	mutex_lock(&port_table_lock);
+	port = port_table[index];
+	if (port)
+		kref_get(&port->serial->kref);
+	mutex_unlock(&port_table_lock);
+	return port;
+}
+EXPORT_SYMBOL(fwtty_port_get);
+
+static int fwtty_ports_add(struct fw_serial *serial)
+{
+	int err = -EBUSY;
+	int i, j;
+
+	if (port_table_corrupt)
+		return err;
+
+	mutex_lock(&port_table_lock);
+	for (i = 0; i + num_ports <= MAX_TOTAL_PORTS; i += num_ports) {
+		if (!port_table[i]) {
+			for (j = 0; j < num_ports; ++i, ++j) {
+				serial->ports[j]->index = i;
+				port_table[i] = serial->ports[j];
+			}
+			err = 0;
+			break;
+		}
+	}
+	mutex_unlock(&port_table_lock);
+	return err;
+}
+
+static void fwserial_destroy(struct kref *kref)
+{
+	struct fw_serial *serial = to_serial(kref, kref);
+	struct fwtty_port **ports = serial->ports;
+	int j, i = ports[0]->index;
+
+	synchronize_rcu();
+
+	mutex_lock(&port_table_lock);
+	for (j = 0; j < num_ports; ++i, ++j) {
+		static bool once;
+		int corrupt = port_table[i] != ports[j];
+		if (corrupt && !once) {
+			WARN(corrupt, "port_table[%d]: %p != ports[%d]: %p",
+			     i, port_table[i], j, ports[j]);
+			once = true;
+			port_table_corrupt = true;
+		}
+
+		port_table[i] = NULL;
+	}
+	mutex_unlock(&port_table_lock);
+
+	for (j = 0; j < num_ports; ++j) {
+		fw_core_remove_address_handler(&ports[j]->rx_handler);
+		dma_fifo_free(&ports[j]->tx_fifo);
+		kfree(ports[j]);
+	}
+	kfree(serial);
+}
+
+void fwtty_port_put(struct fwtty_port *port)
+{
+	kref_put(&port->serial->kref, fwserial_destroy);
+}
+EXPORT_SYMBOL(fwtty_port_put);
+
+static void fwtty_port_dtr_rts(struct tty_port *tty_port, int on)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+
+	fwtty_dbg(port, "on/off: %d", on);
+
+	spin_lock_bh(&port->lock);
+	/* Don't change carrier state if this is a console */
+	if (!port->port.console) {
+		if (on)
+			port->mctrl |= TIOCM_DTR | TIOCM_RTS;
+		else
+			port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS);
+	}
+
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+}
+
+/**
+ * fwtty_port_carrier_raised: required tty_port operation
+ *
+ * This port operation is polled after a tty has been opened and is waiting for
+ * carrier detect -- see drivers/tty/tty_port:tty_port_block_til_ready().
+ */
+static int fwtty_port_carrier_raised(struct tty_port *tty_port)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+	int rc;
+
+	rc = (port->mstatus & TIOCM_CAR);
+
+	fwtty_dbg(port, "%d", rc);
+
+	return rc;
+}
+
+static unsigned set_termios(struct fwtty_port *port, struct tty_struct *tty)
+{
+	unsigned baud, frame;
+
+	baud = tty_termios_baud_rate(&tty->termios);
+	tty_termios_encode_baud_rate(&tty->termios, baud, baud);
+
+	/* compute bit count of 2 frames */
+	frame = 12 + ((C_CSTOPB(tty)) ? 4 : 2) + ((C_PARENB(tty)) ? 2 : 0);
+
+	switch (C_CSIZE(tty)) {
+	case CS5:
+		frame -= (C_CSTOPB(tty)) ? 1 : 0;
+		break;
+	case CS6:
+		frame += 2;
+		break;
+	case CS7:
+		frame += 4;
+		break;
+	case CS8:
+		frame += 6;
+		break;
+	}
+
+	port->cps = (baud << 1) / frame;
+
+	port->status_mask = UART_LSR_OE;
+	if (_I_FLAG(tty, BRKINT | PARMRK))
+		port->status_mask |= UART_LSR_BI;
+
+	port->ignore_mask = 0;
+	if (I_IGNBRK(tty)) {
+		port->ignore_mask |= UART_LSR_BI;
+		if (I_IGNPAR(tty))
+			port->ignore_mask |= UART_LSR_OE;
+	}
+
+	port->write_only = !C_CREAD(tty);
+
+	/* turn off echo and newline xlat if loopback */
+	if (port->loopback) {
+		tty->termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE |
+					  ECHONL | ECHOPRT | ECHOCTL);
+		tty->termios.c_oflag &= ~ONLCR;
+	}
+
+	return baud;
+}
+
+static int fwtty_port_activate(struct tty_port *tty_port,
+			       struct tty_struct *tty)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+	unsigned baud;
+	int err;
+
+	set_bit(TTY_IO_ERROR, &tty->flags);
+
+	err = dma_fifo_alloc(&port->tx_fifo, FWTTY_PORT_TXFIFO_LEN,
+			     cache_line_size(),
+			     port->max_payload,
+			     FWTTY_PORT_MAX_PEND_DMA,
+			     GFP_KERNEL);
+	if (err)
+		return err;
+
+	spin_lock_bh(&port->lock);
+
+	baud = set_termios(port, tty);
+
+	/* if console, don't change carrier state */
+	if (!port->port.console) {
+		port->mctrl = 0;
+		if (baud != 0)
+			port->mctrl = TIOCM_DTR | TIOCM_RTS;
+	}
+
+	if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS)
+		tty->hw_stopped = 1;
+
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+
+	clear_bit(TTY_IO_ERROR, &tty->flags);
+
+	return 0;
+}
+
+/**
+ * fwtty_port_shutdown
+ *
+ * Note: the tty port core ensures this is not the console and
+ * manages TTY_IO_ERROR properly
+ */
+static void fwtty_port_shutdown(struct tty_port *tty_port)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+	struct buffered_rx *buf, *next;
+
+	/* TODO: cancel outstanding transactions */
+
+	cancel_delayed_work_sync(&port->emit_breaks);
+	cancel_delayed_work_sync(&port->drain);
+	cancel_work_sync(&port->push);
+
+	spin_lock_bh(&port->lock);
+	list_for_each_entry_safe(buf, next, &port->buf_list, list) {
+		list_del(&buf->list);
+		kfree(buf);
+	}
+	port->buffered = 0;
+	port->flags = 0;
+	port->break_ctl = 0;
+	port->overrun = 0;
+	__fwtty_write_port_status(port);
+	dma_fifo_free(&port->tx_fifo);
+	spin_unlock_bh(&port->lock);
+}
+
+static int fwtty_open(struct tty_struct *tty, struct file *fp)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	return tty_port_open(&port->port, tty, fp);
+}
+
+static void fwtty_close(struct tty_struct *tty, struct file *fp)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	tty_port_close(&port->port, tty, fp);
+}
+
+static void fwtty_hangup(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	tty_port_hangup(&port->port);
+}
+
+static void fwtty_cleanup(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	tty->driver_data = NULL;
+	fwtty_port_put(port);
+}
+
+static int fwtty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	struct fwtty_port *port = fwtty_port_get(tty->index);
+	int err;
+
+	err = tty_standard_install(driver, tty);
+	if (!err)
+		tty->driver_data = port;
+	else
+		fwtty_port_put(port);
+	return err;
+}
+
+static int fwtty_write(struct tty_struct *tty, const unsigned char *buf, int c)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int n, len;
+
+	fwtty_dbg(port, "%d", c);
+	profile_size_distrib(port->stats.writes, c);
+
+	spin_lock_bh(&port->lock);
+	n = dma_fifo_in(&port->tx_fifo, buf, c);
+	len = dma_fifo_out_level(&port->tx_fifo);
+	if (len < DRAIN_THRESHOLD)
+		schedule_delayed_work(&port->drain, 1);
+	spin_unlock_bh(&port->lock);
+
+	if (len >= DRAIN_THRESHOLD)
+		fwtty_tx(port, false);
+
+	debug_short_write(port, c, n);
+
+	return (n < 0) ? 0 : n;
+}
+
+static int fwtty_write_room(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int n;
+
+	spin_lock_bh(&port->lock);
+	n = dma_fifo_avail(&port->tx_fifo);
+	spin_unlock_bh(&port->lock);
+
+	fwtty_dbg(port, "%d", n);
+
+	return n;
+}
+
+static int fwtty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int n;
+
+	spin_lock_bh(&port->lock);
+	n = dma_fifo_level(&port->tx_fifo);
+	spin_unlock_bh(&port->lock);
+
+	fwtty_dbg(port, "%d", n);
+
+	return n;
+}
+
+static void fwtty_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	fwtty_dbg(port, "%02x", ch);
+
+	fwtty_write_xchar(port, ch);
+}
+
+static void fwtty_throttle(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	/*
+	 * Ignore throttling (but not unthrottling).
+	 * It only makes sense to throttle when data will no longer be
+	 * accepted by the tty flip buffer. For example, it is
+	 * possible for received data to overflow the tty buffer long
+	 * before the line discipline ever has a chance to throttle the driver.
+	 * Additionally, the driver may have already completed the I/O
+	 * but the tty buffer is still emptying, so the line discipline is
+	 * throttling and unthrottling nothing.
+	 */
+
+	++port->stats.throttled;
+}
+
+static void fwtty_unthrottle(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	fwtty_dbg(port, "CRTSCTS: %d", (C_CRTSCTS(tty) != 0));
+
+	profile_fifo_avail(port, port->stats.unthrottle);
+
+	schedule_work(&port->push);
+
+	spin_lock_bh(&port->lock);
+	port->mctrl &= ~OOB_RX_THROTTLE;
+	if (C_CRTSCTS(tty))
+		port->mctrl |= TIOCM_RTS;
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+}
+
+static int check_msr_delta(struct fwtty_port *port, unsigned long mask,
+			   struct async_icount *prev)
+{
+	struct async_icount now;
+	int delta;
+
+	now = port->icount;
+
+	delta = ((mask & TIOCM_RNG && prev->rng != now.rng) ||
+		 (mask & TIOCM_DSR && prev->dsr != now.dsr) ||
+		 (mask & TIOCM_CAR && prev->dcd != now.dcd) ||
+		 (mask & TIOCM_CTS && prev->cts != now.cts));
+
+	*prev = now;
+
+	return delta;
+}
+
+static int wait_msr_change(struct fwtty_port *port, unsigned long mask)
+{
+	struct async_icount prev;
+
+	prev = port->icount;
+
+	return wait_event_interruptible(port->port.delta_msr_wait,
+					check_msr_delta(port, mask, &prev));
+}
+
+static int get_serial_info(struct fwtty_port *port,
+			   struct serial_struct __user *info)
+{
+	struct serial_struct tmp;
+
+	memset(&tmp, 0, sizeof(tmp));
+
+	tmp.type =  PORT_UNKNOWN;
+	tmp.line =  port->port.tty->index;
+	tmp.flags = port->port.flags;
+	tmp.xmit_fifo_size = FWTTY_PORT_TXFIFO_LEN;
+	tmp.baud_base = 400000000;
+	tmp.close_delay = port->port.close_delay;
+
+	return (copy_to_user(info, &tmp, sizeof(*info))) ? -EFAULT : 0;
+}
+
+static int set_serial_info(struct fwtty_port *port,
+			   struct serial_struct __user *info)
+{
+	struct serial_struct tmp;
+
+	if (copy_from_user(&tmp, info, sizeof(tmp)))
+		return -EFAULT;
+
+	if (tmp.irq != 0 || tmp.port != 0 || tmp.custom_divisor != 0 ||
+			tmp.baud_base != 400000000)
+		return -EPERM;
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if (((tmp.flags & ~ASYNC_USR_MASK) !=
+		     (port->port.flags & ~ASYNC_USR_MASK)))
+			return -EPERM;
+	} else
+		port->port.close_delay = tmp.close_delay * HZ / 100;
+
+	return 0;
+}
+
+static int fwtty_ioctl(struct tty_struct *tty, unsigned cmd,
+		       unsigned long arg)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int err;
+
+	switch (cmd) {
+	case TIOCGSERIAL:
+		mutex_lock(&port->port.mutex);
+		err = get_serial_info(port, (void __user *)arg);
+		mutex_unlock(&port->port.mutex);
+		break;
+
+	case TIOCSSERIAL:
+		mutex_lock(&port->port.mutex);
+		err = set_serial_info(port, (void __user *)arg);
+		mutex_unlock(&port->port.mutex);
+		break;
+
+	case TIOCMIWAIT:
+		err = wait_msr_change(port, arg);
+		break;
+
+	default:
+		err = -ENOIOCTLCMD;
+	}
+
+	return err;
+}
+
+static void fwtty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+	struct fwtty_port *port = tty->driver_data;
+	unsigned baud;
+
+	spin_lock_bh(&port->lock);
+	baud = set_termios(port, tty);
+
+	if ((baud == 0) && (old->c_cflag & CBAUD))
+		port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS);
+	else if ((baud != 0) && !(old->c_cflag & CBAUD)) {
+		if (C_CRTSCTS(tty) || !test_bit(TTY_THROTTLED, &tty->flags))
+			port->mctrl |= TIOCM_DTR | TIOCM_RTS;
+		else
+			port->mctrl |= TIOCM_DTR;
+	}
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+
+	if (old->c_cflag & CRTSCTS) {
+		if (!C_CRTSCTS(tty)) {
+			tty->hw_stopped = 0;
+			fwtty_restart_tx(port);
+		}
+	} else if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS) {
+		tty->hw_stopped = 1;
+	}
+}
+
+/**
+ * fwtty_break_ctl - start/stop sending breaks
+ *
+ * Signals the remote to start or stop generating simulated breaks.
+ * First, stop dequeueing from the fifo and wait for writer/drain to leave tx
+ * before signalling the break line status. This guarantees any pending rx will
+ * be queued to the line discipline before break is simulated on the remote.
+ * Conversely, turning off break_ctl requires signalling the line status change,
+ * then enabling tx.
+ */
+static int fwtty_break_ctl(struct tty_struct *tty, int state)
+{
+	struct fwtty_port *port = tty->driver_data;
+	long ret;
+
+	fwtty_dbg(port, "%d", state);
+
+	if (state == -1) {
+		set_bit(STOP_TX, &port->flags);
+		ret = wait_event_interruptible_timeout(port->wait_tx,
+					       !test_bit(IN_TX, &port->flags),
+					       10);
+		if (ret == 0 || ret == -ERESTARTSYS) {
+			clear_bit(STOP_TX, &port->flags);
+			fwtty_restart_tx(port);
+			return -EINTR;
+		}
+	}
+
+	spin_lock_bh(&port->lock);
+	port->break_ctl = (state == -1);
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+
+	if (state == 0) {
+		spin_lock_bh(&port->lock);
+		dma_fifo_reset(&port->tx_fifo);
+		clear_bit(STOP_TX, &port->flags);
+		spin_unlock_bh(&port->lock);
+	}
+	return 0;
+}
+
+static int fwtty_tiocmget(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+	unsigned tiocm;
+
+	spin_lock_bh(&port->lock);
+	tiocm = (port->mctrl & MCTRL_MASK) | (port->mstatus & ~MCTRL_MASK);
+	spin_unlock_bh(&port->lock);
+
+	fwtty_dbg(port, "%x", tiocm);
+
+	return tiocm;
+}
+
+static int fwtty_tiocmset(struct tty_struct *tty, unsigned set, unsigned clear)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	fwtty_dbg(port, "set: %x clear: %x", set, clear);
+
+	/* TODO: simulate loopback if TIOCM_LOOP set */
+
+	spin_lock_bh(&port->lock);
+	port->mctrl &= ~(clear & MCTRL_MASK & 0xffff);
+	port->mctrl |= set & MCTRL_MASK & 0xffff;
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+	return 0;
+}
+
+static int fwtty_get_icount(struct tty_struct *tty,
+			    struct serial_icounter_struct *icount)
+{
+	struct fwtty_port *port = tty->driver_data;
+	struct stats stats;
+
+	memcpy(&stats, &port->stats, sizeof(stats));
+	if (port->port.console)
+		(*port->fwcon_ops->stats)(&stats, port->con_data);
+
+	icount->cts = port->icount.cts;
+	icount->dsr = port->icount.dsr;
+	icount->rng = port->icount.rng;
+	icount->dcd = port->icount.dcd;
+	icount->rx  = port->icount.rx;
+	icount->tx  = port->icount.tx + stats.xchars;
+	icount->frame   = port->icount.frame;
+	icount->overrun = port->icount.overrun;
+	icount->parity  = port->icount.parity;
+	icount->brk     = port->icount.brk;
+	icount->buf_overrun = port->icount.overrun;
+	return 0;
+}
+
+static void fwtty_proc_show_port(struct seq_file *m, struct fwtty_port *port)
+{
+	struct stats stats;
+
+	memcpy(&stats, &port->stats, sizeof(stats));
+	if (port->port.console)
+		(*port->fwcon_ops->stats)(&stats, port->con_data);
+
+	seq_printf(m, " tx:%d rx:%d", port->icount.tx + stats.xchars,
+		   port->icount.rx);
+	seq_printf(m, " cts:%d dsr:%d rng:%d dcd:%d", port->icount.cts,
+		   port->icount.dsr, port->icount.rng, port->icount.dcd);
+	seq_printf(m, " fe:%d oe:%d pe:%d brk:%d", port->icount.frame,
+		   port->icount.overrun, port->icount.parity, port->icount.brk);
+	seq_printf(m, " dr:%d st:%d err:%d lost:%d", stats.dropped,
+		   stats.tx_stall, stats.fifo_errs, stats.lost);
+	seq_printf(m, " pkts:%d thr:%d wtrmk:%d", stats.sent, stats.throttled,
+		   stats.watermark);
+	seq_printf(m, " addr:%012llx", port->rx_handler.offset);
+
+	if (port->port.console) {
+		seq_printf(m, "\n    ");
+		(*port->fwcon_ops->proc_show)(m, port->con_data);
+	}
+
+	dump_profile(m, &port->stats);
+}
+
+static void fwtty_proc_show_peer(struct seq_file *m, struct fwtty_peer *peer)
+{
+	int generation = peer->generation;
+
+	smp_rmb();
+	seq_printf(m, " %s:", dev_name(&peer->unit->device));
+	seq_printf(m, " node:%04x gen:%d", peer->node_id, generation);
+	seq_printf(m, " sp:%d max:%d guid:%016llx", peer->speed,
+		   peer->max_payload, (unsigned long long) peer->guid);
+
+	if (capable(CAP_SYS_ADMIN)) {
+		seq_printf(m, " mgmt:%012llx",
+			   (unsigned long long) peer->mgmt_addr);
+		seq_printf(m, " addr:%012llx",
+			   (unsigned long long) peer->status_addr);
+	}
+	seq_putc(m, '\n');
+}
+
+static int fwtty_proc_show(struct seq_file *m, void *v)
+{
+	struct fwtty_port *port;
+	struct fw_serial *serial;
+	struct fwtty_peer *peer;
+	int i;
+
+	seq_puts(m, "fwserinfo: 1.0 driver: 1.0\n");
+	for (i = 0; i < MAX_TOTAL_PORTS && (port = fwtty_port_get(i)); ++i) {
+		seq_printf(m, "%2d:", i);
+		if (capable(CAP_SYS_ADMIN))
+			fwtty_proc_show_port(m, port);
+		fwtty_port_put(port);
+		seq_printf(m, "\n");
+	}
+	seq_putc(m, '\n');
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(serial, &fwserial_list, list) {
+		seq_printf(m, "card: %s  guid: %016llx\n",
+			   dev_name(serial->card->device),
+			   (unsigned long long) serial->card->guid);
+		list_for_each_entry_rcu(peer, &serial->peer_list, list)
+			fwtty_proc_show_peer(m, peer);
+	}
+	rcu_read_unlock();
+	return 0;
+}
+
+static int fwtty_proc_open(struct inode *inode, struct file *fp)
+{
+	return single_open(fp, fwtty_proc_show, NULL);
+}
+
+static const struct file_operations fwtty_proc_fops = {
+	.owner =        THIS_MODULE,
+	.open =         fwtty_proc_open,
+	.read =         seq_read,
+	.llseek =       seq_lseek,
+	.release =      single_release,
+};
+
+static const struct tty_port_operations fwtty_port_ops = {
+	.dtr_rts =		fwtty_port_dtr_rts,
+	.carrier_raised =	fwtty_port_carrier_raised,
+	.shutdown =		fwtty_port_shutdown,
+	.activate =		fwtty_port_activate,
+};
+
+static const struct tty_operations fwtty_ops = {
+	.open =			fwtty_open,
+	.close =		fwtty_close,
+	.hangup =		fwtty_hangup,
+	.cleanup =		fwtty_cleanup,
+	.install =		fwtty_install,
+	.write =		fwtty_write,
+	.write_room =		fwtty_write_room,
+	.chars_in_buffer =	fwtty_chars_in_buffer,
+	.send_xchar =           fwtty_send_xchar,
+	.throttle =             fwtty_throttle,
+	.unthrottle =           fwtty_unthrottle,
+	.ioctl =		fwtty_ioctl,
+	.set_termios =		fwtty_set_termios,
+	.break_ctl =		fwtty_break_ctl,
+	.tiocmget =		fwtty_tiocmget,
+	.tiocmset =		fwtty_tiocmset,
+	.get_icount =		fwtty_get_icount,
+	.proc_fops =		&fwtty_proc_fops,
+};
+
+static inline int mgmt_pkt_expected_len(__be16 code)
+{
+	static const struct fwserial_mgmt_pkt pkt;
+
+	switch (be16_to_cpu(code)) {
+	case FWSC_VIRT_CABLE_PLUG:
+		return sizeof(pkt.hdr) + sizeof(pkt.plug_req);
+
+	case FWSC_VIRT_CABLE_PLUG_RSP:  /* | FWSC_RSP_OK */
+		return sizeof(pkt.hdr) + sizeof(pkt.plug_rsp);
+
+
+	case FWSC_VIRT_CABLE_UNPLUG:
+	case FWSC_VIRT_CABLE_UNPLUG_RSP:
+	case FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK:
+	case FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK:
+		return sizeof(pkt.hdr);
+
+	default:
+		return -1;
+	}
+}
+
+static inline void fill_plug_params(struct virt_plug_params *params,
+				    struct fwtty_port *port)
+{
+	u64 status_addr = port->rx_handler.offset;
+	u64 fifo_addr = port->rx_handler.offset + 4;
+	size_t fifo_len = port->rx_handler.length - 4;
+
+	params->status_hi = cpu_to_be32(status_addr >> 32);
+	params->status_lo = cpu_to_be32(status_addr);
+	params->fifo_hi = cpu_to_be32(fifo_addr >> 32);
+	params->fifo_lo = cpu_to_be32(fifo_addr);
+	params->fifo_len = cpu_to_be32(fifo_len);
+}
+
+static inline void fill_plug_req(struct fwserial_mgmt_pkt *pkt,
+				 struct fwtty_port *port)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+	fill_plug_params(&pkt->plug_req, port);
+}
+
+static inline void fill_plug_rsp_ok(struct fwserial_mgmt_pkt *pkt,
+				    struct fwtty_port *port)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+	fill_plug_params(&pkt->plug_rsp, port);
+}
+
+static inline void fill_plug_rsp_nack(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_req(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_rsp_nack(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_rsp_ok(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static void fwserial_virt_plug_complete(struct fwtty_peer *peer,
+					struct virt_plug_params *params)
+{
+	struct fwtty_port *port = peer->port;
+
+	peer->status_addr = be32_to_u64(params->status_hi, params->status_lo);
+	peer->fifo_addr = be32_to_u64(params->fifo_hi, params->fifo_lo);
+	peer->fifo_len = be32_to_cpu(params->fifo_len);
+	peer_set_state(peer, FWPS_ATTACHED);
+
+	/* reconfigure tx_fifo optimally for this peer */
+	spin_lock_bh(&port->lock);
+	port->max_payload = min3(peer->max_payload, peer->fifo_len,
+				 MAX_ASYNC_PAYLOAD);
+	dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload);
+	spin_unlock_bh(&peer->port->lock);
+
+	if (port->port.console && port->fwcon_ops->notify != NULL)
+		(*port->fwcon_ops->notify)(FWCON_NOTIFY_ATTACH, port->con_data);
+
+	fwtty_info(&peer->unit, "peer (guid:%016llx) connected on %s",
+		   (unsigned long long)peer->guid, dev_name(port->device));
+}
+
+static inline int fwserial_send_mgmt_sync(struct fwtty_peer *peer,
+					  struct fwserial_mgmt_pkt *pkt)
+{
+	int generation;
+	int rcode, tries = 5;
+
+	do {
+		generation = peer->generation;
+		smp_rmb();
+
+		rcode = fw_run_transaction(peer->serial->card,
+					   TCODE_WRITE_BLOCK_REQUEST,
+					   peer->node_id,
+					   generation, peer->speed,
+					   peer->mgmt_addr,
+					   pkt, be16_to_cpu(pkt->hdr.len));
+		if (rcode == RCODE_BUSY || rcode == RCODE_SEND_ERROR ||
+		    rcode == RCODE_GENERATION) {
+			fwtty_dbg(&peer->unit, "mgmt write error: %d", rcode);
+			continue;
+		} else
+			break;
+	} while (--tries > 0);
+	return rcode;
+}
+
+/**
+ * fwserial_claim_port - attempt to claim port @ index for peer
+ *
+ * Returns ptr to claimed port or error code (as ERR_PTR())
+ * Can sleep - must be called from process context
+ */
+static struct fwtty_port *fwserial_claim_port(struct fwtty_peer *peer,
+					      int index)
+{
+	struct fwtty_port *port;
+
+	if (index < 0 || index >= num_ports)
+		return ERR_PTR(-EINVAL);
+
+	/* must guarantee that previous port releases have completed */
+	synchronize_rcu();
+
+	port = peer->serial->ports[index];
+	spin_lock_bh(&port->lock);
+	if (!rcu_access_pointer(port->peer))
+		rcu_assign_pointer(port->peer, peer);
+	else
+		port = ERR_PTR(-EBUSY);
+	spin_unlock_bh(&port->lock);
+
+	return port;
+}
+
+/**
+ * fwserial_find_port - find avail port and claim for peer
+ *
+ * Returns ptr to claimed port or NULL if none avail
+ * Can sleep - must be called from process context
+ */
+static struct fwtty_port *fwserial_find_port(struct fwtty_peer *peer)
+{
+	struct fwtty_port **ports = peer->serial->ports;
+	int i;
+
+	/* must guarantee that previous port releases have completed */
+	synchronize_rcu();
+
+	/* TODO: implement optional GUID-to-specific port # matching */
+
+	/* find an unattached port (but not the loopback port, if present) */
+	for (i = 0; i < num_ttys; ++i) {
+		spin_lock_bh(&ports[i]->lock);
+		if (!ports[i]->peer) {
+			/* claim port */
+			rcu_assign_pointer(ports[i]->peer, peer);
+			spin_unlock_bh(&ports[i]->lock);
+			return ports[i];
+		}
+		spin_unlock_bh(&ports[i]->lock);
+	}
+	return NULL;
+}
+
+static void fwserial_release_port(struct fwtty_port *port)
+{
+	/* drop carrier (and all other line status) */
+	fwtty_update_port_status(port, 0);
+
+	spin_lock_bh(&port->lock);
+
+	/* reset dma fifo max transmission size back to S100 */
+	port->max_payload = link_speed_to_max_payload(SCODE_100);
+	dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload);
+
+	rcu_assign_pointer(port->peer, NULL);
+	spin_unlock_bh(&port->lock);
+
+	if (port->port.console && port->fwcon_ops->notify != NULL)
+		(*port->fwcon_ops->notify)(FWCON_NOTIFY_DETACH, port->con_data);
+}
+
+static void fwserial_plug_timeout(unsigned long data)
+{
+	struct fwtty_peer *peer = (struct fwtty_peer *) data;
+	struct fwtty_port *port;
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state != FWPS_PLUG_PENDING) {
+		spin_unlock_bh(&peer->lock);
+		return;
+	}
+
+	port = peer_revert_state(peer);
+	spin_unlock_bh(&peer->lock);
+
+	if (port)
+		fwserial_release_port(port);
+}
+
+/**
+ * fwserial_connect_peer - initiate virtual cable with peer
+ *
+ * Returns 0 if VIRT_CABLE_PLUG request was successfully sent,
+ * otherwise error code.  Must be called from process context.
+ */
+static int fwserial_connect_peer(struct fwtty_peer *peer)
+{
+	struct fwtty_port *port;
+	struct fwserial_mgmt_pkt *pkt;
+	int err, rcode;
+
+	pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+	if (!pkt)
+		return -ENOMEM;
+
+	port = fwserial_find_port(peer);
+	if (!port) {
+		fwtty_err(&peer->unit, "avail ports in use");
+		err = -EBUSY;
+		goto free_pkt;
+	}
+
+	spin_lock_bh(&peer->lock);
+
+	/* only initiate VIRT_CABLE_PLUG if peer is currently not attached */
+	if (peer->state != FWPS_NOT_ATTACHED) {
+		err = -EBUSY;
+		goto release_port;
+	}
+
+	peer->port = port;
+	peer_set_state(peer, FWPS_PLUG_PENDING);
+
+	fill_plug_req(pkt, peer->port);
+
+	setup_timer(&peer->timer, fwserial_plug_timeout, (unsigned long)peer);
+	mod_timer(&peer->timer, jiffies + VIRT_CABLE_PLUG_TIMEOUT);
+	spin_unlock_bh(&peer->lock);
+
+	rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_PLUG_PENDING && rcode != RCODE_COMPLETE) {
+		if (rcode == RCODE_CONFLICT_ERROR)
+			err = -EAGAIN;
+		else
+			err = -EIO;
+		goto cancel_timer;
+	}
+	spin_unlock_bh(&peer->lock);
+
+	kfree(pkt);
+	return 0;
+
+cancel_timer:
+	del_timer(&peer->timer);
+	peer_revert_state(peer);
+release_port:
+	spin_unlock_bh(&peer->lock);
+	fwserial_release_port(port);
+free_pkt:
+	kfree(pkt);
+	return err;
+}
+
+/**
+ * fwserial_close_port -
+ * HUP the tty (if the tty exists) and unregister the tty device.
+ * Only used by the unit driver upon unit removal to disconnect and
+ * cleanup all attached ports
+ *
+ * The port reference is put by fwtty_cleanup (if a reference was
+ * ever taken).
+ */
+static void fwserial_close_port(struct fwtty_port *port)
+{
+	struct tty_struct *tty;
+
+	mutex_lock(&port->port.mutex);
+	tty = tty_port_tty_get(&port->port);
+	if (tty) {
+		tty_vhangup(tty);
+		tty_kref_put(tty);
+	}
+	mutex_unlock(&port->port.mutex);
+
+	tty_unregister_device(fwtty_driver, port->index);
+}
+
+/**
+ * fwserial_lookup - finds first fw_serial associated with card
+ * @card: fw_card to match
+ *
+ * NB: caller must be holding fwserial_list_mutex
+ */
+static struct fw_serial *fwserial_lookup(struct fw_card *card)
+{
+	struct fw_serial *serial;
+
+	list_for_each_entry(serial, &fwserial_list, list) {
+		if (card == serial->card)
+			return serial;
+	}
+
+	return NULL;
+}
+
+/**
+ * __fwserial_lookup_rcu - finds first fw_serial associated with card
+ * @card: fw_card to match
+ *
+ * NB: caller must be inside rcu_read_lock() section
+ */
+static struct fw_serial *__fwserial_lookup_rcu(struct fw_card *card)
+{
+	struct fw_serial *serial;
+
+	list_for_each_entry_rcu(serial, &fwserial_list, list) {
+		if (card == serial->card)
+			return serial;
+	}
+
+	return NULL;
+}
+
+/**
+ * __fwserial_peer_by_node_id - finds a peer matching the given generation + id
+ *
+ * If a matching peer could not be found for the specified generation/node id,
+ * this could be because:
+ * a) the generation has changed and one of the nodes hasn't updated yet
+ * b) the remote node has created its remote unit device before this
+ *    local node has created its corresponding remote unit device
+ * In either case, the remote node should retry
+ *
+ * Note: caller must be in rcu_read_lock() section
+ */
+static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card,
+						     int generation, int id)
+{
+	struct fw_serial *serial;
+	struct fwtty_peer *peer;
+
+	serial = __fwserial_lookup_rcu(card);
+	if (!serial) {
+		/*
+		 * Something is very wrong - there should be a matching
+		 * fw_serial structure for every fw_card. Maybe the remote node
+		 * has created its remote unit device before this driver has
+		 * been probed for any unit devices...
+		 */
+		fwtty_err(card, "unknown card (guid %016llx)",
+			  (unsigned long long) card->guid);
+		return NULL;
+	}
+
+	list_for_each_entry_rcu(peer, &serial->peer_list, list) {
+		int g = peer->generation;
+		smp_rmb();
+		if (generation == g && id == peer->node_id)
+			return peer;
+	}
+
+	return NULL;
+}
+
+#ifdef DEBUG
+static void __dump_peer_list(struct fw_card *card)
+{
+	struct fw_serial *serial;
+	struct fwtty_peer *peer;
+
+	serial = __fwserial_lookup_rcu(card);
+	if (!serial)
+		return;
+
+	list_for_each_entry_rcu(peer, &serial->peer_list, list) {
+		int g = peer->generation;
+		smp_rmb();
+		fwtty_dbg(card, "peer(%d:%x) guid: %016llx\n", g,
+			  peer->node_id, (unsigned long long) peer->guid);
+	}
+}
+#else
+#define __dump_peer_list(s)
+#endif
+
+static void fwserial_auto_connect(struct work_struct *work)
+{
+	struct fwtty_peer *peer = to_peer(to_delayed_work(work), connect);
+	int err;
+
+	err = fwserial_connect_peer(peer);
+	if (err == -EAGAIN && ++peer->connect_retries < MAX_CONNECT_RETRIES)
+		schedule_delayed_work(&peer->connect, CONNECT_RETRY_DELAY);
+}
+
+/**
+ * fwserial_add_peer - add a newly probed 'serial' unit device as a 'peer'
+ * @serial: aggregate representing the specific fw_card to add the peer to
+ * @unit: 'peer' to create and add to peer_list of serial
+ *
+ * Adds a 'peer' (ie, a local or remote 'serial' unit device) to the list of
+ * peers for a specific fw_card. Optionally, auto-attach this peer to an
+ * available tty port. This function is called either directly or indirectly
+ * as a result of a 'serial' unit device being created & probed.
+ *
+ * Note: this function is serialized with fwserial_remove_peer() by the
+ * fwserial_list_mutex held in fwserial_probe().
+ *
+ * A 1:1 correspondence between an fw_unit and an fwtty_peer is maintained
+ * via the dev_set_drvdata() for the device of the fw_unit.
+ */
+static int fwserial_add_peer(struct fw_serial *serial, struct fw_unit *unit)
+{
+	struct device *dev = &unit->device;
+	struct fw_device  *parent = fw_parent_device(unit);
+	struct fwtty_peer *peer;
+	struct fw_csr_iterator ci;
+	int key, val;
+	int generation;
+
+	peer = kzalloc(sizeof(*peer), GFP_KERNEL);
+	if (!peer)
+		return -ENOMEM;
+
+	peer_set_state(peer, FWPS_NOT_ATTACHED);
+
+	dev_set_drvdata(dev, peer);
+	peer->unit = unit;
+	peer->guid = (u64)parent->config_rom[3] << 32 | parent->config_rom[4];
+	peer->speed = parent->max_speed;
+	peer->max_payload = min(device_max_receive(parent),
+				link_speed_to_max_payload(peer->speed));
+
+	generation = parent->generation;
+	smp_rmb();
+	peer->node_id = parent->node_id;
+	smp_wmb();
+	peer->generation = generation;
+
+	/* retrieve the mgmt bus addr from the unit directory */
+	fw_csr_iterator_init(&ci, unit->directory);
+	while (fw_csr_iterator_next(&ci, &key, &val)) {
+		if (key == (CSR_OFFSET | CSR_DEPENDENT_INFO)) {
+			peer->mgmt_addr = CSR_REGISTER_BASE + 4 * val;
+			break;
+		}
+	}
+	if (peer->mgmt_addr == 0ULL) {
+		/*
+		 * No mgmt address effectively disables VIRT_CABLE_PLUG -
+		 * this peer will not be able to attach to a remote
+		 */
+		peer_set_state(peer, FWPS_NO_MGMT_ADDR);
+	}
+
+	spin_lock_init(&peer->lock);
+	peer->port = NULL;
+
+	init_timer(&peer->timer);
+	INIT_WORK(&peer->work, NULL);
+	INIT_DELAYED_WORK(&peer->connect, fwserial_auto_connect);
+
+	/* associate peer with specific fw_card */
+	peer->serial = serial;
+	list_add_rcu(&peer->list, &serial->peer_list);
+
+	fwtty_info(&peer->unit, "peer added (guid:%016llx)",
+		   (unsigned long long)peer->guid);
+
+	/* identify the local unit & virt cable to loopback port */
+	if (parent->is_local) {
+		serial->self = peer;
+		if (create_loop_dev) {
+			struct fwtty_port *port;
+			port = fwserial_claim_port(peer, num_ttys);
+			if (!IS_ERR(port)) {
+				struct virt_plug_params params;
+
+				spin_lock_bh(&peer->lock);
+				peer->port = port;
+				fill_plug_params(&params, port);
+				fwserial_virt_plug_complete(peer, &params);
+				spin_unlock_bh(&peer->lock);
+
+				fwtty_write_port_status(port);
+			}
+		}
+
+	} else if (auto_connect) {
+		/* auto-attach to remote units only (if policy allows) */
+		schedule_delayed_work(&peer->connect, 1);
+	}
+
+	return 0;
+}
+
+/**
+ * fwserial_remove_peer - remove a 'serial' unit device as a 'peer'
+ *
+ * Remove a 'peer' from its list of peers. This function is only
+ * called by fwserial_remove() on bus removal of the unit device.
+ *
+ * Note: this function is serialized with fwserial_add_peer() by the
+ * fwserial_list_mutex held in fwserial_remove().
+ */
+static void fwserial_remove_peer(struct fwtty_peer *peer)
+{
+	struct fwtty_port *port;
+
+	spin_lock_bh(&peer->lock);
+	peer_set_state(peer, FWPS_GONE);
+	spin_unlock_bh(&peer->lock);
+
+	cancel_delayed_work_sync(&peer->connect);
+	cancel_work_sync(&peer->work);
+
+	spin_lock_bh(&peer->lock);
+	/* if this unit is the local unit, clear link */
+	if (peer == peer->serial->self)
+		peer->serial->self = NULL;
+
+	/* cancel the request timeout timer (if running) */
+	del_timer(&peer->timer);
+
+	port = peer->port;
+	peer->port = NULL;
+
+	list_del_rcu(&peer->list);
+
+	fwtty_info(&peer->unit, "peer removed (guid:%016llx)",
+		   (unsigned long long)peer->guid);
+
+	spin_unlock_bh(&peer->lock);
+
+	if (port)
+		fwserial_release_port(port);
+
+	synchronize_rcu();
+	kfree(peer);
+}
+
+/**
+ * create_loop_device - create a loopback tty device
+ * @tty_driver: tty_driver to own loopback device
+ * @prototype: ptr to already-assigned 'prototype' tty port
+ * @index: index to associate this device with the tty port
+ * @parent: device to child to
+ *
+ * HACK - this is basically tty_port_register_device() with an
+ * alternate naming scheme. Suggest tty_port_register_named_device()
+ * helper api.
+ *
+ * Creates a loopback tty device named 'fwloop<n>' which is attached to
+ * the local unit in fwserial_add_peer(). Note that <n> in the device
+ * name advances in increments of port allocation blocks, ie., for port
+ * indices 0..3, the device name will be 'fwloop0'; for 4..7, 'fwloop1',
+ * and so on.
+ *
+ * Only one loopback device should be created per fw_card.
+ */
+static void release_loop_device(struct device *dev)
+{
+	kfree(dev);
+}
+
+static struct device *create_loop_device(struct tty_driver *driver,
+					 struct fwtty_port *prototype,
+					 struct fwtty_port *port,
+					 struct device *parent)
+{
+	char name[64];
+	int index = port->index;
+	dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
+	struct device *dev = NULL;
+	int err;
+
+	if (index >= fwtty_driver->num)
+		return ERR_PTR(-EINVAL);
+
+	snprintf(name, 64, "%s%d", loop_dev_name, index / num_ports);
+
+	tty_port_link_device(&port->port, driver, index);
+
+	cdev_init(&driver->cdevs[index], driver->cdevs[prototype->index].ops);
+	driver->cdevs[index].owner = driver->owner;
+	err = cdev_add(&driver->cdevs[index], devt, 1);
+	if (err)
+		return ERR_PTR(err);
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		cdev_del(&driver->cdevs[index]);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	dev->devt = devt;
+	dev->class = prototype->device->class;
+	dev->parent = parent;
+	dev->release = release_loop_device;
+	dev_set_name(dev, "%s", name);
+	dev->groups = NULL;
+	dev_set_drvdata(dev, NULL);
+
+	err = device_register(dev);
+	if (err) {
+		put_device(dev);
+		cdev_del(&driver->cdevs[index]);
+		return ERR_PTR(err);
+	}
+
+	return dev;
+}
+
+/**
+ * fwserial_create - init everything to create TTYs for a specific fw_card
+ * @unit: fw_unit for first 'serial' unit device probed for this fw_card
+ *
+ * This function inits the aggregate structure (an fw_serial instance)
+ * used to manage the TTY ports registered by a specific fw_card. Also, the
+ * unit device is added as the first 'peer'.
+ *
+ * This unit device may represent a local unit device (as specified by the
+ * config ROM unit directory) or it may represent a remote unit device
+ * (as specified by the reading of the remote node's config ROM).
+ *
+ * Returns 0 to indicate "ownership" of the unit device, or a negative errno
+ * value to indicate which error.
+ */
+static int fwserial_create(struct fw_unit *unit)
+{
+	struct fw_device *parent = fw_parent_device(unit);
+	struct fw_card *card = parent->card;
+	struct fw_serial *serial;
+	struct fwtty_port *port;
+	struct device *tty_dev;
+	int i, j;
+	int err;
+
+	serial = kzalloc(sizeof(*serial), GFP_KERNEL);
+	if (!serial)
+		return -ENOMEM;
+
+	kref_init(&serial->kref);
+	serial->card = card;
+	INIT_LIST_HEAD(&serial->peer_list);
+
+	for (i = 0; i < num_ports; ++i) {
+		port = kzalloc(sizeof(*port), GFP_KERNEL);
+		if (!port) {
+			err = -ENOMEM;
+			goto free_ports;
+		}
+		tty_port_init(&port->port);
+		port->index = FWTTY_INVALID_INDEX;
+		port->port.ops = &fwtty_port_ops;
+		port->serial = serial;
+
+		spin_lock_init(&port->lock);
+		INIT_DELAYED_WORK(&port->drain, fwtty_drain_tx);
+		INIT_DELAYED_WORK(&port->emit_breaks, fwtty_emit_breaks);
+		INIT_WORK(&port->hangup, fwtty_do_hangup);
+		INIT_WORK(&port->push, fwtty_pushrx);
+		INIT_LIST_HEAD(&port->buf_list);
+		init_waitqueue_head(&port->wait_tx);
+		port->max_payload = link_speed_to_max_payload(SCODE_100);
+		dma_fifo_init(&port->tx_fifo);
+
+		rcu_assign_pointer(port->peer, NULL);
+		serial->ports[i] = port;
+
+		/* get unique bus addr region for port's status & recv fifo */
+		port->rx_handler.length = FWTTY_PORT_RXFIFO_LEN + 4;
+		port->rx_handler.address_callback = fwtty_port_handler;
+		port->rx_handler.callback_data = port;
+		/*
+		 * XXX: use custom memory region above cpu physical memory addrs
+		 * this will ease porting to 64-bit firewire adapters
+		 */
+		err = fw_core_add_address_handler(&port->rx_handler,
+						  &fw_high_memory_region);
+		if (err) {
+			kfree(port);
+			goto free_ports;
+		}
+	}
+	/* preserve i for error cleanup */
+
+	err = fwtty_ports_add(serial);
+	if (err) {
+		fwtty_err(&unit, "no space in port table");
+		goto free_ports;
+	}
+
+	for (j = 0; j < num_ttys; ++j) {
+		tty_dev = tty_port_register_device(&serial->ports[j]->port,
+						   fwtty_driver,
+						   serial->ports[j]->index,
+						   card->device);
+		if (IS_ERR(tty_dev)) {
+			err = PTR_ERR(tty_dev);
+			fwtty_err(&unit, "register tty device error (%d)", err);
+			goto unregister_ttys;
+		}
+
+		serial->ports[j]->device = tty_dev;
+	}
+	/* preserve j for error cleanup */
+
+	if (create_loop_dev) {
+		struct device *loop_dev;
+
+		loop_dev = create_loop_device(fwtty_driver,
+					      serial->ports[0],
+					      serial->ports[num_ttys],
+					      card->device);
+		if (IS_ERR(loop_dev)) {
+			err = PTR_ERR(loop_dev);
+			fwtty_err(&unit, "create loop device failed (%d)", err);
+			goto unregister_ttys;
+		}
+		serial->ports[num_ttys]->device = loop_dev;
+		serial->ports[num_ttys]->loopback = true;
+	}
+
+	list_add_rcu(&serial->list, &fwserial_list);
+
+	fwtty_notice(&unit, "TTY over FireWire on device %s (guid %016llx)",
+		     dev_name(card->device), (unsigned long long) card->guid);
+
+	err = fwserial_add_peer(serial, unit);
+	if (!err)
+		return 0;
+
+	fwtty_err(&unit, "unable to add peer unit device (%d)", err);
+
+	/* fall-through to error processing */
+	list_del_rcu(&serial->list);
+unregister_ttys:
+	for (--j; j >= 0; --j)
+		tty_unregister_device(fwtty_driver, serial->ports[j]->index);
+	kref_put(&serial->kref, fwserial_destroy);
+	return err;
+
+free_ports:
+	for (--i; i >= 0; --i)
+		kfree(serial->ports[i]);
+	kfree(serial);
+	return err;
+}
+
+/**
+ * fwserial_probe: bus probe function for firewire 'serial' unit devices
+ *
+ * A 'serial' unit device is created and probed as a result of:
+ * - declaring a ieee1394 bus id table for 'devices' matching a fabricated
+ *   'serial' unit specifier id
+ * - adding a unit directory to the config ROM(s) for a 'serial' unit
+ *
+ * The firewire core registers unit devices by enumerating unit directories
+ * of a node's config ROM after reading the config ROM when a new node is
+ * added to the bus topology after a bus reset.
+ *
+ * The practical implications of this are:
+ * - this probe is called for both local and remote nodes that have a 'serial'
+ *   unit directory in their config ROM (that matches the specifiers in
+ *   fwserial_id_table).
+ * - no specific order is enforced for local vs. remote unit devices
+ *
+ * This unit driver copes with the lack of specific order in the same way the
+ * firewire net driver does -- each probe, for either a local or remote unit
+ * device, is treated as a 'peer' (has a struct fwtty_peer instance) and the
+ * first peer created for a given fw_card (tracked by the global fwserial_list)
+ * creates the underlying TTYs (aggregated in a fw_serial instance).
+ *
+ * NB: an early attempt to differentiate local & remote unit devices by creating
+ *     peers only for remote units and fw_serial instances (with their
+ *     associated TTY devices) only for local units was discarded. Managing
+ *     the peer lifetimes on device removal proved too complicated.
+ *
+ * fwserial_probe/fwserial_remove are effectively serialized by the
+ * fwserial_list_mutex. This is necessary because the addition of the first peer
+ * for a given fw_card will trigger the creation of the fw_serial for that
+ * fw_card, which must not simultaneously contend with the removal of the
+ * last peer for a given fw_card triggering the destruction of the same
+ * fw_serial for the same fw_card.
+ */
+static int fwserial_probe(struct device *dev)
+{
+	struct fw_unit *unit = fw_unit(dev);
+	struct fw_serial *serial;
+	int err;
+
+	mutex_lock(&fwserial_list_mutex);
+	serial = fwserial_lookup(fw_parent_device(unit)->card);
+	if (!serial)
+		err = fwserial_create(unit);
+	else
+		err = fwserial_add_peer(serial, unit);
+	mutex_unlock(&fwserial_list_mutex);
+	return err;
+}
+
+/**
+ * fwserial_remove: bus removal function for firewire 'serial' unit devices
+ *
+ * The corresponding 'peer' for this unit device is removed from the list of
+ * peers for the associated fw_serial (which has a 1:1 correspondence with a
+ * specific fw_card). If this is the last peer being removed, then trigger
+ * the destruction of the underlying TTYs.
+ */
+static int fwserial_remove(struct device *dev)
+{
+	struct fwtty_peer *peer = dev_get_drvdata(dev);
+	struct fw_serial *serial = peer->serial;
+	int i;
+
+	mutex_lock(&fwserial_list_mutex);
+	fwserial_remove_peer(peer);
+
+	if (list_empty(&serial->peer_list)) {
+		/* unlink from the fwserial_list here */
+		list_del_rcu(&serial->list);
+
+		for (i = 0; i < num_ports; ++i)
+			fwserial_close_port(serial->ports[i]);
+		kref_put(&serial->kref, fwserial_destroy);
+	}
+	mutex_unlock(&fwserial_list_mutex);
+
+	return 0;
+}
+
+/**
+ * fwserial_update: bus update function for 'firewire' serial unit devices
+ *
+ * Updates the new node_id and bus generation for this peer. Note that locking
+ * is unnecessary; but careful memory barrier usage is important to enforce the
+ * load and store order of generation & node_id.
+ *
+ * The fw-core orders the write of node_id before generation in the parent
+ * fw_device to ensure that a stale node_id cannot be used with a current
+ * bus generation. So the generation value must be read before the node_id.
+ *
+ * In turn, this orders the write of node_id before generation in the peer to
+ * also ensure a stale node_id cannot be used with a current bus generation.
+ */
+static void fwserial_update(struct fw_unit *unit)
+{
+	struct fw_device *parent = fw_parent_device(unit);
+	struct fwtty_peer *peer = dev_get_drvdata(&unit->device);
+	int generation;
+
+	generation = parent->generation;
+	smp_rmb();
+	peer->node_id = parent->node_id;
+	smp_wmb();
+	peer->generation = generation;
+}
+
+static const struct ieee1394_device_id fwserial_id_table[] = {
+	{
+		.match_flags  = IEEE1394_MATCH_SPECIFIER_ID |
+				IEEE1394_MATCH_VERSION,
+		.specifier_id = LINUX_VENDOR_ID,
+		.version      = FWSERIAL_VERSION,
+	},
+	{ }
+};
+
+static struct fw_driver fwserial_driver = {
+	.driver = {
+		.owner  = THIS_MODULE,
+		.name   = KBUILD_MODNAME,
+		.bus    = &fw_bus_type,
+		.probe  = fwserial_probe,
+		.remove = fwserial_remove,
+	},
+	.update   = fwserial_update,
+	.id_table = fwserial_id_table,
+};
+
+#define FW_UNIT_SPECIFIER(id)	((CSR_SPECIFIER_ID << 24) | (id))
+#define FW_UNIT_VERSION(ver)	((CSR_VERSION << 24) | (ver))
+#define FW_UNIT_ADDRESS(ofs)	(((CSR_OFFSET | CSR_DEPENDENT_INFO) << 24)  \
+				 | (((ofs) - CSR_REGISTER_BASE) >> 2))
+/* XXX: config ROM definitons could be improved with semi-automated offset
+ * and length calculation
+ */
+#define FW_ROM_DESCRIPTOR(ofs)	(((CSR_LEAF | CSR_DESCRIPTOR) << 24) | (ofs))
+
+struct fwserial_unit_directory_data {
+	u16	crc;
+	u16	len;
+	u32	unit_specifier;
+	u32	unit_sw_version;
+	u32	unit_addr_offset;
+	u32	desc1_ofs;
+	u16	desc1_crc;
+	u16	desc1_len;
+	u32	desc1_data[5];
+} __packed;
+
+static struct fwserial_unit_directory_data fwserial_unit_directory_data = {
+	.len = 4,
+	.unit_specifier = FW_UNIT_SPECIFIER(LINUX_VENDOR_ID),
+	.unit_sw_version = FW_UNIT_VERSION(FWSERIAL_VERSION),
+	.desc1_ofs = FW_ROM_DESCRIPTOR(1),
+	.desc1_len = 5,
+	.desc1_data = {
+		0x00000000,			/*   type = text            */
+		0x00000000,			/*   enc = ASCII, lang EN   */
+		0x4c696e75,			/* 'Linux TTY'              */
+		0x78205454,
+		0x59000000,
+	},
+};
+
+static struct fw_descriptor fwserial_unit_directory = {
+	.length = sizeof(fwserial_unit_directory_data) / sizeof(u32),
+	.key    = (CSR_DIRECTORY | CSR_UNIT) << 24,
+	.data   = (u32 *)&fwserial_unit_directory_data,
+};
+
+/*
+ * The management address is in the unit space region but above other known
+ * address users (to keep wild writes from causing havoc)
+ */
+const struct fw_address_region fwserial_mgmt_addr_region = {
+	.start = CSR_REGISTER_BASE + 0x1e0000ULL,
+	.end = 0x1000000000000ULL,
+};
+
+static struct fw_address_handler fwserial_mgmt_addr_handler;
+
+/**
+ * fwserial_handle_plug_req - handle VIRT_CABLE_PLUG request work
+ * @work: ptr to peer->work
+ *
+ * Attempts to complete the VIRT_CABLE_PLUG handshake sequence for this peer.
+ *
+ * This checks for a collided request-- ie, that a VIRT_CABLE_PLUG request was
+ * already sent to this peer. If so, the collision is resolved by comparing
+ * guid values; the loser sends the plug response.
+ *
+ * Note: if an error prevents a response, don't do anything -- the
+ * remote will timeout its request.
+ */
+static void fwserial_handle_plug_req(struct work_struct *work)
+{
+	struct fwtty_peer *peer = to_peer(work, work);
+	struct virt_plug_params *plug_req = &peer->work_params.plug_req;
+	struct fwtty_port *port;
+	struct fwserial_mgmt_pkt *pkt;
+	int rcode;
+
+	pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+	if (!pkt)
+		return;
+
+	port = fwserial_find_port(peer);
+
+	spin_lock_bh(&peer->lock);
+
+	switch (peer->state) {
+	case FWPS_NOT_ATTACHED:
+		if (!port) {
+			fwtty_err(&peer->unit, "no more ports avail");
+			fill_plug_rsp_nack(pkt);
+		} else {
+			peer->port = port;
+			fill_plug_rsp_ok(pkt, peer->port);
+			peer_set_state(peer, FWPS_PLUG_RESPONDING);
+			/* don't release claimed port */
+			port = NULL;
+		}
+		break;
+
+	case FWPS_PLUG_PENDING:
+		if (peer->serial->card->guid > peer->guid)
+			goto cleanup;
+
+		/* We lost - hijack the already-claimed port and send ok */
+		del_timer(&peer->timer);
+		fill_plug_rsp_ok(pkt, peer->port);
+		peer_set_state(peer, FWPS_PLUG_RESPONDING);
+		break;
+
+	default:
+		fill_plug_rsp_nack(pkt);
+	}
+
+	spin_unlock_bh(&peer->lock);
+	if (port)
+		fwserial_release_port(port);
+
+	rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_PLUG_RESPONDING) {
+		if (rcode == RCODE_COMPLETE) {
+			struct fwtty_port *tmp = peer->port;
+
+			fwserial_virt_plug_complete(peer, plug_req);
+			spin_unlock_bh(&peer->lock);
+
+			fwtty_write_port_status(tmp);
+			spin_lock_bh(&peer->lock);
+		} else {
+			fwtty_err(&peer->unit, "PLUG_RSP error (%d)", rcode);
+			port = peer_revert_state(peer);
+		}
+	}
+cleanup:
+	spin_unlock_bh(&peer->lock);
+	if (port)
+		fwserial_release_port(port);
+	kfree(pkt);
+	return;
+}
+
+static void fwserial_handle_unplug_req(struct work_struct *work)
+{
+	struct fwtty_peer *peer = to_peer(work, work);
+	struct fwtty_port *port = NULL;
+	struct fwserial_mgmt_pkt *pkt;
+	int rcode;
+
+	pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+	if (!pkt)
+		return;
+
+	spin_lock_bh(&peer->lock);
+
+	switch (peer->state) {
+	case FWPS_ATTACHED:
+		fill_unplug_rsp_ok(pkt);
+		peer_set_state(peer, FWPS_UNPLUG_RESPONDING);
+		break;
+
+	case FWPS_UNPLUG_PENDING:
+		if (peer->serial->card->guid > peer->guid)
+			goto cleanup;
+
+		/* We lost - send unplug rsp */
+		del_timer(&peer->timer);
+		fill_unplug_rsp_ok(pkt);
+		peer_set_state(peer, FWPS_UNPLUG_RESPONDING);
+		break;
+
+	default:
+		fill_unplug_rsp_nack(pkt);
+	}
+
+	spin_unlock_bh(&peer->lock);
+
+	rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_UNPLUG_RESPONDING) {
+		if (rcode == RCODE_COMPLETE)
+			port = peer_revert_state(peer);
+		else
+			fwtty_err(&peer->unit, "UNPLUG_RSP error (%d)", rcode);
+	}
+cleanup:
+	spin_unlock_bh(&peer->lock);
+	if (port)
+		fwserial_release_port(port);
+	kfree(pkt);
+	return;
+}
+
+static int fwserial_parse_mgmt_write(struct fwtty_peer *peer,
+				     struct fwserial_mgmt_pkt *pkt,
+				     unsigned long long addr,
+				     size_t len)
+{
+	struct fwtty_port *port = NULL;
+	int rcode;
+
+	if (addr != fwserial_mgmt_addr_handler.offset || len < sizeof(pkt->hdr))
+		return RCODE_ADDRESS_ERROR;
+
+	if (len != be16_to_cpu(pkt->hdr.len) ||
+	    len != mgmt_pkt_expected_len(pkt->hdr.code))
+		return RCODE_DATA_ERROR;
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_GONE) {
+		/*
+		 * This should never happen - it would mean that the
+		 * remote unit that just wrote this transaction was
+		 * already removed from the bus -- and the removal was
+		 * processed before we rec'd this transaction
+		 */
+		fwtty_err(&peer->unit, "peer already removed");
+		spin_unlock_bh(&peer->lock);
+		return RCODE_ADDRESS_ERROR;
+	}
+
+	rcode = RCODE_COMPLETE;
+
+	fwtty_dbg(&peer->unit, "mgmt: hdr.code: %04hx", pkt->hdr.code);
+
+	switch (be16_to_cpu(pkt->hdr.code) & FWSC_CODE_MASK) {
+	case FWSC_VIRT_CABLE_PLUG:
+		if (work_pending(&peer->work)) {
+			fwtty_err(&peer->unit, "plug req: busy");
+			rcode = RCODE_CONFLICT_ERROR;
+
+		} else {
+			peer->work_params.plug_req = pkt->plug_req;
+			PREPARE_WORK(&peer->work, fwserial_handle_plug_req);
+			queue_work(system_unbound_wq, &peer->work);
+		}
+		break;
+
+	case FWSC_VIRT_CABLE_PLUG_RSP:
+		if (peer->state != FWPS_PLUG_PENDING) {
+			rcode = RCODE_CONFLICT_ERROR;
+
+		} else if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK) {
+			fwtty_notice(&peer->unit, "NACK plug rsp");
+			port = peer_revert_state(peer);
+
+		} else {
+			struct fwtty_port *tmp = peer->port;
+
+			fwserial_virt_plug_complete(peer, &pkt->plug_rsp);
+			spin_unlock_bh(&peer->lock);
+
+			fwtty_write_port_status(tmp);
+			spin_lock_bh(&peer->lock);
+		}
+		break;
+
+	case FWSC_VIRT_CABLE_UNPLUG:
+		if (work_pending(&peer->work)) {
+			fwtty_err(&peer->unit, "unplug req: busy");
+			rcode = RCODE_CONFLICT_ERROR;
+		} else {
+			PREPARE_WORK(&peer->work, fwserial_handle_unplug_req);
+			queue_work(system_unbound_wq, &peer->work);
+		}
+		break;
+
+	case FWSC_VIRT_CABLE_UNPLUG_RSP:
+		if (peer->state != FWPS_UNPLUG_PENDING)
+			rcode = RCODE_CONFLICT_ERROR;
+		else {
+			if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK)
+				fwtty_notice(&peer->unit, "NACK unplug?");
+			port = peer_revert_state(peer);
+		}
+		break;
+
+	default:
+		fwtty_err(&peer->unit, "unknown mgmt code %d",
+			  be16_to_cpu(pkt->hdr.code));
+		rcode = RCODE_DATA_ERROR;
+	}
+	spin_unlock_bh(&peer->lock);
+
+	if (port)
+		fwserial_release_port(port);
+
+	return rcode;
+}
+
+/**
+ * fwserial_mgmt_handler: bus address handler for mgmt requests
+ * @parameters: fw_address_callback_t as specified by firewire core interface
+ *
+ * This handler is responsible for handling virtual cable requests from remotes
+ * for all cards.
+ */
+static void fwserial_mgmt_handler(struct fw_card *card,
+				  struct fw_request *request,
+				  int tcode, int destination, int source,
+				  int generation,
+				  unsigned long long addr,
+				  void *data, size_t len,
+				  void *callback_data)
+{
+	struct fwserial_mgmt_pkt *pkt = data;
+	struct fwtty_peer *peer;
+	int rcode;
+
+	rcu_read_lock();
+	peer = __fwserial_peer_by_node_id(card, generation, source);
+	if (!peer) {
+		fwtty_dbg(card, "peer(%d:%x) not found", generation, source);
+		__dump_peer_list(card);
+		rcode = RCODE_CONFLICT_ERROR;
+
+	} else {
+		switch (tcode) {
+		case TCODE_WRITE_BLOCK_REQUEST:
+			rcode = fwserial_parse_mgmt_write(peer, pkt, addr, len);
+			break;
+
+		default:
+			rcode = RCODE_TYPE_ERROR;
+		}
+	}
+
+	rcu_read_unlock();
+	fw_send_response(card, request, rcode);
+}
+
+static int __init fwserial_init(void)
+{
+	int err, num_loops = !!(create_loop_dev);
+
+	/* num_ttys/num_ports must not be set above the static alloc avail */
+	if (num_ttys + num_loops > MAX_CARD_PORTS)
+		num_ttys = MAX_CARD_PORTS - num_loops;
+	num_ports = num_ttys + num_loops;
+
+	fwtty_driver = alloc_tty_driver(MAX_TOTAL_PORTS);
+	if (!fwtty_driver) {
+		err = -ENOMEM;
+		return err;
+	}
+
+	fwtty_driver->driver_name	= KBUILD_MODNAME;
+	fwtty_driver->name		= tty_dev_name;
+	fwtty_driver->major		= 0;
+	fwtty_driver->minor_start	= 0;
+	fwtty_driver->type		= TTY_DRIVER_TYPE_SERIAL;
+	fwtty_driver->subtype		= SERIAL_TYPE_NORMAL;
+	fwtty_driver->flags		= TTY_DRIVER_REAL_RAW |
+						TTY_DRIVER_DYNAMIC_DEV;
+
+	fwtty_driver->init_termios	    = tty_std_termios;
+	fwtty_driver->init_termios.c_cflag  |= CLOCAL;
+	tty_set_operations(fwtty_driver, &fwtty_ops);
+
+	err = tty_register_driver(fwtty_driver);
+	if (err) {
+		driver_err("register tty driver failed (%d)", err);
+		goto put_tty;
+	}
+
+	fwtty_txn_cache = kmem_cache_create("fwtty_txn_cache",
+					    sizeof(struct fwtty_transaction),
+					    0, 0, fwtty_txn_constructor);
+	if (!fwtty_txn_cache) {
+		err = -ENOMEM;
+		goto unregister_driver;
+	}
+
+	/*
+	 * Ideally, this address handler would be registered per local node
+	 * (rather than the same handler for all local nodes). However,
+	 * since the firewire core requires the config rom descriptor *before*
+	 * the local unit device(s) are created, a single management handler
+	 * must suffice for all local serial units.
+	 */
+	fwserial_mgmt_addr_handler.length = sizeof(struct fwserial_mgmt_pkt);
+	fwserial_mgmt_addr_handler.address_callback = fwserial_mgmt_handler;
+
+	err = fw_core_add_address_handler(&fwserial_mgmt_addr_handler,
+					  &fwserial_mgmt_addr_region);
+	if (err) {
+		driver_err("add management handler failed (%d)", err);
+		goto destroy_cache;
+	}
+
+	fwserial_unit_directory_data.unit_addr_offset =
+		FW_UNIT_ADDRESS(fwserial_mgmt_addr_handler.offset);
+	err = fw_core_add_descriptor(&fwserial_unit_directory);
+	if (err) {
+		driver_err("add unit descriptor failed (%d)", err);
+		goto remove_handler;
+	}
+
+	err = driver_register(&fwserial_driver.driver);
+	if (err) {
+		driver_err("register fwserial driver failed (%d)", err);
+		goto remove_descriptor;
+	}
+
+	return 0;
+
+remove_descriptor:
+	fw_core_remove_descriptor(&fwserial_unit_directory);
+remove_handler:
+	fw_core_remove_address_handler(&fwserial_mgmt_addr_handler);
+destroy_cache:
+	kmem_cache_destroy(fwtty_txn_cache);
+unregister_driver:
+	tty_unregister_driver(fwtty_driver);
+put_tty:
+	put_tty_driver(fwtty_driver);
+	return err;
+}
+
+static void __exit fwserial_exit(void)
+{
+	driver_unregister(&fwserial_driver.driver);
+	fw_core_remove_descriptor(&fwserial_unit_directory);
+	fw_core_remove_address_handler(&fwserial_mgmt_addr_handler);
+	kmem_cache_destroy(fwtty_txn_cache);
+	tty_unregister_driver(fwtty_driver);
+	put_tty_driver(fwtty_driver);
+}
+
+module_init(fwserial_init);
+module_exit(fwserial_exit);
+
+MODULE_AUTHOR("Peter Hurley (peter@hurleysoftware.com)");
+MODULE_DESCRIPTION("FireWire Serial TTY Driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ieee1394, fwserial_id_table);
+MODULE_PARM_DESC(ttys, "Number of ttys to create for each local firewire node");
+MODULE_PARM_DESC(auto, "Auto-connect a tty to each firewire node discovered");
+MODULE_PARM_DESC(loop, "Create a loopback device, fwloop<n>, with ttys");
+MODULE_PARM_DESC(limit_bw, "Limit bandwidth utilization to 20%.");
diff --git a/drivers/staging/fwserial/fwserial.h b/drivers/staging/fwserial/fwserial.h
new file mode 100644
index 0000000..8b572ed
--- /dev/null
+++ b/drivers/staging/fwserial/fwserial.h
@@ -0,0 +1,387 @@
+#ifndef _FIREWIRE_FWSERIAL_H
+#define _FIREWIRE_FWSERIAL_H
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/list.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/spinlock.h>
+#include <linux/rcupdate.h>
+#include <linux/mutex.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+
+#include "dma_fifo.h"
+
+#ifdef FWTTY_PROFILING
+#define DISTRIBUTION_MAX_SIZE     8192
+#define DISTRIBUTION_MAX_INDEX    (ilog2(DISTRIBUTION_MAX_SIZE) + 1)
+static inline void profile_size_distrib(unsigned stat[], unsigned val)
+{
+	int n = (val) ? min(ilog2(val) + 1, DISTRIBUTION_MAX_INDEX) : 0;
+	++stat[n];
+}
+#else
+#define DISTRIBUTION_MAX_INDEX    0
+#define profile_size_distrib(st, n)
+#endif
+
+/* Parameters for both VIRT_CABLE_PLUG & VIRT_CABLE_PLUG_RSP mgmt codes */
+struct virt_plug_params {
+	__be32  status_hi;
+	__be32  status_lo;
+	__be32	fifo_hi;
+	__be32	fifo_lo;
+	__be32	fifo_len;
+};
+
+struct peer_work_params {
+	union {
+		struct virt_plug_params plug_req;
+	};
+};
+
+/**
+ * fwtty_peer: structure representing local & remote unit devices
+ * @unit: unit child device of fw_device node
+ * @serial: back pointer to associated fw_serial aggregate
+ * @guid: unique 64-bit guid for this unit device
+ * @generation: most recent bus generation
+ * @node_id: most recent node_id
+ * @speed: link speed of peer (0 = S100, 2 = S400, ... 5 = S3200)
+ * @mgmt_addr: bus addr region to write mgmt packets to
+ * @status_addr: bus addr register to write line status to
+ * @fifo_addr: bus addr region to write serial output to
+ * @fifo_len:  max length for single write to fifo_addr
+ * @list: link for insertion into fw_serial's peer_list
+ * @rcu: for deferring peer reclamation
+ * @lock: spinlock to synchonize changes to state & port fields
+ * @work: only one work item can be queued at any one time
+ *        Note: pending work is canceled prior to removal, so this
+ *        peer is valid for at least the lifetime of the work function
+ * @work_params: parameter block for work functions
+ * @timer: timer for resetting peer state if remote request times out
+ * @state: current state
+ * @connect: work item for auto-connecting
+ * @connect_retries: # of connections already attempted
+ * @port: associated tty_port (usable if state == FWSC_ATTACHED)
+ */
+struct fwtty_peer {
+	struct fw_unit		*unit;
+	struct fw_serial	*serial;
+	u64			guid;
+	int			generation;
+	int			node_id;
+	unsigned		speed;
+	int			max_payload;
+	u64			mgmt_addr;
+
+	/* these are usable only if state == FWSC_ATTACHED */
+	u64			status_addr;
+	u64			fifo_addr;
+	int			fifo_len;
+
+	struct list_head	list;
+	struct rcu_head		rcu;
+
+	spinlock_t		lock;
+	struct work_struct	work;
+	struct peer_work_params work_params;
+	struct timer_list	timer;
+	int			state;
+	struct delayed_work	connect;
+	int			connect_retries;
+
+	struct fwtty_port	*port;
+};
+
+#define to_peer(ptr, field)	(container_of(ptr, struct fwtty_peer, field))
+
+/* state values for fwtty_peer.state field */
+enum fwtty_peer_state {
+	FWPS_GONE,
+	FWPS_NOT_ATTACHED,
+	FWPS_ATTACHED,
+	FWPS_PLUG_PENDING,
+	FWPS_PLUG_RESPONDING,
+	FWPS_UNPLUG_PENDING,
+	FWPS_UNPLUG_RESPONDING,
+
+	FWPS_NO_MGMT_ADDR = -1,
+};
+
+#define CONNECT_RETRY_DELAY	HZ
+#define MAX_CONNECT_RETRIES	10
+
+/* must be holding peer lock for these state funclets */
+static inline void peer_set_state(struct fwtty_peer *peer, int new)
+{
+	peer->state = new;
+}
+
+static inline struct fwtty_port *peer_revert_state(struct fwtty_peer *peer)
+{
+	struct fwtty_port *port = peer->port;
+
+	peer->port = NULL;
+	peer_set_state(peer, FWPS_NOT_ATTACHED);
+	return port;
+}
+
+struct fwserial_mgmt_pkt {
+	struct {
+		__be16		len;
+		__be16		code;
+	} hdr;
+	union {
+		struct virt_plug_params plug_req;
+		struct virt_plug_params plug_rsp;
+	};
+} __packed;
+
+/* fwserial_mgmt_packet codes */
+#define FWSC_RSP_OK			0x0000
+#define FWSC_RSP_NACK			0x8000
+#define FWSC_CODE_MASK			0x0fff
+
+#define FWSC_VIRT_CABLE_PLUG		1
+#define FWSC_VIRT_CABLE_UNPLUG		2
+#define FWSC_VIRT_CABLE_PLUG_RSP	3
+#define FWSC_VIRT_CABLE_UNPLUG_RSP	4
+
+/* 1 min. plug timeout -- suitable for userland authorization */
+#define VIRT_CABLE_PLUG_TIMEOUT		(60 * HZ)
+
+struct stats {
+	unsigned	xchars;
+	unsigned	dropped;
+	unsigned	tx_stall;
+	unsigned	fifo_errs;
+	unsigned	sent;
+	unsigned	lost;
+	unsigned	throttled;
+	unsigned	watermark;
+	unsigned	reads[DISTRIBUTION_MAX_INDEX + 1];
+	unsigned	writes[DISTRIBUTION_MAX_INDEX + 1];
+	unsigned	txns[DISTRIBUTION_MAX_INDEX + 1];
+	unsigned	unthrottle[DISTRIBUTION_MAX_INDEX + 1];
+};
+
+struct fwconsole_ops {
+	void (*notify)(int code, void *data);
+	void (*stats)(struct stats *stats, void *data);
+	void (*proc_show)(struct seq_file *m, void *data);
+};
+
+/* codes for console ops notify */
+#define FWCON_NOTIFY_ATTACH		1
+#define FWCON_NOTIFY_DETACH		2
+
+struct buffered_rx {
+	struct list_head	list;
+	size_t			n;
+	unsigned char		data[0];
+};
+
+/**
+ * fwtty_port: structure used to track/represent underlying tty_port
+ * @port: underlying tty_port
+ * @device: tty device
+ * @index: index into port_table for this particular port
+ *    note: minor = index + FWSERIAL_TTY_START_MINOR
+ * @serial: back pointer to the containing fw_serial
+ * @rx_handler: bus address handler for unique addr region used by remotes
+ *              to communicate with this port. Every port uses
+ *		fwtty_port_handler() for per port transactions.
+ * @fwcon_ops: ops for attached fw_console (if any)
+ * @con_data: private data for fw_console
+ * @wait_tx: waitqueue for sleeping until writer/drain completes tx
+ * @emit_breaks: delayed work responsible for generating breaks when the
+ *               break line status is active
+ * @cps : characters per second computed from the termios settings
+ * @break_last: timestamp in jiffies from last emit_breaks
+ * @hangup: work responsible for HUPing when carrier is dropped/lost
+ * @mstatus: loose virtualization of LSR/MSR
+ *         bits 15..0  correspond to TIOCM_* bits
+ *         bits 19..16 reserved for mctrl
+ *         bit 20      OOB_TX_THROTTLE
+ *	   bits 23..21 reserved
+ *         bits 31..24 correspond to UART_LSR_* bits
+ * @lock: spinlock for protecting concurrent access to fields below it
+ * @mctrl: loose virtualization of MCR
+ *         bits 15..0  correspond to TIOCM_* bits
+ *         bit 16      OOB_RX_THROTTLE
+ *         bits 19..17 reserved
+ *	   bits 31..20 reserved for mstatus
+ * @drain: delayed work scheduled to ensure that writes are flushed.
+ *         The work can race with the writer but concurrent sending is
+ *         prevented with the IN_TX flag. Scheduled under lock to
+ *         limit scheduling when fifo has just been drained.
+ * @push: work responsible for pushing buffered rx to the ldisc.
+ *	  rx can become buffered if the tty buffer is filled before the
+ *	  ldisc throttles the sender.
+ * @buf_list: list of buffered rx yet to be sent to ldisc
+ * @buffered: byte count of buffered rx
+ * @tx_fifo: fifo used to store & block-up writes for dma to remote
+ * @max_payload: max bytes transmissable per dma (based on peer's max_payload)
+ * @status_mask: UART_LSR_* bitmask significant to rx (based on termios)
+ * @ignore_mask: UART_LSR_* bitmask of states to ignore (also based on termios)
+ * @break_ctl: if set, port is 'sending break' to remote
+ * @write_only: self-explanatory
+ * @overrun: previous rx was lost (partially or completely)
+ * @loopback: if set, port is in loopback mode
+ * @flags: atomic bit flags
+ *         bit 0: IN_TX - gate to allow only one cpu to send from the dma fifo
+ *                        at a time.
+ *         bit 1: STOP_TX - force tx to exit while sending
+ * @peer: rcu-pointer to associated fwtty_peer (if attached)
+ *        NULL if no peer attached
+ * @icount: predefined statistics reported by the TIOCGICOUNT ioctl
+ * @stats: additional statistics reported in /proc/tty/driver/firewire_serial
+ */
+struct fwtty_port {
+	struct tty_port		   port;
+	struct device		   *device;
+	unsigned		   index;
+	struct fw_serial	   *serial;
+	struct fw_address_handler  rx_handler;
+
+	struct fwconsole_ops	   *fwcon_ops;
+	void			   *con_data;
+
+	wait_queue_head_t	   wait_tx;
+	struct delayed_work	   emit_breaks;
+	unsigned		   cps;
+	unsigned long		   break_last;
+
+	struct work_struct	   hangup;
+
+	unsigned		   mstatus;
+
+	spinlock_t		   lock;
+	unsigned		   mctrl;
+	struct delayed_work	   drain;
+	struct work_struct	   push;
+	struct list_head	   buf_list;
+	int			   buffered;
+	struct dma_fifo		   tx_fifo;
+	int			   max_payload;
+	unsigned		   status_mask;
+	unsigned		   ignore_mask;
+	unsigned		   break_ctl:1,
+				   write_only:1,
+				   overrun:1,
+				   loopback:1;
+	unsigned long		   flags;
+
+	struct fwtty_peer	   *peer;
+
+	struct async_icount	   icount;
+	struct stats		   stats;
+};
+
+#define to_port(ptr, field)	(container_of(ptr, struct fwtty_port, field))
+
+/* bit #s for flags field */
+#define IN_TX                      0
+#define STOP_TX                    1
+#define BUFFERING_RX		   2
+
+/* bitmasks for special mctrl/mstatus bits */
+#define OOB_RX_THROTTLE   0x00010000
+#define MCTRL_RSRVD       0x000e0000
+#define OOB_TX_THROTTLE   0x00100000
+#define MSTATUS_RSRVD     0x00e00000
+
+#define MCTRL_MASK        (TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | TIOCM_OUT2 | \
+			   TIOCM_LOOP | OOB_RX_THROTTLE | MCTRL_RSRVD)
+
+/* XXX even every 1/50th secs. may be unnecessarily accurate */
+/* delay in jiffies between brk emits */
+#define FREQ_BREAKS        (HZ / 50)
+
+/* Ports are allocated in blocks of num_ports for each fw_card */
+#define MAX_CARD_PORTS           32	/* max # of ports per card */
+#define MAX_TOTAL_PORTS          64	/* max # of ports total    */
+
+/* tuning parameters */
+#define FWTTY_PORT_TXFIFO_LEN	4096
+#define FWTTY_PORT_MAX_PEND_DMA    8    /* costs a cache line per pend */
+#define DRAIN_THRESHOLD         1024
+#define MAX_ASYNC_PAYLOAD       4096    /* ohci-defined limit          */
+#define WRITER_MINIMUM           128
+/* TODO: how to set watermark to AR context size? see fwtty_rx() */
+#define HIGH_WATERMARK         32768	/* AR context is 32K	       */
+
+/*
+ * Size of bus addr region above 4GB used per port as the recv addr
+ * - must be at least as big as the MAX_ASYNC_PAYLOAD
+ */
+#define FWTTY_PORT_RXFIFO_LEN	MAX_ASYNC_PAYLOAD
+
+/**
+ * fw_serial: aggregate used to associate tty ports with specific fw_card
+ * @card: fw_card associated with this fw_serial device (1:1 association)
+ * @kref: reference-counted multi-port management allows delayed destroy
+ * @self: local unit device as 'peer'. Not valid until local unit device
+ *         is enumerated.
+ * @list: link for insertion into fwserial_list
+ * @peer_list: list of local & remote unit devices attached to this card
+ * @ports: fixed array of tty_ports provided by this serial device
+ */
+struct fw_serial {
+	struct fw_card	  *card;
+	struct kref	  kref;
+
+	struct fwtty_peer *self;
+
+	struct list_head  list;
+	struct list_head  peer_list;
+
+	struct fwtty_port *ports[MAX_CARD_PORTS];
+};
+
+#define to_serial(ptr, field)	(container_of(ptr, struct fw_serial, field))
+
+#define TTY_DEV_NAME		    "fwtty"	/* ttyFW was taken           */
+static const char tty_dev_name[] =  TTY_DEV_NAME;
+static const char loop_dev_name[] = "fwloop";
+extern bool limit_bw;
+
+struct tty_driver *fwtty_driver;
+
+#define driver_err(s, v...)	pr_err(KBUILD_MODNAME ": " s, ##v)
+
+struct fwtty_port *fwtty_port_get(unsigned index);
+void fwtty_port_put(struct fwtty_port *port);
+
+static inline void fwtty_bind_console(struct fwtty_port *port,
+				      struct fwconsole_ops *fwcon_ops,
+				      void *data)
+{
+	port->con_data = data;
+	port->fwcon_ops = fwcon_ops;
+}
+
+/*
+ * Returns the max send async payload size in bytes based on the unit device
+ * link speed - if set to limit bandwidth to max 20%, use lookup table
+ */
+static inline int link_speed_to_max_payload(unsigned speed)
+{
+	static const int max_async[] = { 307, 614, 1229, 2458, 4916, 9832, };
+	BUILD_BUG_ON(ARRAY_SIZE(max_async) - 1 != SCODE_3200);
+
+	speed = clamp(speed, (unsigned) SCODE_100, (unsigned) SCODE_3200);
+	if (limit_bw)
+		return max_async[speed];
+	else
+		return 1 << (speed + 9);
+}
+
+#endif /* _FIREWIRE_FWSERIAL_H */
-- 
1.7.12.3

^ permalink raw reply related

* [PATCH v2 0/1] staging: Add firewire-serial driver
From: Peter Hurley @ 2012-11-02 12:16 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Stefan Richter, Alan Cox
  Cc: linux-kernel, devel, linux1394-devel, linux-serial, Peter Hurley
In-Reply-To: <1350565015.23730.4.camel@thor>

v2 of this driver submission builds and runs cleanly against Greg KH's
tty-next and (hopefully) addresses the concerns raised regarding the
dependence on tty_buffer internals; specifically with the workarounds to fix
1) data loss on hangup and 2) throttling before the flip buffers are full.

1) Data loss on hangup
Although I experimented with alternative solutions as Alan suggested, none
were practical. For example, this sequence (which is almost identical to
what the n_tty ldisc does in input_available_p()):

     tty_flush_to_ldisc(tty);
     n = ldisc->ops->chars_in_buffer(tty);

suffers from a race that the ldisc could have _just_ emptied the read
buffer between these calls, so that n == 0 but data is still in the
flip buffers.

For now, the driver v2 delays the hangup on carrier loss by a fixed timeout.

2) Flip buffers full before throttle received from ldisc
Because driver throttling is performed by the ldisc, and not by the
tty_buffer, the flip buffers can be filled before receiving a throttle
request.

For now, the driver v2 pre-buffers in front of the tty_buffer; ie, rx data
which is not accepted by tty_insert_flip_string...() is buffered in the
driver, the sender is throttled and when ldisc unthrottles, normal operation
resumes by feeding the tty_buffer from the pre-buffered data.

This driver v2 also fixes the occasionally flaky auto-connect, 2 (valid)
lockdep warnings, racy line status changes, and delays fifo allocation
until .activate() to minimize memory footprint.

As before, the TODO file notes the remaining issues.

Regards,
Peter

Peter Hurley (1):
  staging: fwserial: Add TTY-over-Firewire serial driver

 drivers/staging/Kconfig             |    2 +
 drivers/staging/Makefile            |    1 +
 drivers/staging/fwserial/Kconfig    |    9 +
 drivers/staging/fwserial/Makefile   |    2 +
 drivers/staging/fwserial/TODO       |   37 +
 drivers/staging/fwserial/dma_fifo.c |  310 ++++
 drivers/staging/fwserial/dma_fifo.h |  130 ++
 drivers/staging/fwserial/fwserial.c | 2946 +++++++++++++++++++++++++++++++++++
 drivers/staging/fwserial/fwserial.h |  387 +++++
 9 files changed, 3824 insertions(+)
 create mode 100644 drivers/staging/fwserial/Kconfig
 create mode 100644 drivers/staging/fwserial/Makefile
 create mode 100644 drivers/staging/fwserial/TODO
 create mode 100644 drivers/staging/fwserial/dma_fifo.c
 create mode 100644 drivers/staging/fwserial/dma_fifo.h
 create mode 100644 drivers/staging/fwserial/fwserial.c
 create mode 100644 drivers/staging/fwserial/fwserial.h

-- 
1.7.12.3


^ permalink raw reply

* Re: [PATCH 5/8] ARM: zynq: add COMMON_CLK support
From: Josh Cartwright @ 2012-11-02 13:38 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Michal Simek, Russell King, John Stultz, devicetree-discuss,
	Michal Simek, Rob Herring, linux-kernel, Grant Likely,
	linux-serial, Greg Kroah-Hartman, Thomas Gleixner, Alan Cox,
	John Linn, linux-arm-kernel, Mike Turquette
In-Reply-To: <50939378.2050500@metafoo.de>

Thanks for the review.

On Fri, Nov 02, 2012 at 10:33:44AM +0100, Lars-Peter Clausen wrote:
> On 10/31/2012 07:58 PM, Josh Cartwright wrote:
> > [...]
> > +#define PERIPH_CLK_CTRL_SRC(x)	(periph_clk_parent_map[((x)&3)>>4])
> > +#define PERIPH_CLK_CTRL_DIV(x)	(((x)&0x3F00)>>8)
> 
> A few more spaces wouldn't hurt ;)

Okay, sure.

> > [...]
> > +static void __init zynq_periph_clk_setup(struct device_node *np)
> > +{
> > +	struct zynq_periph_clk *periph;
> > +	const char *parent_names[3];
> > +	struct clk_init_data init;
> > +	struct clk *clk;
> > +	int err;
> > +	u32 reg;
> > +	int i;
> > +
> > +	err = of_property_read_u32(np, "reg", &reg);
> > +	WARN_ON(err);
>
> Shouldn't the function abort if a error happens somewhere? Continuing here
> will lead to undefined behavior. Same is probably true for the other WARN_ONs.

The way I see it is: the kernel is will be left in a bad state in the
case of any failure, regardless of if we bail out or continue.  AFAICT,
there is no clean way to recover from a failure this early.

Given that, it seems simpler (albeit marginally so) just to continue; so
that's what I chose to do.  I'm not opposed to bailing out, just not
convinced it does anything for us.

> > +
> > +	periph = kzalloc(sizeof(*periph), GFP_KERNEL);
> > +	WARN_ON(!periph);
> > +
> > +	periph->clk_ctrl = slcr_base + reg;
> > +	spin_lock_init(&periph->clkact_lock);
> > +
> > +	init.name = np->name;
> > +	init.ops = &zynq_periph_clk_ops;
> > +	for (i = 0; i < ARRAY_SIZE(parent_names); i++)
> > +		parent_names[i] = of_clk_get_parent_name(np, i);
> > +	init.parent_names = parent_names;
> > +	init.num_parents = ARRAY_SIZE(parent_names);
> > +
> > +	periph->hw.init = &init;
> > +
> > +	clk = clk_register(NULL, &periph->hw);
> > +	WARN_ON(IS_ERR(clk));
> > +
> > +	err = of_clk_add_provider(np, of_clk_src_simple_get, clk);
> > +	WARN_ON(err);
> > +
> > +	for (i = 0; i < 2; i++) {
> 
> Not all of the peripheral clock generators have two output clocks. I think
> it makes sense to use the number entries in clock-output-names here.

Yes, I agree.  I'll also update the bindings documentation.

Thanks again,
  Josh

^ permalink raw reply

* Re: [PATCH 7/8] serial: xilinx_uartps: get clock rate info from dts
From: Josh Cartwright @ 2012-11-02 13:39 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Michal Simek, Russell King, John Stultz, devicetree-discuss,
	Michal Simek, Rob Herring, linux-kernel, Grant Likely,
	linux-serial, Greg Kroah-Hartman, Thomas Gleixner, Alan Cox,
	John Linn, linux-arm-kernel, Mike Turquette
In-Reply-To: <50939061.9060201@metafoo.de>


[-- Attachment #1.1: Type: text/plain, Size: 1242 bytes --]

On Fri, Nov 02, 2012 at 10:20:33AM +0100, Lars-Peter Clausen wrote:
> On 10/31/2012 08:28 PM, Josh Cartwright wrote:
> > Add support for specifying clock information for the uart clk via the
> > device tree.  This eliminates the need to hardcode rates in the device
> > tree.
> > 
> > Signed-off-by: Josh Cartwright <josh.cartwright@ni.com>
> > ---
> >  arch/arm/boot/dts/zynq-7000.dtsi   |  4 ++--
> >  drivers/tty/serial/xilinx_uartps.c | 30 +++++++++++++++++-------------
> >  2 files changed, 19 insertions(+), 15 deletions(-)
> > 
> > diff --git a/arch/arm/boot/dts/zynq-7000.dtsi b/arch/arm/boot/dts/zynq-7000.dtsi
> > index bb3085c..5fb763f 100644
> > --- a/arch/arm/boot/dts/zynq-7000.dtsi
> > +++ b/arch/arm/boot/dts/zynq-7000.dtsi
> > @@ -44,14 +44,14 @@
> >  			compatible = "xlnx,xuartps";
> >  			reg = <0xE0000000 0x1000>;
> >  			interrupts = <0 27 4>;
> > -			clock = <50000000>;
> > +			clocks = <&uart_clk 0>;
> >  		};
> >  
> >  		uart1: uart@e0001000 {
> >  			compatible = "xlnx,xuartps";
> >  			reg = <0xE0001000 0x1000>;
> >  			interrupts = <0 50 4>;
> > -			clock = <50000000>;
> > +			clocks = <&uart_clk 0>;
> 
> Shouldn't this be <&uart_clk 1>?

Yes, indeed.

Thanks,
  Josh

[-- Attachment #1.2: Type: application/pgp-signature, Size: 836 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* Re: [PATCH 5/8] ARM: zynq: add COMMON_CLK support
From: Lars-Peter Clausen @ 2012-11-02 15:12 UTC (permalink / raw)
  To: Josh Cartwright
  Cc: Grant Likely, Rob Herring, Russell King, Mike Turquette,
	John Stultz, Thomas Gleixner, Alan Cox, Greg Kroah-Hartman,
	John Linn, Michal Simek, devicetree-discuss, linux-kernel,
	linux-arm-kernel, linux-serial, Michal Simek
In-Reply-To: <20121102133850.GO5190@beefymiracle.amer.corp.natinst.com>

On 11/02/2012 02:38 PM, Josh Cartwright wrote:
> Thanks for the review.
> 
> On Fri, Nov 02, 2012 at 10:33:44AM +0100, Lars-Peter Clausen wrote:
>> On 10/31/2012 07:58 PM, Josh Cartwright wrote:
>>> [...]
>>> +#define PERIPH_CLK_CTRL_SRC(x)	(periph_clk_parent_map[((x)&3)>>4])
>>> +#define PERIPH_CLK_CTRL_DIV(x)	(((x)&0x3F00)>>8)
>>
>> A few more spaces wouldn't hurt ;)
> 
> Okay, sure.
> 
>>> [...]
>>> +static void __init zynq_periph_clk_setup(struct device_node *np)
>>> +{
>>> +	struct zynq_periph_clk *periph;
>>> +	const char *parent_names[3];
>>> +	struct clk_init_data init;
>>> +	struct clk *clk;
>>> +	int err;
>>> +	u32 reg;
>>> +	int i;
>>> +
>>> +	err = of_property_read_u32(np, "reg", &reg);
>>> +	WARN_ON(err);
>>
>> Shouldn't the function abort if a error happens somewhere? Continuing here
>> will lead to undefined behavior. Same is probably true for the other WARN_ONs.
> 
> The way I see it is: the kernel is will be left in a bad state in the
> case of any failure, regardless of if we bail out or continue.  AFAICT,
> there is no clean way to recover from a failure this early.
> 
> Given that, it seems simpler (albeit marginally so) just to continue; so
> that's what I chose to do.  I'm not opposed to bailing out, just not
> convinced it does anything for us.
> 

The issue with this approach is that, while you get a warning, unexpected
seemingly unrelated side-effects may happen later on. E.g. if no reg
property for the clock is specified the reg variable will be uninitialized
and contain whatever was on the stack before. The clock will be registered
nonetheless and the boot process continues. Now if the clock is enabled a
bit in a random register will be modified, which could result in strange and
abnormal behavior, which can be very hard to track down.

Also if for example just one clock has its reg property missing the system
will continue to boot if we bail out here. It is just that the peripherals
using that clock won't work. Which will certainly be easier to diagnose than
random abnormal behavior.

>>> +
>>> +	periph = kzalloc(sizeof(*periph), GFP_KERNEL);
>>> +	WARN_ON(!periph);
>>> +
>>> +	periph->clk_ctrl = slcr_base + reg;
>>> +	spin_lock_init(&periph->clkact_lock);
>>> +
>>> +	init.name = np->name;
>>> +	init.ops = &zynq_periph_clk_ops;
>>> +	for (i = 0; i < ARRAY_SIZE(parent_names); i++)
>>> +		parent_names[i] = of_clk_get_parent_name(np, i);
>>> +	init.parent_names = parent_names;
>>> +	init.num_parents = ARRAY_SIZE(parent_names);
>>> +
>>> +	periph->hw.init = &init;
>>> +
>>> +	clk = clk_register(NULL, &periph->hw);
>>> +	WARN_ON(IS_ERR(clk));
>>> +
>>> +	err = of_clk_add_provider(np, of_clk_src_simple_get, clk);
>>> +	WARN_ON(err);
>>> +
>>> +	for (i = 0; i < 2; i++) {
>>
>> Not all of the peripheral clock generators have two output clocks. I think
>> it makes sense to use the number entries in clock-output-names here.
> 
> Yes, I agree.  I'll also update the bindings documentation.
> 
> Thanks again,
>   Josh


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox