/*
 * Copyright 2004 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @file mxc_fir.c
 *
 * @brief Driver for the Freescale Semiconductor MXC FIRI.
 *
 * This driver is based on drivers/net/irda/sa1100_ir.c, by Russell King.
 *
 * @ingroup FIRI
 */

/*
 * Include Files
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>

#include <net/irda/irda.h>
#include <net/irda/wrapper.h>
#include <net/irda/irda_device.h>

#include <asm/irq.h>
#include <asm/dma.h>
#include <asm/hardware.h>
#include <asm/arch/platform.h>
#include <asm/arch/gpio.h>
#include "mxc_fir.h"

/*!
 * This structure is a way for the low level driver to define their own
 * \b mxc_fir structure. This structure includes SK buffers, DMA buffers.
 * and has other elements that are specifically required by this driver.
 */
typedef struct {
        /*!
         * This keeps track of device is running or not
         */
        unsigned char           open;

        /*!
         * This holds current FIRI communication speed
         */
        int                     speed;

        /*!
         * This holds FIRI communication speed for next packet
         */
        int                     newspeed;
        
        /*!
         * SK buffer for transmitter
         */
        struct sk_buff          *txskb;

        /*!
         * SK buffer for receiver 
         */
        struct sk_buff          *rxskb;

        /*!
         * DMA address for transmitter
         */
        dma_addr_t              txbuf_dma;

        /*!
         * DMA address for receiver
         */
        dma_addr_t              rxbuf_dma;

        /*!
         * DMA channel for transmitter
         */
        int                     txdma_ch;
       
        /*!
         * DMA channel for receiver
         */
        int                     rxdma_ch;

        /*!
         * IrDA network device statistics
         */
        struct net_device_stats stats;

        /*!
         * The device structure used to get FIRI information
         */
        struct device           *dev;

        /*!
         * IrLAP layer instance
         */
        struct irlap_cb         *irlap;

        /*!
         * Driver supported baudrate capabilities
         */
        struct qos_info         qos;
} mxc_fir;

/*!
 * This function allocates and maps the receive buffer, 
 * unless it is already allocated.
 *
 * @param   si   FIRI device specific structure.
 * @return  The function returns 0 on success and a non-zero value on
 *          failure.
 */
static int mxc_fir_rx_alloc(mxc_fir *si)
{
        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        if (si->rxskb) {
                return 0;
        }

        si->rxskb = alloc_skb(FIR_MAX_RXLEN + 1, GFP_ATOMIC);

        if (!si->rxskb) {
                ERROR("mxc_fir: out of memory for RX SKB\n");
                return -ENOMEM;
        }

        /*
         * Align any IP headers that may be contained
         * within the frame.
         */
        skb_reserve(si->rxskb, 1);

        si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data,
                                       FIR_MAX_RXLEN, DMA_FROM_DEVICE);
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;
}

/*!
 * SDMA error callback routine.
 *
 * @param   errno  error code
 * @param   arg    pointer to device specific structure
 */

static void mxc_fir_dma_err_irq(int errno,void *arg)
{
        IRDA_DEBUG(KERN_INFO,"SDMA Error Callback\n");
}

/*!
 * Receiver DMA callback routine.
 *
 * @param   id   pointer to device specific structure
 */
static void mxc_fir_rxdma_irq(void *id)
{

        /* we should never get here!! */
        struct net_device *dev = id;
        mxc_fir *si = dev->priv;
        struct sk_buff *skb = si->rxskb;
        unsigned int len = 0;
        int ret;
                
        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);

        mxc_dma_stop(si->rxdma_ch);
        if (!skb) {
                ERROR("%s: SKB is NULL!\n", __FUNCTION__);
                return;
        }

        /* TODO:Get the current data position.*/
        /*
        dma_addr = mxc_get_dma_pos(si->rxdma_ch);
        len = dma_addr - si->rxbuf_dma;
        if (len > FIR_MAX_RXLEN) {
                len = FIR_MAX_RXLEN;
        }
        */
        len = 0;
        dma_unmap_single(si->dev, si->rxbuf_dma, len, DMA_FROM_DEVICE);
        if (FIRIRSR & FIRIRSR_RPE) {
                si->rxskb = NULL;

                skb_put(skb, len);
                skb->dev = dev;
                skb->mac.raw = skb->data;
                skb->protocol = htons(ETH_P_IRDA);
                si->stats.rx_packets++;
                si->stats.rx_bytes += len;

                /* Before we pass the buffer up, allocate a new one. */
                ret = mxc_fir_rx_alloc(si);
                if (ret) {
                        ERROR("Not sufficient Memory for SK Buffer\n");
                        ERROR("disabling RX and RX DMA channel\n");
                        MESSAGE("Close the device and Re-open\n");
                        FIRIRCR = FIRIRCR | FIRIRCR_RE;
                        mxc_dma_stop(si->rxdma_ch);
                }

                netif_rx(skb);
                dev->last_rx = jiffies;
        } else {
                si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data,
                                FIR_MAX_RXLEN,
                                DMA_FROM_DEVICE);
        }

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
}

