Subject: [PATCH] irda: driver for Freescale FIRI controller

From: Anton Vorontsov <[email-masked]>
Date: Wed, 4 Jun 2008 19:45:10 +0400

From: Zhang Wei <[email-masked]>

The driver supports SIR, MIR, FIR modes and maximum 4000000bps rate.

Signed-off-by: Zhang Wei <[email-masked]>
[AV: few small fixes, plus had made platform ops passing via node->data
     to avoid #ifdef stuff in the fsl_soc (think DIU). ]
Signed-off-by: Anton Vorontsov <[email-masked]>
---

If anyone from the Freescale is already working on this patch for the
mainline, I'll readily step back.

 drivers/net/irda/Kconfig  |    5 +
 drivers/net/irda/Makefile |    1 +
 drivers/net/irda/fsl_ir.c |  792 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/fsl_ir.h    |  207 ++++++++++++
 4 files changed, 1005 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/irda/fsl_ir.c
 create mode 100644 include/linux/fsl_ir.h

diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig
index ce816ba..da24f57 100644
--- a/drivers/net/irda/Kconfig
+++ b/drivers/net/irda/Kconfig
@@ -341,5 +341,10 @@ config MCS_FIR
 	  To compile it as a module, choose M here: the module will be called
 	  mcs7780.
 
+config FSL_FIR
+	tristate "Freescale Irda driver"
+	depends on IRDA && FSL_SOC
+	help
+
 endmenu
 
diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile
index 5d20fde..724218c 100644
--- a/drivers/net/irda/Makefile
+++ b/drivers/net/irda/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_VIA_FIR)		+= via-ircc.o
 obj-$(CONFIG_PXA_FICP)	        += pxaficp_ir.o
 obj-$(CONFIG_MCS_FIR)	        += mcs7780.o
 obj-$(CONFIG_AU1000_FIR)	+= au1k_ir.o
+obj-$(CONFIG_FSL_FIR)	        += fsl_ir.o
 # SIR drivers
 obj-$(CONFIG_IRTTY_SIR)		+= irtty-sir.o	sir-dev.o
 # dongle drivers for SIR drivers