/*!
 * This function is called to configure and start RX DMA channel
 * This uses the existing buffer.
 *
 * @param   si   FIRI device specific structure.
 */
static void mxc_fir_rx_dma_start(mxc_fir *si)
{
        int data;
        dma_request_t dma_cfg;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        if (!si->rxskb) {
                ERROR("%s: rx buffer went missing\n", __FUNCTION__);
                return;
        }

        /*
         * Does RX FIFO contain any more data ?
         * First check for available bytes in RX FIFO
         * and empty RX FIFO
         */
        while ((FIRIRSR & FIRIRSR_RFP) >> 8) {
                data = FIRIRXFIFO;
        }

        /* 
         * Enable the DMA, receiver and receive interrupt.
         * Configure DMA RX Channel for source and destination addresses,
         * Number of bytes in RX FIFO and Reception complete
         * callback function.
         */
        dma_cfg.sourceAddr = (__u8*)&(FIRIRXFIFO);
        dma_cfg.destAddr = (__u8*)&si->txbuf_dma;
        dma_cfg.count =   FIRIRSR_RFP;
        dma_cfg.callback = mxc_fir_rxdma_irq;
        mxc_dma_set_config(si->rxdma_ch, &dma_cfg);

        mxc_dma_start(si->rxdma_ch);
        FIRITCR = FIRITCR | FIRITCR_TE;
        FIRIRCR = FIRIRCR | FIRIRCR_RE;
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
}

/*!
 * This function is called to set the IrDA communications speed.
 *
 * @param   si     FIRI specific structure.
 * @param   speed  new Speed to be configured for.
 *
 * @returns The function returns \b -EINVAL on failure, else it returns 0
 */
static int mxc_fir_set_speed(mxc_fir *si, int speed)
{
        unsigned long flags;
        int ret = -EINVAL;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        /* stop RX DMA */
        mxc_dma_stop(si->rxdma_ch);
        local_irq_save(flags);

        /* clear TX and RX status registers */
        FIRITSR = 0xff;
        FIRIRSR = 0xff;

        /* Burst length = 32 bytes */
        FIRICR = 32 << 5;
        FIRITCR = FIRITCR_TCIE | FIRITCR_TPEIE | FIRITCR_TFUIE;
        FIRITCTR = FIRITCTR_TPL;
        FIRIRCR = FIRIRCR_RPEDE | FIRIRCR_RPEIE | FIRIRCR_RFOIE | FIRIRCR_PAIE;

        switch (speed) {
        case 576000:
                /* TODO:configure Clock Controller and Over Sampling Factor */
                FIRITCR = FIRITCR | FIRITCR_SRF_MIR | FIRITCR_TDT_MIR |
                          FIRITCR_TM_MIR1;
                FIRIRCR = FIRIRCR | FIRIRCR_RDT_MIR | FIRIRCR_RM_MIR1;
                ret = 0;
                break;
                
        case 115200:

                /*
                 * TODO:configure Clock Controller and Over Sampling Factor
                 * accordingly
                 */
                FIRITCR = FIRITCR | FIRITCR_SRF_MIR | FIRITCR_TDT_MIR | 
                          FIRITCR_TM_MIR2;
                FIRIRCR = FIRIRCR | FIRIRCR_RDT_MIR | FIRIRCR_RM_MIR1;
                ret = 0;
                break;
                
        case 4000000:

                /*
                 * TODO:configure Clock Controller and Over Sampling Factor
                 * accordingly
                 */
                FIRITCR = FIRITCR | FIRITCR_SRF_FIR | FIRITCR_TDT_FIR | 
                          FIRITCR_TM_FIR;
                FIRIRCR = FIRIRCR | FIRIRCR_RDT_FIR | FIRIRCR_RM_FIR;
                ret = 0;
                break;

        default:
                break;
        }

        si->speed = speed;
        ret = mxc_fir_rx_alloc(si);
        if (ret) {
                goto err_mem;
        }
        mxc_fir_rx_dma_start(si);
err_mem:
        local_irq_restore(flags);

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);;
        return ret;
}

/*!
 * This function enables FIRI port.
 * configures GPIO/IOMUX
 *
 * @param   si  FIRI port specific structure.
 *
 * @return  The function returns 0 on success and a non-zero value on
 *          failure.
 */
static int mxc_fir_startup(mxc_fir *si)
{
        int ret;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        /* TODO:Configure the IOMUX to disable the FIRI port. */
        /* iomux_config_gpr(MUX_PGP_FIRI,TRUE); */
        /* configure FIRI device for speed */
        ret = mxc_fir_set_speed(si, si->speed = 576000);
        if (ret) {
                return ret;
        }

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;
}

/*!
 * This function is called to disable the FIRI port
 *
 * @param   si  FIRI port specific structure.
 */
static void mxc_fir_shutdown(mxc_fir *si)
{
        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        /* Stop all DMA activity. */
        mxc_dma_stop(si->txdma_ch);
        mxc_dma_stop(si->rxdma_ch);

        /* TODO:Configure the IOMUX to disable the FIRI port. */
        /* iomux_config_gpr(MUX_PGP_FIRI,FALSE); */
       
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
}

/*!
 * This function is called to put the FIRI in a low power state. Refer to the
 * document driver-model/driver.txt in the kernel source tree for more
 * information.
 *
 * @param   _dev   the device structure used to give information on which FIRI
 *                to suspend
 * @param   state the power state the device is entering
 * @param   level the stage in device suspension process that we want the
 *                device to be put in
 *
 * @return  The function always returns 0.
 */
static int mxc_fir_suspend(struct device *_dev, u32 state, u32 level)
{
        struct net_device *dev = dev_get_drvdata(_dev);
        mxc_fir *si;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        if (!dev || level != SUSPEND_DISABLE) {
                return 0;
        }

        si = dev->priv;
        if (si->open) {
                /* wait for Transfer to complete */
                while ((FIRITSR & FIRITSR_TC) || (FIRIRSR & FIRIRSR_RFP)) {
                        /* do nothing */
                }
                /* Stop the transmit queue */
                netif_device_detach(dev);
                disable_irq(dev->irq);
                mxc_fir_shutdown(si);
        }

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;
}

/*!
 * This function is called to bring the FIRI back from a low power state. Refer
 * to the document driver-model/driver.txt in the kernel source tree for more
 * information.
 *
 * @param   _dev   the device structure used to give information on which FIRI 
 *                to resume
 * @param   level the stage in device resumption process that we want the
 *                device to be put in
 *
 * @return  The function always returns 0.
 */
static int mxc_fir_resume(struct device *_dev, u32 level)
{
        struct net_device *dev = dev_get_drvdata(_dev);
        mxc_fir *si;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
                
        if (!dev || level != RESUME_ENABLE) {
                return 0;
        }

        si = dev->priv;
        if (si->open) {

                /*
                 * If we missed a speed change, initialize at the new speed
                 * directly.  
                 */
                if (si->newspeed) {
                        si->speed = si->newspeed;
                        si->newspeed = 0;
                }

                mxc_fir_startup(si);
                enable_irq(dev->irq);
                /* This automatically wakes up the queue */
                netif_device_attach(dev);
        }

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;
}

/*!
 * This function is called by FIRI Interrupt Service Routine to handle packet
 * receive errors.
 * 
 * @param   si   FIRI device specific structure
 * @param   dev  net_device structure
 */