diff --git a/drivers/net/irda/fsl_ir.c b/drivers/net/irda/fsl_ir.c
new file mode 100644
index 0000000..d38d309
--- /dev/null
+++ b/drivers/net/irda/fsl_ir.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Zhang Wei, [email-masked], Oct. 2007
+ *
+ * Description:
+ * The IrDA driver for Freescale PowerPC MPC8610 processor. The driver
+ * support SIR and FIR mode. The maximum speed is 4Mbps.
+ *
+ * Changelog:
+ * Oct 2007 Zhang Wei <[email-masked]>
+ * - Initial version.
+ *
+ * This file is part of the Linux kernel
+ *
+ * This 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/of_platform.h>
+#include <linux/fsl_ir.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/wrapper.h>
+#include <net/irda/irda_device.h>
+#include <net/irda/irlap_frame.h>
+#include <net/irda/irlap.h>
+
+#include <sysdev/fsl_soc.h>
+
+#define is_sir_speed(speed) ((speed <= 115200) ? 1 : 0)
+#define is_sir(ir) (is_sir_speed(ir->speed))
+
+static void init_iobuf(iobuff_t *io, void *buff, int size)
+{
+	io->head = buff;
+	io->truesize = size;
+	io->in_frame = FALSE;
+	io->state = OUTSIDE_FRAME;
+	io->data = io->head;
+	io->skb = NULL;
+}
+
+static void ir_switch_mode(struct fsl_ir *ir, u32 speed)
+{
+
+	if (ir->speed && (ir->speed < 115200))	/* Switch from SIR to FIR */
+		/* Disable SIRI */
+		clrbits32(&ir->reg_base->scr1, FSL_IR_SCR1_IREN |
+						FSL_IR_SCR1_SIRIEN);
+	else {					/* Switch from FIR to SIR */
+		/* Disable FIRI */
+		out_be32(&ir->reg_base->firitcr, 0);
+		out_be32(&ir->reg_base->firircr, 0);
+	}
+
+	/* Switch the IrDA mode on board */
+	if (ir->plat_op && ir->plat_op->set_ir_mode)
+		ir->plat_op->set_ir_mode(ir, speed);
+}
+
+static int ir_crc_len(struct fsl_ir *ir)
+{
+	int crc_len;
+
+	switch (ir->speed) {
+	case 576000:
+	case 1152000:
+		crc_len = 2;	/* CRC16, 16 bits */
+		break;
+	case 4000000:
+		crc_len = 4;	/* CRC32, 32 bits */
+		break;
+	default:
+		crc_len = 0;
+		break;
+	}
+	return crc_len;
+}
+
+static void fir_rx(struct fsl_ir *ir, int len)
+{
+	struct net_device *ndev = dev_get_drvdata(ir->dev);
+	struct sk_buff *skb;
+	int i;
+
+	if (len <= ir_crc_len(ir))
+		return;
+
+	do_gettimeofday(&ir->stamp);
+	/* Now, for new packet arriving */
+	skb = alloc_skb(len + 1, GFP_ATOMIC);
+	if (!skb) {
+		ir->stats.rx_dropped++;
+		return;
+	}
+	skb_reserve(skb, 1);
+
+	for (i = 0; i < len; i++)
+		skb->data[i] = in_8((u8 *)&ir->reg_base->rfifo);
+
+	len -= ir_crc_len(ir);
+	skb_put(skb, len);
+
+	ir->stats.rx_packets++;
+	ir->stats.rx_bytes += len;
+
+	skb->dev = ndev;
+	skb_reset_mac_header(skb);
+	skb->protocol = htons(ETH_P_IRDA);
+	netif_rx(skb);
+}
+
+static void fir_tx(struct fsl_ir *ir)
+{
+	int free_bytes;
+	struct sk_buff *skb = ir->tx_buff.skb;
+	size_t len;
+
+	if (!skb)
+		return;
+
+	spin_lock(&ir->tx_lock);
+	do {
+		free_bytes = 128 -
+			((in_be32(&ir->reg_base->firitsr) >> 8) & 0xff);
+		for (len = min(free_bytes, ir->tx_buff.len); len > 0;
+			len--, ir->tx_buff.len--)
+			out_8((u8 *)&ir->reg_base->tfifo,
+				skb->data[skb->len - ir->tx_buff.len]);
+	} while (ir->tx_buff.len > 0);
+	spin_unlock(&ir->tx_lock);
+
+	dev_kfree_skb_any(ir->tx_buff.skb);
+	ir->stats.tx_packets++;
+	ir->stats.tx_bytes += skb->len;
+}
+
+static void sir_set_data(struct fsl_ir *ir, u8 data_size)
+{
+	switch (data_size) {
+	case 7:
+		clrbits32(&ir->reg_base->scr2, FSL_IR_SCR2_WS);
+		break;
+	case 8:
+		setbits32(&ir->reg_base->scr2, FSL_IR_SCR2_WS);
+		break;
+	}
+}
+
+static void sir_set_parity(struct fsl_ir *ir, enum sir_parity parity)
+{
+	switch (parity) {
+	case SIR_PARITY_NONE:
+		clrbits32(&ir->reg_base->scr2, FSL_IR_SCR2_PREN);
+		break;
+	case SIR_PARITY_EVEN:
+		setbits32(&ir->reg_base->scr2, FSL_IR_SCR2_PREN);
+		clrbits32(&ir->reg_base->scr2, FSL_IR_SCR2_PROE);
+		break;
+	case SIR_PARITY_ODD:
+		setbits32(&ir->reg_base->scr2, FSL_IR_SCR2_PREN
+				| FSL_IR_SCR2_PROE);
+		break;
+	}
+}
+
+static void sir_set_stop(struct fsl_ir *ir, u8 stop_bit)
+{
+	switch (stop_bit) {
+	case 1:
+		clrbits32(&ir->reg_base->scr2, FSL_IR_SCR2_STPB);
+		break;
+	case 2:
+		setbits32(&ir->reg_base->scr2, FSL_IR_SCR2_STPB);
+		break;
+	}
+}
+
+static void sir_put(struct fsl_ir *ir, u8 ch)
+{
+	out_be32(&ir->reg_base->stxd, ch);
+}
+
+static u8 sir_get(struct fsl_ir *ir)
+{
+	u32 rxd = in_be32(&ir->reg_base->srxd);
+
+	if (rxd & FSL_IR_SRXD_ERR)
+		ir->stats.rx_errors++;
+
+	if (rxd & FSL_IR_SRXD_OVRRUN)
+		ir->stats.rx_fifo_errors++;
+
+	if (rxd & FSL_IR_SRXD_FRMERR)
+		ir->stats.rx_frame_errors++;
+
+	if (rxd & FSL_IR_SRXD_PRERR)
+		ir->stats.rx_crc_errors++;
+
+	return (u8)rxd;
+}
+
+static int fsl_ir_set_speed(struct net_device *ndev, u32 speed)
+{
+	u32 sbir;
+	u32 sbmr;
+	struct fsl_ir *ir = netdev_priv(ndev);
+	struct irlap_cb *self;
+
+	if (is_sir_speed(ir->speed) != is_sir_speed(speed))
+		ir_switch_mode(ir, speed);
+
+	ir->speed = speed;
+	if (is_sir_speed(speed)) {
+		/* SIR */
+		sbir = 89;
+		sbmr = ir->clock_in / ir->div / 16 * (sbir + 1) / speed - 1;
+		out_be32(&ir->reg_base->sbir, sbir);
+		out_be32(&ir->reg_base->sbmr, sbmr);
+
+		/* Prepare SIR buff */
+		init_iobuf(&ir->tx_buff, ir->txb, FSL_SIR_TXBUFF_SIZE);
+		init_iobuf(&ir->rx_buff, ir->rxb, FSL_SIR_RXBUFF_SIZE);
+
+		/* Enable SIR */
+		setbits32(&ir->reg_base->scr1, FSL_IR_SCR1_IREN |
+						FSL_IR_SCR1_SIRIEN);
+	} else {
+		/* MIR and FIR are the same mode in FSL IR controller */
+
+		/* Clean the TCR and RCR register */
+		out_be32(&ir->reg_base->firitcr, 0);
+		out_be32(&ir->reg_base->firircr, 0);
+
+		/* Prepare FIR buff */
+		ir->tx_buff.skb = NULL;
+		ir->rx_buff.skb = NULL;
+
+		/* Set FIRI Transmitter Control
+		 * Transmit complete interrupt: Enabled
+		 * Send packet abort sysmbol when FIFO underrun or transfer
+		 * abort: Enabled
+		 */
+		setbits32(&ir->reg_base->firitcr,
+			FSL_FIRITCR_TCIE | FSL_FIRITCR_PCF | FSL_FIRITCR_PC);
+		out_be32(&ir->reg_base->firicr, (32 << 5) | 5);
+
+		/* Set FIRI Receive control */
+		self = ndev->atalk_ptr;
+		setbits32(&ir->reg_base->firircr,
+			FSL_FIRIRCR_RAM | ((((u32)self->caddr |
+						((self->state == LAP_NRM_S) ?
+						 CMD_FRAME : 0)) << 16)
+						& 0x00ff0000)|
+			FSL_FIRIRCR_RDT_16 | FSL_FIRIRCR_RPA | FSL_FIRIRCR_RPP |
+			FSL_FIRIRCR_RPEIE);
+
+		switch (speed) {
+		case 576000:
+			setbits32(&ir->reg_base->firitcr,
+						FSL_FIRITCR_TM_MIR_576);
+			setbits32(&ir->reg_base->firircr,
+						FSL_FIRIRCR_RM_MIR_576);
+			break;
+		case 1152000:
+			setbits32(&ir->reg_base->firitcr,
+						FSL_FIRITCR_TM_MIR_1152);
+			setbits32(&ir->reg_base->firircr,
+						FSL_FIRIRCR_RM_MIR_1152);
+			break;
+		case 4000000:
+			setbits32(&ir->reg_base->firitcr, FSL_FIRITCR_TM_FIR);
+			setbits32(&ir->reg_base->firircr, FSL_FIRIRCR_RM_FIR);
+			break;
+		default:
+			dev_err(ir->dev, "speed %d is not supported!\n",
+					speed);
+			return -ENOTSUPP;
+		}
+
+		out_be32(&ir->reg_base->firitsr, 0xffff);
+		/* Enable FIR Rx */
+		setbits32(&ir->reg_base->firircr, FSL_FIRIRCR_RE);
+		out_be32(&ir->reg_base->firirsr, 0xffff);
+	}
+	return 0;
+}
+
+/* Start IR from 9600 speed */
+static void fsl_ir_start(struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+
+	/* Start IrDA speed from 9600 with SIRI mode */
+	ir->div = ir->clock_in / 0x10000 / 1000 + 1;
+
+	if (ir->div > 7) {
+		dev_err(ir->dev, "Unsupported SIR clock in frequency!\n");
+		return;
+	}
+
+	clrbits32(&ir->reg_base->sfcr, FSL_IR_SFCR_RFDIV_MASK);
+	setbits32(&ir->reg_base->sfcr, (ir->div < 7) ?
+				((6 - ir->div) << 7) : FSL_IR_SFCR_RFDIV_7);
+
+	/* Set Tx and Rx watermarks,
+	 * Tx watermarks = 2
+	 * Rx watermarks = 30
+	 */
+	setbits32(&ir->reg_base->sfcr, (2 << 10) | 30);
+
+	/* Set One Millisecond Register */
+	out_be32(&ir->reg_base->sonems, 0xffff & (ir->clock_in / ir->div
+								/ 1000));
+
+	/* Select interrupts */
+
+	/* SCR3:
+	 * Invert TX
+	 * If the Tx need invert, enable below:
+	 * out_be32(&ir->reg_base->scr3, 0x0700 | FSL_IR_SCR3_INVT);
+	 */
+
+
+	/* SCR1:
+	 * SIR initialization */
+	setbits32(&ir->reg_base->scr1, FSL_IR_SCR1_IREN | FSL_IR_SCR1_RRDYEN |
+				FSL_IR_SCR1_ATDMAEN | FSL_IR_SCR1_SIRIEN);
+
+	/* 9600 8-N-1 */
+	ir_switch_mode(ir, 9600);
+	fsl_ir_set_speed(ndev, 9600);
+	sir_set_data(ir, 8);
+	sir_set_parity(ir, SIR_PARITY_NONE);
+	sir_set_stop(ir, 1);
+
+	out_be32(&ir->reg_base->sts, 0x60);
+
+	/* SCR2:
+	 * Enable Tx/Rx */
+	setbits32(&ir->reg_base->scr2, 0x4000 | FSL_IR_SCR2_TXEN |
+				FSL_IR_SCR2_ATEN | FSL_IR_SCR2_RXEN);
+
+	/* SCR4:
+	 * If the Rx need invert, enable FSL_IR_SCR4_INVR bit.
+	 */
+	out_be32(&ir->reg_base->scr4, 0x8000 | FSL_IR_SCR4_IRSC);
+
+
+	dev_dbg(ir->dev, "scr1 %08x\n", in_be32(&ir->reg_base->scr1));
+	dev_dbg(ir->dev, "scr2 %08x\n", in_be32(&ir->reg_base->scr2));
+	dev_dbg(ir->dev, "scr3 %08x\n", in_be32(&ir->reg_base->scr3));
+	dev_dbg(ir->dev, "scr4 %08x\n", in_be32(&ir->reg_base->scr4));
+}
+
+static void fsl_ir_halt(struct fsl_ir *ir)
+{
+	ir_switch_mode(ir, 0);
+
+	/* Clean SIR registers */
+	out_be32(&ir->reg_base->scr1, 0);
+	out_be32(&ir->reg_base->scr2, 0);
+	out_be32(&ir->reg_base->scr3, 0);
+	out_be32(&ir->reg_base->scr4, 0);
+
+	/* Clean FIR TCR and RCR register */
+	out_be32(&ir->reg_base->firitcr, 0);
+	out_be32(&ir->reg_base->firircr, 0);
+}
+
+static irqreturn_t fsl_ir_sir_irq(struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+	u32 ssr1, ssr2;
+	ssr1 = in_be32(&ir->reg_base->ssr1);
+	ssr2 = in_be32(&ir->reg_base->ssr2);
+
+	/* Tx is ready */
+	if ((ssr1 & FSL_IR_SSR1_TRDY) && ir->tx_buff.len) {
+		clrbits32(&ir->reg_base->scr1, FSL_IR_SCR1_TRDYEN);
+		tasklet_schedule(&ir->tx_tasklet);
+	}
+
+	/* Last Tx transfer is finished */
+	if (ssr2 & FSL_IR_SSR2_TXDC && !ir->tx_buff.len) {
+		if (ir->new_speed) {
+			fsl_ir_set_speed(ndev, ir->new_speed);
+			ir->new_speed = 0;
+		}
+		clrbits32(&ir->reg_base->scr4, FSL_IR_SCR4_TCEN);
+
+		ir->stats.tx_packets++;
+		ir->stats.tx_bytes += ir->tx_buff.data
+						- ir->tx_buff.head;
+
+		netif_wake_queue(ndev);
+	}
+
+	/* Rx is ready */
+	if (ssr1 & FSL_IR_SSR1_RRDY) {
+		int i;
+		int rxchars = in_be32(&ir->reg_base->sfcr) & 0x3f;
+
+		for (i = 0; i < rxchars; i++)
+			async_unwrap_char(ndev, &ir->stats, &ir->rx_buff,
+								sir_get(ir));
+		ndev->last_rx = jiffies;
+	}
+
+	/* There are some Rx datas in FIFO less than watermark */
+	if (ssr1 & FSL_IR_SSR1_AGTIM) {
+		while (in_be32(&ir->reg_base->ssr2) & 0x1)
+			async_unwrap_char(ndev, &ir->stats, &ir->rx_buff,
+								sir_get(ir));
+		ndev->last_rx = jiffies;
+	}
+
+	out_be32(&ir->reg_base->ssr1, ssr1);
+	out_be32(&ir->reg_base->ssr2, ssr2);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fsl_ir_fir_irq(struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+	u32 tsr, rsr;
+
+	tsr = in_be32(&ir->reg_base->firitsr);
+	rsr = in_be32(&ir->reg_base->firirsr);
+
+	out_be32(&ir->reg_base->firitsr, tsr);
+	out_be32(&ir->reg_base->firirsr, rsr);
+
+	/* Tx completed */
+	if ((tsr & FSL_FIRITSR_TC) && (!ir->tx_buff.len)) {
+		clrbits32(&ir->reg_base->firitcr, FSL_FIRITCR_TE);
+		if (ir->new_speed) {
+			fsl_ir_set_speed(ndev, ir->new_speed);
+			ir->new_speed = 0;
+		}
+		ir->tx_buff.skb = NULL;
+		/* Tx finish */
+		netif_wake_queue(ndev);
+		goto out;
+	}
+
+	/* Rx errors */
+	if (rsr & (FSL_FIRIRSR_CRCE | FSL_FIRIRSR_DDE | FSL_FIRIRSR_RFO)) {
+		ir->stats.rx_errors++;
+		if (rsr & FSL_FIRIRSR_CRCE)
+			ir->stats.rx_crc_errors++;
+		if (rsr & FSL_FIRIRSR_DDE)
+			ir->stats.rx_frame_errors++;
+		if (rsr & FSL_FIRIRSR_RFO) {
+			ir->stats.rx_over_errors++;
+			/* Clean FIFO, and restart Rx */
+			clrbits32(&ir->reg_base->firircr, FSL_FIRIRCR_RE);
+			setbits32(&ir->reg_base->firircr, FSL_FIRIRCR_RE);
+		}
+		goto out;
+	}
+
+	/* Rx */
+	if (rsr & FSL_FIRIRSR_RPE)
+		fir_rx(ir, (rsr >> 8) & 0xff);
+
+out:
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fsl_ir_do_interrupt(int irq, void *data)
+{
+	struct net_device *ndev = data;
+	struct fsl_ir *ir = netdev_priv(ndev);
+
+	return is_sir(ir) ? fsl_ir_sir_irq(ndev) : fsl_ir_fir_irq(ndev);
+}
+
+static void fsl_ir_tx_do_tasklet(unsigned long data)
+{
+	struct fsl_ir *ir = (struct fsl_ir *)data;
+
+	if (is_sir(ir)) {
+		int i;
+		i = FSL_SIR_TX_MAX
+			- ((in_be32(&ir->reg_base->sfcr) >> 10) & 0x3f);
+
+		spin_lock(&ir->tx_lock);
+		while (ir->tx_buff.len && i--) {
+			sir_put(ir, *ir->tx_buff.data++);
+			ir->tx_buff.len--;
+		}
+
+		if (ir->tx_buff.len)
+			setbits32(&ir->reg_base->scr1, FSL_IR_SCR1_TRDYEN);
+		else
+			setbits32(&ir->reg_base->scr4, FSL_IR_SCR4_TCEN);
+		spin_unlock(&ir->tx_lock);
+	}
+}
+
+static struct net_device_stats *fsl_ir_stats(struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+	return &ir->stats;
+}
+
+static int fsl_ir_ioctl(struct net_device *ndev, struct ifreq *ifreq, int cmd)
+{
+	struct if_irda_req *rq = (struct if_irda_req *)ifreq;
+	struct fsl_ir *ir = netdev_priv(ndev);
+	int err = 0;
+
+	switch (cmd) {
+	case SIOCSBANDWIDTH:
+		if (capable(CAP_NET_ADMIN)) {
+			if (ir->tx_buff.len)
+				ir->new_speed = rq->ifr_baudrate;
+			else
+				err = fsl_ir_set_speed(ndev, rq->ifr_baudrate);
+		}
+		break;
+	case SIOCSMEDIABUSY:
+		if (capable(CAP_NET_ADMIN))
+			irda_device_set_media_busy(ndev, TRUE);
+		else
+			err = -EPERM;
+		break;
+	case SIOCGRECEIVING:
+		rq->ifr_receiving = is_sir(ir) ?
+				ir->rx_buff.state != OUTSIDE_FRAME : 0;
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	return err;
+}
+
+static int fsl_ir_hard_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+	int speed = irda_get_next_speed(skb);
+
+	/*
+	 * Does this packet contain a request to change the interface
+	 * speed?  If so, remember it until we complete the transmission
+	 * of this frame.
+	 */
+	if (speed != ir->speed && speed != -1)
+		ir->new_speed = speed;
+
+	/*
+	 * If this is an empty frame, we can bypass a lot.
+	 */
+	if (skb->len == 0) {
+		if (ir->new_speed) {
+			ir->new_speed = 0;
+			fsl_ir_set_speed(ndev, speed);
+		}
+		dev_kfree_skb(skb);
+		return 0;
+	}
+
+	/* Tx buff is one */
+	netif_stop_queue(ndev);
+	if (is_sir(ir)) {
+		ir->tx_buff.data = ir->tx_buff.head;
+		ir->tx_buff.len = async_wrap_skb(skb, ir->tx_buff.data,
+						 ir->tx_buff.truesize);
+		dev_kfree_skb(skb);
+		setbits32(&ir->reg_base->scr1, FSL_IR_SCR1_TRDYEN);
+	} else {
+		unsigned int mtt = irda_get_mtt(skb);
+		struct timeval now;
+		int pending;
+
+		ir->tx_buff.skb = skb;
+		ir->tx_buff.len = skb->len;
+
+		/*
+		 * If we have a mean turn-around time, impose the specified
+		 * specified delay.  We could shorten this by timing from
+		 * the point we received the packet.
+		 */
+		if (mtt) {
+			do_gettimeofday(&now);
+			pending = now.tv_usec - ir->stamp.tv_usec;
+
+			if (pending < 0)
+				pending += 1000000;
+
+			if (mtt > pending)
+				mtt -= pending;
+			else if (pending < mtt * 2)
+				mtt = 0;
+			udelay(mtt);
+		}
+
+		/* Enable Tx */
+		out_be32(&ir->reg_base->firitctr, skb->len - 1);
+		setbits32(&ir->reg_base->firitcr, FSL_FIRITCR_TE);
+		fir_tx(ir);
+	}
+
+	ndev->trans_start = jiffies;
+	return 0;
+}
+
+static int fsl_ir_open(struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+	int err = 0;
+	unsigned int baudrate_mask;
+
+	dev_info(ir->dev, "%s open\n", ndev->name);
+
+	irda_init_max_qos_capabilies(&ir->qos);
+
+	/*
+	 * We support SIR, MIR and FIR rate.
+	 */
+	baudrate_mask = IR_9600 | IR_19200 | IR_38400 | IR_57600 | IR_115200
+			| IR_576000 | IR_1152000 	/* MIR rates */
+			| (IR_4000000 << 8);		/* FIR rates */
+	ir->qos.baud_rate.bits &= baudrate_mask;
+	ir->qos.min_turn_time.bits = 7;	/* Min turn around time is 1ms */
+
+	/* 64 bytes receive size, since FIFO buffer size is 128 bytes. */
+	ir->qos.data_size.bits = 0x01;
+
+	irda_qos_bits_to_value(&ir->qos);
+
+	ir->speed = 0;
+	fsl_ir_start(ndev);
+
+	/*http://www.kernel.org/pub/scm/linux/kernel/git/avi/kvm.git
+	 * Open a new IrLAP layer instance.
+	 */
+	ir->irlap = irlap_open(ndev, &ir->qos, "fsl-irda");
+	if (!ir->irlap) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	if (ir->irq != NO_IRQ) {
+		err = request_irq(ir->irq, &fsl_ir_do_interrupt, IRQF_SHARED,
+					"fsl-irda", ndev);
+		if (err)
+			dev_err(&ndev->dev, "fsl irda request_irq error "
+				"with return %d\n", err);
+	}
+
+	netif_start_queue(ndev);
+	return 0;
+
+err:
+	free_irq(ir->irq, ndev);
+	irlap_close(ir->irlap);
+	fsl_ir_halt(ir);
+	return err;
+}
+
+static int fsl_ir_stop(struct net_device *ndev)
+{
+	struct fsl_ir *ir = netdev_priv(ndev);
+
+	irlap_close(ir->irlap);
+	ir->irlap = NULL;
+
+	netif_stop_queue(ndev);
+	free_irq(ir->irq, ndev);
+	fsl_ir_halt(ir);
+	return 0;
+}
+
+static int of_fsl_ir_probe(struct of_device *ofdev,
+			const struct of_device_id *match)
+{
+	struct net_device *ndev;
+	struct fsl_ir *ir = NULL;
+	int err;
+	const void *of_val;
+	int len;
+	struct device_node *cpu_node;
+
+	ndev = alloc_irdadev(sizeof(struct fsl_ir));
+	if (!ndev) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	ir = ndev->priv;
+	ir->dev = &ofdev->dev;
+	ir->plat_op = ofdev->node->data;
+
+	spin_lock_init(&ir->tx_lock);
+	spin_lock_init(&ir->rx_lock);
+
+	/* get register base from of-node */
+	err = of_address_to_resource(ofdev->node, 0, &ir->res);
+	if (err) {
+		dev_err(&ofdev->dev, "Can't get %s property 'reg'\n",
+				ofdev->node->full_name);
+		goto err;
+	}
+
+	ir->reg_base = ioremap(ir->res.start, ir->res.end - ir->res.start + 1);
+	tasklet_init(&ir->tx_tasklet, fsl_ir_tx_do_tasklet, (unsigned long)ir);
+
+	cpu_node = of_find_node_by_type(NULL, "cpu");
+	if (!cpu_node) {
+		err = -ENODEV;
+		goto err;
+	}
+
+	of_val = of_get_property(cpu_node, "bus-frequency", &len);
+	if (of_val)
+		ir->clock_in = *(u32 *)of_val / 2;
+
+	/* Get irq from of-node */
+	ir->irq = irq_of_parse_and_map(ofdev->node, 0);
+
+	ndev->hard_start_xmit	= fsl_ir_hard_xmit;
+	ndev->open		= fsl_ir_open;
+	ndev->stop		= fsl_ir_stop;
+	ndev->do_ioctl		= fsl_ir_ioctl;
+	ndev->get_stats		= fsl_ir_stats;
+	ndev->irq		= ir->irq;
+
+	err = register_netdev(ndev);
+	if (err)
+		goto err;
+
+	dev_set_drvdata(&ofdev->dev, ndev);
+
+	dev_info(&ofdev->dev, "Found fsl-irda controller\n");
+	dev_info(&ofdev->dev, "Clock-in frequency %dHz, irq %d, %s\n",
+				ir->clock_in, ir->irq, ndev->name);
+
+err:
+	return err;
+}
+
+static int of_fsl_ir_remove(struct of_device *ofdev)
+{
+	struct net_device *ndev = dev_get_drvdata(&ofdev->dev);
+
+	if (ndev) {
+		unregister_netdev(ndev);
+		free_netdev(ndev);
+	}
+	return 0;
+}
+
+static struct of_device_id of_fsl_ir_ids[] = {
+	{ .compatible = "fsl,mpc8610-irda", },
+	{}
+};
+
+static struct of_platform_driver of_fsl_ir_drv = {
+	.owner		= THIS_MODULE,
+	.name		= "of-fsl-irda",
+	.match_table	= of_fsl_ir_ids,
+	.probe		= of_fsl_ir_probe,
+	.remove		= of_fsl_ir_remove,
+};
+
+static __init int of_fsl_ir_init(void)
+{
+	return of_register_platform_driver(&of_fsl_ir_drv);
+}
+
+static void __exit of_fsl_ir_exit(void)
+{
+	of_unregister_platform_driver(&of_fsl_ir_drv);
+}
+
+device_initcall(of_fsl_ir_init);
+module_exit(of_fsl_ir_exit);
+
+MODULE_AUTHOR("Zhang Wei <[email-masked]>");
+MODULE_DESCRIPTION("Freescale MPC8610 IrDA driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/fsl_ir.h b/include/linux/fsl_ir.h
new file mode 100644
index 0000000..2ee623a
--- /dev/null
+++ b/include/linux/fsl_ir.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2007 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Zhang Wei, [email-masked], Oct. 2007
+ *
+ * Description:
+ * This file is the header file for Freescale IrDA driver.
+ *
+ * Changelog:
+ * Oct 2007 Zhang Wei <[email-masked]>
+ * - Initial version.
+ *
+ * This file is part of the Linux kernel
+ *
+ * This 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.
+ *
+ */
+#ifndef __LINUX_FSL_IR_H
+#define __LINUX_FSL_IR_H
+
+#include <linux/device.h>
+#include <net/irda/irda.h>
+#include <net/irda/wrapper.h>
+#include <net/irda/irda_device.h>
+
+enum fsl_ir_mode {
+	FSL_IR_CLOSE,
+	FSL_IR_SIR,
+	FSL_IR_MIR,
+	FSL_IR_FIR
+};
+
+#define FSL_IR_SRXD_CHARRDY	0x00008000 /* Character ready */
+#define FSL_IR_SRXD_ERR		0x00004000 /* Error detect */
+#define FSL_IR_SRXD_OVRRUN	0x00002000 /* Receiver overrun */
+#define FSL_IR_SRXD_FRMERR	0x00001000 /* Frame error */
+#define FSL_IR_SRXD_PRERR	0x00000400 /* Parity error */
+
+#define FSL_IR_SCR1_TRDYEN	0x00002000 /* Transmitter ready enable */
+#define FSL_IR_SCR1_RRDYEN	0x00000200 /* Receive ready interrupt enable */
+#define FSL_IR_SCR1_RXDMAEN	0x00000100 /* Receive ready DMA enable */
+#define FSL_IR_SCR1_IREN	0x00000080 /* Infrared interface enable */
+#define FSL_IR_SCR1_TXDMAEN	0x00000008 /* Transmitter ready DMA enable */
+#define FSL_IR_SCR1_ATDMAEN	0x00000004 /* Aging DMA timer enable */
+#define FSL_IR_SCR1_SIRIEN	0x00000001 /* SIRI Enable */
+
+#define FSL_IR_SCR2_PREN	0x00000100 /* Parity enable */
+#define FSL_IR_SCR2_PROE	0x00000080 /* Parity odd/even */
+#define FSL_IR_SCR2_STPB	0x00000040 /* Stop bit */
+#define FSL_IR_SCR2_WS		0x00000020 /* Word size */
+#define FSL_IR_SCR2_ATEN	0x00000008 /* Aging timer enable */
+#define FSL_IR_SCR2_TXEN	0x00000004 /* Transmitter enable */
+#define FSL_IR_SCR2_RXEN	0x00000002 /* Receiver enable */
+#define FSL_IR_SCR2_SRST	0x00000001 /* Software reset */
+
+#define FSL_IR_SCR3_RXDSEN	0x00000040 /* Receive status interrupt enable */
+#define FSL_IR_SCR3_AIRINTEN	0x00000020 /* Asynchronous IR Wake int enable */
+#define FSL_IR_SCR3_AWAKEN	0x00000010 /* Asynchronous WAKE int enable */
+#define FSL_IR_SCR3_INVT	0x00000002 /* Inverted infrared transmission */
+
+#define FSL_IR_SCR4_INVR	0x00000200 /* Inverted infrared reception */
+#define FSL_IR_SCR4_ENIRI	0x00000100 /* Serial irda interrupt enable*/
+#define FSL_IR_SCR4_IRSC	0x00000020 /* IR special case */
+#define FSL_IR_SCR4_LPBYP	0x00000010 /* Low power bypass */
+#define FSL_IR_SCR4_TCEN	0x00000008 /* Transmit complete int enable */
+#define FSL_IR_SCR4_DREN	0x00000001 /* Receive data ready int enable */
+
+#define FSL_IR_SFCR_RFDIV_MASK	0x00000380
+#define FSL_IR_SFCR_RFDIV_6	0x00000000
+#define FSL_IR_SFCR_RFDIV_5	0x00000080
+#define FSL_IR_SFCR_RFDIV_4	0x00000100
+#define FSL_IR_SFCR_RFDIV_3	0x00000180
+#define FSL_IR_SFCR_RFDIV_2	0x00000200
+#define FSL_IR_SFCR_RFDIV_1	0x00000210
+#define FSL_IR_SFCR_RFDIV_7	0x00000300
+
+#define FSL_IR_STS_LOOP		0x00001000	/* Loop TX and RX for test */
+#define FSL_IR_STS_LOOPIR	0x00000400	/* Loop TX and RX for test */
+#define FSL_IR_STS_TXFULL	0x00000010	/* TX FIFO full */
+
+#define FSL_IR_SSR1_TRDY	0x00002000	/* Transmitter ready */
+#define FSL_IR_SSR1_RRDY	0x00000200	/* Receiver ready */
+#define FSL_IR_SSR1_AGTIM	0x00000100	/* Aging timer interrupt flag */
+
+#define FSL_IR_SSR2_TXDC	0x00000008	/* Transmitter is complete */
+#define FSL_IR_SSR2_RDR		0x00000001	/* Receive data is ready */
+
+#define FSL_FIRITCR_TDT_32	0x00000800	/* TDT trigger leverl = 32 */
+#define FSL_FIRITCR_TDT_16	0x00000400	/* TDT trigger leverl = 16 */
+#define FSL_FIRITCR_TCIE	0x00000200	/* Transmit complete INT en */
+#define FSL_FIRITCR_PCF		0x00000040	/* Send abort if underrun */
+#define FSL_FIRITCR_PC		0x00000020	/* Send abort is sip == 1 */
+#define FSL_FIRITCR_TM_FIR	0x00000000
+#define FSL_FIRITCR_TM_MIR_576	0x00000002
+#define FSL_FIRITCR_TM_MIR_1152	0x00000004
+#define FSL_FIRITCR_TE		0x00000001
+
+#define FSL_FIRIRCR_RAM		0x03000000	/* Match RA and broadcast */
+#define FSL_FIRIRCR_RPEDE	0x00000800	/* Enable Rx DMA request */
+#define FSL_FIRIRCR_RDT_96	0x00000600	/* Rx DMA trigger level is 96 */
+#define FSL_FIRIRCR_RDT_32	0x00000200	/* Rx DMA trigger level is 32 */
+#define FSL_FIRIRCR_RDT_16	0x00000100	/* Rx DMA trigger level is 32 */
+#define FSL_FIRIRCR_RPA		0x00000080	/* Clear FIFO is illegal recv */
+#define FSL_FIRIRCR_RPEIE	0x00000040	/* Recv packet end INT enable */
+#define FSL_FIRIRCR_PAIE	0x00000020	/* Recv abort INT enable */
+#define FSL_FIRIRCR_RFOIE	0x00000010	/* Recv overrun INT enable */
+#define FSL_FIRIRCR_RPP		0x00000008	/* Recv signal is inverted*/
+#define FSL_FIRIRCR_RM_FIR	0x00000000
+#define FSL_FIRIRCR_RM_MIR_576	0x00000002
+#define FSL_FIRIRCR_RM_MIR_1152	0x00000004
+#define FSL_FIRIRCR_RE		0x00000001
+
+#define FSL_FIRITSR_TC		0x00000008	/* Transmit complete */
+#define FSL_FIRITSR_TPE		0x00000002	/* Transmit Packet complete */
+
+#define FSL_FIRIRSR_RFP_mask	0x0000ff00	/* Receiver FIFO point mask */
+#define FSL_FIRIRSR_PAS		0x00000020	/* Preamble search */
+#define FSL_FIRIRSR_RPE		0x00000010	/* Receiver packet end */
+#define FSL_FIRIRSR_RFO		0x00000008	/* Receiver FIFO overrun */
+#define FSL_FIRIRSR_BAM		0x00000006	/* Broadcast address match */
+#define FSL_FIRIRSR_CRCE	0x00000002	/* Receiver CRC error */
+#define FSL_FIRIRSR_DDE		0x00000001	/* Receiver DD error */
+
+struct fsl_ir_reg {
+	__be32	srxd;		/* 0x00: SIRI receiver register */
+	u8	res1[0x3c];
+	__be32	stxd;		/* 0x40: SIRI transmitter register */
+	u8	res2[0x3c];
+	__be32	scr1;		/* 0x80: SIRI control register 1 */
+	__be32	scr2;		/* 0x84: SIRI control register 2 */
+	__be32	scr3;		/* 0x88: SIRI control register 3 */
+	__be32	scr4;		/* 0x8c: SIRI control register 4 */
+	__be32	sfcr;		/* 0x90: SIRI FIFO control register */
+	__be32	ssr1;		/* 0x94: SIRI status register 1 */
+	__be32	ssr2;		/* 0x98: SIRI status register 2 */
+	__be32	sesc;		/* 0x9c: SIRI escape character register */
+	__be32	stim;		/* 0xa0: SIRI escape timer register */
+	__be32	sbir;		/* 0xa4: SIRI BRM incremental register */
+	__be32	sbmr;		/* 0xa8: SIRI BRM modulator register */
+	__be32	sbrc;		/* 0xac: SIRI baud rate count register */
+	__be32	sonems;		/* 0xb0: SIRI one millisecond register */
+	__be32	sts;		/* 0xb4: SIRI test register */
+	u8	res3[0x48];
+	__be32	firitcr;	/* 0x100: FIRI transmit control register */
+	__be32	firitctr;	/* 0x104: FIRI transmit count register */
+	__be32	firircr;	/* 0x108: FIRI receive control register */
+	__be32	firitsr;	/* 0x10c: FIRI transmit status register */
+	__be32	firirsr;	/* 0x110: FIRI receive status register */
+	__be32	tfifo;		/* 0x114: Transmiter FIFO */
+	__be32	rfifo;		/* 0x118: Receiver FIFO */
+	__be32	firicr;		/* 0x11c: FIRI control register */
+	u8	res4[0xee0];
+};
+
+#define FSL_SIR_TXBUFF_SIZE	14384
+#define FSL_SIR_RXBUFF_SIZE	4000
+
+enum sir_parity {
+	SIR_PARITY_NONE,
+	SIR_PARITY_EVEN,
+	SIR_PARITY_ODD
+};
+
+#define FSL_SIR_TX_LEVEL	2
+#define FSL_SIR_TX_MAX		32
+
+#define FIR_PKG_SIZE		2048
+
+struct fsl_ir;
+
+struct fsl_ir_op {
+	void (*set_ir_mode)(struct fsl_ir *, u32 speed);
+};
+
+struct fsl_ir {
+	struct fsl_ir_reg __iomem	*reg_base;
+	struct resource			res;
+	struct device			*dev;
+	int				irq;
+	struct fsl_ir_op		*plat_op;
+
+	struct irlap_cb			*irlap;
+	struct qos_info			qos;
+	struct net_device_stats		stats;
+
+	int				speed;
+	int				new_speed;
+	u32				clock_in;
+	int				div;
+
+	u8				txb[FSL_SIR_TXBUFF_SIZE];
+	u8				rxb[FSL_SIR_RXBUFF_SIZE];
+	iobuff_t			tx_buff;
+	iobuff_t			rx_buff;
+
+	spinlock_t			tx_lock;  /* Lock for Tx */
+	spinlock_t			rx_lock;  /* Lock for Rx */
+
+	struct tasklet_struct		tx_tasklet;
+	struct delayed_work		rx_work;
+	struct timeval			stamp;
+};
+
+#endif /* __LINUX_FSL_IR_H */
-- 
1.5.5.1