static void mxc_fir_error(mxc_fir *si, struct net_device *dev)
{
        struct sk_buff *skb = si->rxskb;
        /* dma_addr_t dma_addr; */
        unsigned int len = 0, stat, data;
        int ret;

        if (!skb) {
                ERROR("%s: SKB is NULL!\n", __FUNCTION__);
                return;
        }

        /* TODO:Get the current data position. */
        /*
        dma_addr = mxc_get_dma_pos(si->rxdma); 
        len = dma_addr - si->rxbuf_dma;
        if (len > FIR_MAX_RXLEN) {
                len = FIR_MAX_RXLEN;
        }                                
        */
        dma_unmap_single(si->dev, si->rxbuf_dma, len, DMA_FROM_DEVICE);

        do {
                /* Read Status, and then Data. */
                stat = FIRIRSR;
                rmb();
                data = FIRIRXFIFO;

                if (stat & (FIRIRSR_CRCE | FIRIRSR_RFO)) {
                        si->stats.rx_errors++;
                        if (stat & FIRIRSR_CRCE) {
                                si->stats.rx_crc_errors++;
                        }
                        if (stat & FIRIRSR_RFO) {
                                si->stats.rx_frame_errors++;
                        }
                } else {
                        skb->data[len++] = data;
                }
                /*
                 * If we hit the end of frame, there's
                 * no point in continuing.
                 */
                if (stat & FIRIRSR_RPE) {
                        break;
                }
        } while (FIRIRSR & FIRIRSR_RFP);

        if (stat & FIRIRSR_RPE) {
                si->rxskb = NULL;

                skb_put(skb, len);
                skb->dev = dev;
                skb->mac.raw = skb->data;
                skb->protocol = htons(ETH_P_IRDA);
                si->stats.rx_packets++;
                si->stats.rx_bytes += len;

                /* Before we pass the buffer up, allocate a new one. */
                ret = mxc_fir_rx_alloc(si);
                if (ret) {
                        ERROR("Not sufficient Memory for SK Buffer\n");
                        ERROR("disabling RX and RX DMA channel\n");
                        MESSAGE("Close the device and Re-open\n");
                        FIRIRCR = FIRIRCR | FIRIRCR_RE;
                        mxc_dma_stop(si->rxdma_ch);
                }

                netif_rx(skb);
                dev->last_rx = jiffies;
                FIRIRSR = FIRIRSR | FIRIRSR_RPE;
        } else {
                /* Remap the buffer. */
                si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data,
                                FIR_MAX_RXLEN,
                                DMA_FROM_DEVICE);
        }
}

/*!
 * This is FIRI interrupt service routine.
 * This function handles TX interrupts, like Transmit Complete, Transmit Packet
 * End and Transmitter FIFO Under-run.
 * For receiver interrupt this function disables RX , process received packet,
 * and then restart RX. This also checks for any receive errors.
 *
 * @param   irq    the interrupt number
 * @param   dev_id driver private data
 * @param   regs   holds a snapshot of the processor's context before the 
 *                 processor entered the interrupt code
 *
 * @return  The function returns \b IRQ_HANDLED 
 *          \b IRQ_HANDLED is defined in \b include/linux/interrupt.h.
 */
static irqreturn_t mxc_fir_irq(int irq, void *dev_id, struct pt_regs *regs)
{
        struct net_device *dev = dev_id;
        mxc_fir *si = dev->priv;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        /* Check TX status register */
        if (FIRITSR & (FIRITSR_TC | FIRITSR_TPE | FIRITSR_TFU)) {
                if (FIRITSR & FIRITSR_TC) {
                        MESSAGE("%s:Transmit completed and TX FIFO empty\n",
                                        __FUNCTION__);
                        FIRITSR = FIRITSR | FIRITSR_TC;
                }
                if (FIRITSR & FIRITSR_TPE) {
                        MESSAGE("%s:Transmit completed and TX FIFO empty\n",
                                         __FUNCTION__);
                        FIRITSR = FIRITSR | FIRITSR_TPE; 
                }
                if (FIRITSR & FIRITSR_TFU) {
                        MESSAGE("%s:Transmit FIFO Underrun\n",
                                         __FUNCTION__);
                        FIRITSR = FIRITSR | FIRITSR_TFU; 
                }
                return IRQ_HANDLED;
        }
        /* Stop RX DMA */
        mxc_dma_stop(si->rxdma_ch);
        /* Check RX status register */
        if (FIRIRSR & FIRIRSR_RFO) {
                if (FIRIRSR & FIRIRSR_RFO) {
                        MESSAGE("%s: RX FIFO underrun\n", __FUNCTION__);
                        FIRIRSR = FIRIRSR | FIRIRSR_RFO;
                }
        }

        /* Check for CRC/Framing errors - Throw away the packet completely */
        if (FIRIRSR & (FIRIRSR_CRCE | FIRIRSR_DDE)) {
                si->stats.rx_errors++;
                if (FIRIRSR & FIRIRSR_DDE) {
                        si->stats.rx_frame_errors++;
                }
                /*
                 * Clear selected status bits now, so we
                 * don't miss them next time around.
                 */
                FIRIRCR = FIRIRCR | FIRIRCR_RE;
                FIRIRSR = FIRIRSR_CRCE | FIRIRSR_DDE;
        }
        if (FIRIRSR & FIRIRSR_RPE) {
                MESSAGE("%s:Packet End interrupt\n", __FUNCTION__);
                mxc_fir_error(si, dev);
        }
        /* No matter what happens, we must restart reception. */
        mxc_fir_rx_dma_start(si);

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return IRQ_HANDLED;
}

/*!
 * This function is called by SDMA Interrupt Service Routine to indicate
 * requested DMA transfer is completed.
 *
 * @param   id   channel number 
 */
static void mxc_fir_txdma_irq(void *id)
{
        struct net_device *dev = id;
        mxc_fir *si = dev->priv;
        struct sk_buff *skb = si->txskb;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        si->txskb = NULL;

        /* change speed if newspeed is set. */
        if (si->newspeed) {
                mxc_fir_set_speed(si, si->newspeed);
                si->newspeed = 0;
        }

        /*
         * Start reception. This disables the transmitter for
         * us. This will be using the existing RX buffer.
         */
        mxc_fir_rx_dma_start(si);

        /* Account and free the packet. */
        if (skb) {
                dma_unmap_single(si->dev, si->txbuf_dma, skb->len, 
                                 DMA_TO_DEVICE);
                si->stats.tx_packets++;
                si->stats.tx_bytes += skb->len;
                dev_kfree_skb_irq(skb);
        }

        /*
         * Make sure that the TX queue is available for sending
         * TX has priority over RX at all times.
         */
        netif_wake_queue(dev);
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
}

/*!
 * This function is called by Linux IrDA network subsystem to
 * transmit the Infrared data packet. The TX DMA channel is configured
 * to transfer SK buffer data to FIRI TX FIFO along with DMA transfer
 * completion routine.
 *
 * @param   skb   The packet that is queued to be sent
 * @param   dev   net_device structure.
 *
 * @return  The function returns 0 on success and a non-zero value on
 *          failure.
 */
static int mxc_fir_hard_xmit(struct sk_buff *skb, struct net_device *dev)
{
        mxc_fir *si = dev->priv;
        int speed = irda_get_next_speed(skb);
        int i, mtt;
        dma_request_t dma_cfg;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        /*
         * 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 != si->speed && speed != -1) {
                si->newspeed = speed;
        }

        /* If this is an empty frame, we can bypass a lot. */
        if (skb->len == 0) {
                if (si->newspeed) {
                        si->newspeed = 0;
                        mxc_fir_set_speed(si, speed);
                }
                dev_kfree_skb(skb);
                return 0;
        }

        mtt = irda_get_mtt(skb);

        /* We must not be transmitting... */
        netif_stop_queue(dev);
        /*
         * if (si->txskb) {
         *      BUG();
         * }
         */      
        si->txskb = skb;
        si->txbuf_dma = dma_map_single(si->dev, skb->data,
                        skb->len, DMA_TO_DEVICE);
        /*
         * Configure DMA Tx Channel for source and destination addresses,
         * Number of bytes in SK buffer to transfer and Transfer complete
         * callback function.
         */
        dma_cfg.sourceAddr = (__u8*)&si->txbuf_dma;
        /*
         * Need to mention this ?
         * dma_cfg.destAddr = (__u8*) &(FIRITXFIFO);
         */
        dma_cfg.count =   skb->len;
        dma_cfg.callback = mxc_fir_txdma_irq;
        mxc_dma_set_config(si->txdma_ch, &dma_cfg);

        mxc_dma_start(si->txdma_ch);

        for (i = 0; i < skb->len; i++) {
                (i % 64) ? MESSAGE("%2x ", skb->data[i]) : 
                           MESSAGE("%2x \n", skb->data[i]) ;
                /* FIRITXFIFO = skb->data[i]; */
        }
        MESSAGE("\n");

        /*
         * 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) {
                udelay(mtt);
        }
        /* Enable Transmitter */
        FIRITCR = FIRITCR | FIRITCR_TE;

        dev->trans_start = jiffies;
        /*
         * Since i don't get DMA callback where i enable netif
         * later it has be removed
         */
        netif_wake_queue(dev);

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;
}

/*!
 * This function handles network interface ioctls passed to this driver..
 *
 * @param   dev   net device structure
 * @param   ifreq user request data
 * @param   cmd   command issued 
 *
 * @return  The function returns 0 on success and a non-zero value on
 *           failure.
 */
static int mxc_fir_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd)
{
        struct if_irda_req *rq = (struct if_irda_req *)ifreq;
        mxc_fir *si = dev->priv;
        int ret = -EOPNOTSUPP;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        switch (cmd) {
        /* This function will be used by IrLAP to change the speed */
        case SIOCSBANDWIDTH:
                MESSAGE("%s:with cmd SIOCSBANDWIDTH\n", __FUNCTION__);
                if (capable(CAP_NET_ADMIN)) {
                        /*
                         * We are unable to set the speed if the
                         * device is not running.
                         */
                        if (si->open) {
                                ret = mxc_fir_set_speed(si, rq->ifr_baudrate);
                        } else {
                                MESSAGE("mxc_fir_ioctl: SIOCSBANDWIDTH:\
                                         !netif_running\n");
                                ret = 0;
                        }
                }
                break;
                
        case SIOCSMEDIABUSY:
                MESSAGE("%s:with cmd SIOCSMEDIABUSY\n", __FUNCTION__);
                ret = -EPERM;
                if (capable(CAP_NET_ADMIN)) {
                        irda_device_set_media_busy(dev, TRUE);
                        ret = 0;
                }
                break;
                
        case SIOCGRECEIVING:
                MESSAGE("%s:with cmd SIOCGRECEIVING\n",__FUNCTION__);
                rq->ifr_receiving = FIRIRSR & FIRIRSR_PAS;
                ret = 0;
                break;

        default:
                break;
        }

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return ret;
}

/*!
 * Kernel interface routine to get current statistics of the device 
 * which includes the number bytes/packets transmitted/received, 
 * receive errors, CRC errors, framing errors etc.
 *
 * @param  dev  the net_device structure
 *
 * @return This function returns IrDA network statistics
 */
static struct net_device_stats *mxc_fir_stats(struct net_device *dev)
{
        mxc_fir *si = dev->priv;
        return &si->stats;
}

/*!
 * When an ifconfig is issued which changes the device flag to include
 * IFF_UP this function is called. It is only called when the change
 * occurs, not when the interface remains up. The function grabs the interrupt
 * resources and registers FIRI interrupt service routines, requests for DMA
 * channels, configures the DMA channel. It then initializes  the IOMUX 
 * registers to configure the pins for FIRI signals and finally initializes the
 * various FIRI registers and enables the port for reception.
 *
 * @param   dev   net device structure that is being opened
 * 
 * @return  The function returns 0 for a successful open and non-zero value 
 *          on failure.
 */
static int mxc_fir_start(struct net_device *dev)
{
        mxc_fir *si = dev->priv;
        dma_channel_params params; 
        int err;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        err = request_irq(dev->irq, mxc_fir_irq, 0, dev->name, dev);
        if (err) {
                ERROR("request_irq() Failed with %d\n",err);
                goto err_irq;
        }
        /* Dynamic allocation of DMA channel */
        si->rxdma_ch = 0;
        if ((err = mxc_request_dma(&si->rxdma_ch, "MXC FIRI RX")) < 0) { 
                ERROR("%s:err = %d\n", __FUNCTION__, err);
                goto err_rx_dma;
        } else {
                MESSAGE("%s:got channel %d\n", __FUNCTION__, si->rxdma_ch);
        }
        /* Setup DMA Channel parameters */
        params.watermark_level = 4;
        params.peripheral_type = FIRI;
        params.transfer_type   = per_2_emi;
        params.per_address     = (__u32) FIRI_BASE_ADDR;
        params.event_id        = DMA_REQ_FIRI_RX;
        params.bd_number       = 0;
        params.word_size       = TRANSFER_32BIT;
        params.error_callback  = mxc_fir_dma_err_irq;

        if ((err = mxc_dma_setup_channel(si->rxdma_ch, &params)) < 0) {
                ERROR("%s:err = %d\n", __FUNCTION__, err);
                goto err_rx_dma;
        } else {
                MESSAGE("%s:for %d success\n", __FUNCTION__, 
                                si->rxdma_ch);
        }         
        si->txdma_ch = 0;
        if ((err = mxc_request_dma(&si->txdma_ch, "MXC FIRI RX")) < 0) { 
                ERROR("%s:err = %d\n", __FUNCTION__, err);
                goto err_tx_dma;
        } else {
                MESSAGE("%s:got channel %d\n", __FUNCTION__, 
                                si->txdma_ch);
        }
        params.watermark_level = 4;
        params.peripheral_type = FIRI;
        params.transfer_type   = emi_2_per;
        params.per_address     = (__u32) FIRI_BASE_ADDR;
        params.event_id        = DMA_REQ_FIRI_TX;
        params.bd_number       = 0;
        params.word_size       = TRANSFER_32BIT;
        params.error_callback  = mxc_fir_dma_err_irq;

        if ((err = mxc_dma_setup_channel(si->txdma_ch, &params)) < 0) {
                ERROR("%s:err = %d\n", __FUNCTION__, err);
                goto err_tx_dma;
        } else {
                MESSAGE("%s:for %d success\n", __FUNCTION__, si->txdma_ch);
        }

        /* The interrupt must remain disabled for now. */
        disable_irq(dev->irq);

        /* Setup the FIRI port and configure FIRI registers. */
        err = mxc_fir_startup(si);
        if (err) {
                goto err_startup;
        }

        /* Open a new IrLAP layer instance. */
        si->irlap = irlap_open(dev, &si->qos, "mxc");
        err = -ENOMEM;
        if (!si->irlap) {
                goto err_irlap;
        }

        /* Now enable the interrupt and start the queue */
        si->open = 1;
        enable_irq(dev->irq);
        netif_start_queue(dev);

        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;

err_irlap:
        si->open = 0;
        mxc_fir_shutdown(si);
err_startup:
        mxc_free_dma(si->txdma_ch);
err_tx_dma:
        mxc_free_dma(si->rxdma_ch);
err_rx_dma:
        free_irq(dev->irq, dev);
err_irq:
        return err;
}

/*!
 * This function is called when IFF_UP flag has been cleared by the user via
 * the ifconfig irda0 down command. This function stops any further 
 * transmissions being queued, and then disables the interrupts. 
 * Finally it resets the device.
 * @param   dev   the net_device structure
 * 
 * @return  dev   the function always returns 0 indicating a success.
 */
static int mxc_fir_stop(struct net_device *dev)
{
        mxc_fir *si = dev->priv;

        disable_irq(dev->irq);
        mxc_fir_shutdown(si);

        /*
         * If we have been doing DMA receive, make sure we
         * tidy that up cleanly.
         */
        if (si->rxskb) {
                dma_unmap_single(si->dev, si->rxbuf_dma, FIR_MAX_RXLEN,
                                 DMA_FROM_DEVICE);
                dev_kfree_skb(si->rxskb);
                si->rxskb = NULL;
        }

        /* Stop IrLAP */
        if (si->irlap) {
                irlap_close(si->irlap);
                si->irlap = NULL;
        }

        netif_stop_queue(dev);
        si->open = 0;

        /* Free resources */
        mxc_free_dma(si->txdma_ch);
        mxc_free_dma(si->rxdma_ch);
        free_irq(dev->irq, dev);

        return 0;
}

/*!
 * This function is called during the driver binding process. 
 * This function requests for memory, initializes net_device structure and
 * registers with kernel. 
 *
 * @param   _dev   the device structure used to store device specific 
 *                information that is used by the suspend, resume and remove 
 *                functions
 *
 * @return  The function returns 0 on success and a non-zero value on failure
 */
static int mxc_fir_probe(struct device *_dev)
{
        struct platform_device *pdev = to_platform_device(_dev);
        struct net_device *dev;
        mxc_fir *si;
        unsigned int baudrate_mask;
        int err;

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
         /* Lock the FIRI device memory */
        err = request_mem_region(FIRI_BASE_ADDR, 0x1c, "IrDA") ? 0 : -EBUSY;
        if (err) {
                ERROR("Failed to create a new busy region\n");
                goto err_mem_1;
        }

        dev = alloc_irdadev(sizeof(mxc_fir));
        if (!dev) {
                ERROR("Failed to allocates and set up an IRDA device\n");
                goto err_mem_2;
        }

        si = dev->priv;
        si->dev = &pdev->dev;
        dev->hard_start_xmit    = mxc_fir_hard_xmit;
        dev->open               = mxc_fir_start;
        dev->stop               = mxc_fir_stop;
        dev->do_ioctl           = mxc_fir_ioctl;
        dev->get_stats          = mxc_fir_stats;
        dev->irq                = FIRI_INT;

        irda_init_max_qos_capabilies(&si->qos);

        /*
         * We support MIR(0.576 and 1.152 Mbps) and FIR(4 Mbps)
         * Min Turn Time set to 1ms or greater.
         */
        baudrate_mask = IR_576000 | IR_1152000;
        baudrate_mask |= IR_4000000 << 8;

        si->qos.baud_rate.bits &= baudrate_mask;
        si->qos.min_turn_time.bits = 7;

        irda_qos_bits_to_value(&si->qos);

        err = register_netdev(dev);
        if (err == 0) {
                dev_set_drvdata(&pdev->dev, si);
        } else {
                ERROR("%s:failed to register_netdev\n",__FUNCTION__);
                free_netdev(dev);
err_mem_2:
                release_mem_region(FIRI_BASE_ADDR, 0x1C);
        }
err_mem_1:
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return err;
}

/*!
 * Dissociates the driver from the FIRI device. Removes the appropriate FIRI
 * port structure from the kernel.
 *
 * @param   _dev   the device structure used to give information on which FIRI
 *                to remove
 *
 * @return  The function always returns 0. 
 */
static int mxc_fir_remove(struct device *_dev)
{
        struct net_device *dev = dev_get_drvdata(_dev);

        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        if (dev) {
                unregister_netdev(dev);
                free_netdev(dev);
        }
        release_mem_region(FIRI_BASE_ADDR, 0x1C);
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return 0;
}

/*!
 * This structure contains pointers to the power management callback functions.
 */
static struct device_driver mxcfir_driver = {
        .name           = "mxc-fir",
        .bus            = &platform_bus_type,
        .probe          = mxc_fir_probe,
        .remove         = mxc_fir_remove,
        .suspend        = mxc_fir_suspend,
        .resume         = mxc_fir_resume,
};

/*!
 * This is platform device structure for adding FIRI 
 */
static struct platform_device mxcfir_device = {
        .name           = "mxc-fir",
        .id             = 0,
};

/*!
 * This function is used to initialize the FIRI driver module. The function
 * registers the power management callback functions with the kernel and also
 * registers the FIRI callback functions.
 *
 * @return  The function returns 0 on success and a non-zero value on failure.
 */
static int __init mxc_fir_init(void)
{
        int ret;
        IRDA_DEBUG(KERN_INFO, "IN %s()\n", __FUNCTION__);
        ret = driver_register(&mxcfir_driver);
        if (ret == 0) {
                ret = platform_device_register(&mxcfir_device);
                if (ret) {
                        driver_unregister(&mxcfir_driver);
                }
        }
        IRDA_DEBUG(KERN_INFO, "OUT %s()\n", __FUNCTION__);
        return ret;
}

/*!
 * This function is used to cleanup all resources before the driver exits.
 */ 
static void __exit mxc_fir_exit(void)
{
        driver_unregister(&mxcfir_driver);
        platform_device_unregister(&mxcfir_device);
}

module_init(mxc_fir_init);
module_exit(mxc_fir_exit);

MODULE_AUTHOR("Freescale Semiconductor");
MODULE_DESCRIPTION("MXC FIRI driver");
MODULE_LICENSE("GPL");
