/*
 *  linux/drivers/mmc/imxmmc.c - Motorola i.MX MMCI driver
 *
 *  Copyright (C) 2004 Sascha Hauer, Pengutronix <sascha@saschahauer.de>
 *
 *  derived from pxamci.c by Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

/*
 * 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_mmc.c
 *
 * @brief Driver for the Freescale Semiconductor MXC SDHC modules.
 *
 * This driver code is based on imxmmc.c, by Sascha Hauer,
 * Pengutronix <sascha@saschahauer.de>. This driver supports both Secure Digital
 * Host Controller modules (SDHC1 and SDHC2) of MXC. SDHC is also referred as
 * MMC/SD controller. This code is not tested for SD cards.
 *
 * @ingroup MMC_SD
 */

/*
 * Include Files
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/blkdev.h>
#include <linux/dma-mapping.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/protocol.h>

#include <asm/dma.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/sizes.h>
#include <asm/delay.h>

#include "mxc_mmc.h"
#include <asm/arch/spba.h>
#include <asm/arch/gpio.h>
#include <asm/arch/clock.h>

#ifdef CONFIG_MMC_DEBUG
#define DBG(x...)       printk(x)
#else
#define DBG(x...)       do { } while (0)
#endif

/*
 * This define is used to test the driver without using SDMA
 */

/*
 * SDMAD dependeny disabled for L26.1.8 release.
 *
 * #ifdef CONFIG_MXC_SDMA_API
 * #define MXC_MMC_DMA_ENABLE
 * #endif
 */

/*!
 * Maxumum length of s/g list, only length of 1 is currently supported
 */
#define NR_SG   1

#define MXC_MMC_DMA_ENABLE
#ifdef CONFIG_MMC_DEBUG
static void dump_cmd(struct mmc_command *cmd)
{
        printk("%s: CMD: opcode: %d ", DRIVER_NAME, cmd->opcode);
        printk("arg: 0x%08x ", cmd->arg);
        printk("flags: 0x%08x\n", cmd->flags);
}

static void dump_status(int sts)
{
        unsigned int bitset;
        printk("status: ");
        while (sts) {
                /* Find the next bit set */
                bitset = sts & ~(sts - 1);
                switch(bitset) {
                case STATUS_CARD_INSERTION:
                        printk("CARD_INSERTION|");
                        break;
                case STATUS_CARD_REMOVAL:
                        printk("CARD_REMOVAL |");
                        break;
                case STATUS_YBUF_EMPTY:
                        printk("YBUF_EMPTY |");
                        break;
                case STATUS_XBUF_EMPTY:
                        printk("XBUF_EMPTY |");
                        break;
                case STATUS_YBUF_FULL:
                        printk("YBUF_FULL |");
                        break;
                case STATUS_XBUF_FULL:
                        printk("XBUF_FULL |");
                        break;
                case STATUS_BUF_UND_RUN:
                        printk("BUF_UND_RUN |");
                        break;
                case STATUS_BUF_OVFL:
                        printk("BUF_OVFL |");
                        break;
                case STATUS_READ_OP_DONE:
                        printk("READ_OP_DONE |");
                        break;
                case STATUS_WR_CRC_ERROR_CODE_MASK:
                        printk("WR_CRC_ERROR_CODE |");
                        break;
                case STATUS_READ_CRC_ERR:
                        printk("READ_CRC_ERR |");
                        break;
                case STATUS_WRITE_CRC_ERR:
                        printk("WRITE_CRC_ERR |");
                        break;
                case STATUS_SDIO_INT_ACTIVE:
                        printk("SDIO_INT_ACTIVE |");
                        break;
                case STATUS_END_CMD_RESP:
                        printk("END_CMD_RESP |");
                        break;
                case STATUS_WRITE_OP_DONE:
                        printk("WRITE_OP_DONE |");
                        break;
                case STATUS_CARD_BUS_CLK_RUN:
                        printk("CARD_BUS_CLK_RUN |");
                        break;
                case STATUS_BUF_READ_RDY:
                        printk("BUF_READ_RDY |");
                        break;
                case STATUS_BUF_WRITE_RDY:
                        printk("BUF_WRITE_RDY |");
                        break;
                case STATUS_RESP_CRC_ERR:
                        printk("RESP_CRC_ERR |");
                        break;
                case STATUS_TIME_OUT_RESP:
                        printk("TIME_OUT_RESP |");
                        break;
                case STATUS_TIME_OUT_READ:
                        printk("TIME_OUT_READ |");
                        break;
                default:
                        printk("Ivalid Status Register value");
                        break;
                }
                sts &= ~bitset;
        }
        printk("\n");
}
#endif

/*!
 * This structure is a way for the low level driver to define their own
 * \b mmc_host structure. This structure includes the core \b mmc_host
 * structure that is provided by Linux MMC/SD Bus protocol driver as an
 * element and has other elements that are specifically required by this
 * low-level driver.
 */
struct mxcmci_host {
        /*!
         * The mmc structure holds all the information about the device
         * structure, current SDHC io bus settings, the current OCR setting,
         * devices attached to this host, and so on.
         */
        struct mmc_host         *mmc;

        /*!
         * This variable is used for locking the host data structure from
         * multiple access.
         */
        spinlock_t              lock;

        /*!
         * Resource structure, which will maintain base addresses and IRQs.
         */
        struct resource         *res;

        /*!
         * Base address of SDHC, used in readl and writel.
         */
        void                    *base;

        /*!
         * SDHC IRQ number.
         */
        int                     irq;

        /*!
         * Clock id to hold ipg_perclk.
         */
        enum mxc_clocks        clock_id;
        /*!
         * DMA Event number.
         */
        int                     event_id;

        /*!
         * DMA channel number.
         */
        int                     dma;

        /*!
         * Holds the Interrupt Control Register value.
         */
        unsigned int            icntr;

        /*!
         * Pointer to hold MMC/SD request.
         */
        struct mmc_request      *req;

        /*!
         * Pointer to hold MMC/SD command.
         */
        struct mmc_command      *cmd;

        /*!
         * Pointer to hold MMC/SD data.
         */
        struct mmc_data         *data;

        /*!
         * Holds the number of bytes to transfer using SDMA.
         */
        unsigned int            dma_size;

        /*!
         * Value to store in Command and Data Control Register 
         * - currently unused
         */
        unsigned int            cmdat;

        /*!
         * Power mode - currently unused
         */
        unsigned int            power_mode;

        /*!
         * DMA address for scatter-gather transfers
         */
        dma_addr_t              sg_dma;

        /*!
         * Length of the scatter-gather list
         */
        unsigned int            dma_len;

        /*!
         * Holds the direction of data transfer.
         */
        unsigned int            dma_dir;

};

extern void gpio_sdhc_active(int module);
extern void gpio_sdhc_inactive(int module);
extern int sdhc_intr_setup(void *host, gpio_irq_handler handler);
extern void sdhc_intr_clear(int *flag);
extern unsigned int sdhc_get_min_clock(enum mxc_clocks clk);
extern unsigned int sdhc_get_max_clock(enum mxc_clocks clk);
extern int sdhc_find_card(void);

#ifdef MXC_MMC_DMA_ENABLE
static void mxcmci_dma_irq(void *devid);
#endif

/*!
 * This function sets the SDHC register to stop the clock and waits for the
 * clock stop indication.
 */
static void mxcmci_stop_clock(struct mxcmci_host *host)
{
        int wait_cnt;
        while (1) {
                writel(STR_STP_CLK_STOP_CLK, host->base + MMC_STR_STP_CLK);
                wait_cnt = 0;
                while ((readl(host->base + MMC_STATUS) & 
                        STATUS_CARD_BUS_CLK_RUN) && 
                        (wait_cnt++ < 100));
                
                if(!(readl(host->base + MMC_STATUS) & STATUS_CARD_BUS_CLK_RUN) )
                        break;
        }
}

/* Wait count to start the clock */
#define CMD_WAIT_CNT 100
/*!
 * This function sets the SDHC register to start the clock and waits for the
 * clock start indication. When the clock starts SDHC module starts processing
 * the command in CMD Register with arguments in ARG Register.
 *
 * @param host Pointer to MMC/SD host structure
 */
static void mxcmci_start_clock(struct mxcmci_host *host)
{
        int wait_cnt;
        while (1) {
                writel(STR_STP_CLK_START_CLK, host->base + MMC_STR_STP_CLK);
                wait_cnt = 0;
                while (!(readl(host->base + MMC_STATUS) & 
                         STATUS_CARD_BUS_CLK_RUN) && 
                         (wait_cnt++ < CMD_WAIT_CNT)) {
                        /* Do Nothing */
                }

                if (readl(host->base + MMC_STATUS) & STATUS_CARD_BUS_CLK_RUN) {
                        break;
                }
        }
        DBG("%s:CLK_RATE: 0x%08x\n", DRIVER_NAME,
                        readl(host->base + MMC_CLK_RATE));
}

/*!
 * This function resets the SDHC host.
 *
 * @param host  Pointer to MMC/SD  host structure
 */
static void mxcmci_softreset(struct mxcmci_host *host)
{
        /* reset sequence */
        writel(0x8, host->base + MMC_STR_STP_CLK);
        writel(0x9, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x1, host->base + MMC_STR_STP_CLK);
        writel(0x3f, host->base + MMC_CLK_RATE);

        writel(0xff, host->base + MMC_RES_TO);
        writel(512, host->base + MMC_BLK_LEN);
        writel(1, host->base + MMC_NOB);
}

/*!
 * This function is called to setup SDHC register for data transfer.
 * The function allocates DMA buffers, configures the SDMA channel.
 * Start the SDMA channel to transfer data. When DMA is not enabled this
 * function set ups only Number of Block and Block Length registers.
 *
 * @param host  Pointer to MMC/SD host structure
 * @param data  Pointer to MMC/SD data structure
 */
static void mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data)
{
        unsigned int nob = data->blocks;

#ifdef MXC_MMC_DMA_ENABLE
        dma_request_t sdma_request;
        dma_channel_params params;
#endif
        if (data->flags & MMC_DATA_STREAM) {
                nob = 0xffff;
        }

        host->data = data;

        writel(nob, host->base + MMC_NOB);
        writel(1 << data->blksz_bits, host->base + MMC_BLK_LEN);

        host->dma_size = data->blocks << data->blksz_bits;
        DBG("%s:Request bytes to transfer:%d\n", DRIVER_NAME, host->dma_size);
#ifdef MXC_MMC_DMA_ENABLE
        if (data->flags & MMC_DATA_READ) {
                host->dma_dir = DMA_FROM_DEVICE;
        } else {
                host->dma_dir = DMA_TO_DEVICE;
        }
        host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
                                   host->dma_dir);
        if (data->flags & MMC_DATA_READ) {
                sdma_request.destAddr = (__u8 *)sg_dma_address(&data->sg[0]);
                params.transfer_type = per_2_emi;
        } else {
                sdma_request.sourceAddr = (__u8 *)sg_dma_address(&data->sg[0]);
                params.transfer_type = emi_2_per;
        }

        /*
         * Setup DMA Channel parameters
         */
        /* 16 bytes in 1-bit mode and 64 bytes in 4-bit mode */
        params.watermark_level = SDHC_MMC_WML;
        params.peripheral_type = MMC;
        params.per_address = host->res->start + MMC_BUFFER_ACCESS;
        params.event_id = host->event_id;
        params.bd_number = 1 /* host->dma_size / 256 */;
        params.word_size = TRANSFER_32BIT;
        params.callback = mxcmci_dma_irq ;
        params.arg = host;
        mxc_dma_setup_channel(host->dma, &params);

        sdma_request.count = host->dma_size;
        mxc_dma_set_config(host->dma, &sdma_request, 0);

        /* mxc_dma_start(host->dma); */
#endif
}

/*!
 * This function is called by \b mxcmmc_request() function to setup the SDHC
 * register to issue command. This function disables the card insertion and
 * removal detection interrupt.
 *
 * @param host  Pointer to MMC/SD host structure
 * @param cmd   Pointer to MMC/SD command structure
 * @param cmdat Value to store in Command and Data Control Register
 */
static void mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd,
                             unsigned int cmdat)
{
        WARN_ON(host->cmd != NULL);
        host->cmd = cmd;
        switch (cmd->flags & (MMC_RSP_MASK | MMC_RSP_CRC)) {
                case MMC_RSP_SHORT | MMC_RSP_CRC:
                        cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R1;
                        break;
                case MMC_RSP_SHORT:
                        cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R3;
                        break;
                case MMC_RSP_LONG | MMC_RSP_CRC:
                        cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R2;
                        break;
                default:
                        /* No Response required */
                        break;
        }

        if (cmd->opcode == MMC_GO_IDLE_STATE) {
                cmdat |= CMD_DAT_CONT_INIT; /* This command needs init */
        }

        writel(cmd->opcode, host->base + MMC_CMD);
        writel(cmd->arg, host->base + MMC_ARG);

        writel(cmdat, host->base + MMC_CMD_DAT_CONT);

        mxcmci_start_clock(host);
}

/*!
 * This function is called to complete the command request.
 * This function enables insertion or removal interrupt.
 *
 * @param host Pointer to MMC/SD host structure
 * @param req  Pointer to MMC/SD command request structure
 */
static void mxcmci_finish_request(struct mxcmci_host *host,
                                  struct mmc_request *req)
{

        host->req = NULL;
        host->cmd = NULL;
        host->data = NULL;
        mmc_request_done(host->mmc, req);
}

/*!
 * This function is called when the requested command is completed.
 * This function reads the response from the card and data if the command is for
 * data transfer. This function checks for CRC error in response FIFO or
 * data FIFO.
 *
 * @param host  Pointer to MMC/SD host structure
 * @param stat  Content of SDHC Status Register
 *
 * @return This function returns 0 if there is no pending command, otherwise 1
 * always.
 */
static int mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat)
{
        struct mmc_command *cmd = host->cmd;
#ifndef MXC_MMC_DMA_ENABLE
        struct mmc_data *data = host->data;
#endif
        int i;
        u32 a, b, c;

        if (!cmd) {
        /* There is no command for completion */
                return 0;
        }

        /* As this function finishes the command, initialize cmd to NULL */
        host->cmd = NULL;

        /* check for Time out errors */
        if (stat & STATUS_TIME_OUT_RESP) {
                DBG("%s: CMD TIMEOUT\n", DRIVER_NAME);
                cmd->error = MMC_ERR_TIMEOUT;
        } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
                printk("%s: cmd crc error\n", DRIVER_NAME);
                cmd->error = MMC_ERR_BADCRC;
        }

        /* Read response from the card */
        switch (cmd->flags & (MMC_RSP_MASK | MMC_RSP_CRC)) {
                case MMC_RSP_SHORT | MMC_RSP_CRC:
                        a = readl(host->base + MMC_RES_FIFO) & 0xffff;
                        b = readl(host->base + MMC_RES_FIFO) & 0xffff;
                        c = readl(host->base + MMC_RES_FIFO) & 0xffff;
                        cmd->resp[0] = a << 24 | b << 8 | c >> 8;
                        break;
                case MMC_RSP_SHORT:
                        a = readl(host->base + MMC_RES_FIFO) & 0xffff;
                        b = readl(host->base + MMC_RES_FIFO) & 0xffff;
                        c = readl(host->base + MMC_RES_FIFO) & 0xffff;
                        cmd->resp[0] = a << 24 | b << 8 | c >> 8;
                        break;
                case MMC_RSP_LONG | MMC_RSP_CRC:
                        for (i = 0; i < 4; i++) {
                                a = readl(host->base + MMC_RES_FIFO) &
                                        0xffff;
                                b = readl(host->base + MMC_RES_FIFO) &
                                        0xffff;
                                cmd->resp[i] = a << 16 | b;
                        }
                        break;
                default:
                        break;
        }

        DBG("%s: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", DRIVER_NAME, cmd->resp[0],
                        cmd->resp[1], cmd->resp[2], cmd->resp[3]);

        if (host->data && cmd->error == MMC_ERR_NONE) {
                /* The command is associated with data transfer */
#ifdef MXC_MMC_DMA_ENABLE
                mxc_dma_start(host->dma);                
#endif
#ifndef MXC_MMC_DMA_ENABLE /* Manual tranfer of data */
                unsigned int i = 0, status;
                unsigned long *buf = (unsigned long*)data->req->buffer;

                /* calculate the number of bytes requested for transfer */
                int no_of_bytes =  data->blocks << data->blksz_bits;
                int no_of_words = no_of_bytes / 4;
                DBG("no_of_words=%d\n", no_of_words);

                if (data->flags & MMC_DATA_READ) {
                        for (i = 0; i < no_of_words; i++) {
                                /* wait for buffers to be ready for read */
                                do {
                                        status = readl(host->base + MMC_STATUS);
                                } while (!(status & STATUS_BUF_READ_RDY));

                                udelay(1);

                                /* read 32 bit data */
                                *buf++ = readl(host->base + MMC_BUFFER_ACCESS);
                                if (readl(host->base + MMC_STATUS) &
                                    STATUS_READ_OP_DONE) {
                                        DBG("i=%d\n", i);
                                        /* 
                                         * Check whether you completely read
                                         * no_of_words?
                                         * 
                                         */
                                        status = readl(host->base + MMC_STATUS);
                                        if(status & (STATUS_BUF_READ_RDY | 
                                                     STATUS_XBUF_FULL|
                                                     STATUS_YBUF_FULL)) {
                                                continue;
                                        } else {
                                                break;
                                        }
                                }
                        }

                        /* wait for read operation completion bit */
                        do {
                                status = readl(host->base + MMC_STATUS);
                        } while (!(status & STATUS_READ_OP_DONE));

                        /* check for time out and CRC errors */
                        status = readl(host->base + MMC_STATUS);
                        if (status & STATUS_READ_OP_DONE) {
                                if (status & STATUS_TIME_OUT_READ) {
                                        DBG("%s: Read time out occurred\n", 
                                             DRIVER_NAME);
                                        data->error = MMC_ERR_TIMEOUT;
                                        writel(STATUS_TIME_OUT_READ,
                                               host->base + MMC_STATUS);
                                }
                                else if (status & STATUS_READ_CRC_ERR) {
                                        DBG("%s: Read CRC error occurred\n", 
                                             DRIVER_NAME);
                                        data->error = MMC_ERR_BADCRC;
                                        writel(STATUS_READ_CRC_ERR,
                                               host->base + MMC_STATUS);
                                }
                                writel(STATUS_READ_OP_DONE,
                                       host->base + MMC_STATUS);
                        }
                        DBG("%s: Read %u words\n", DRIVER_NAME, i);
                } else {
                        for (i = 0;i < no_of_words; i++) {
                                status = readl(host->base + MMC_STATUS);

                                /* wait for buffers to be ready for write */
                                do {
                                        status = readl(host->base + MMC_STATUS);
                                } while (!(status & STATUS_BUF_WRITE_RDY));

                                /* write 32 bit data */
                                writel(*buf++, host->base + MMC_BUFFER_ACCESS);
                                if (readl(host->base + MMC_STATUS) &
                                    STATUS_WRITE_OP_DONE) {
                                        break;
                                }
                        }

                        /* wait for write operation completion bit */
                        do {
                                status = readl(host->base + MMC_STATUS);
                        } while (!(status & STATUS_WRITE_OP_DONE));

                        /* check for CRC errors */
                        if (status & STATUS_WRITE_OP_DONE) {
                                if (status & STATUS_WRITE_CRC_ERR){
                                        DBG("%s: Write CRC error occurred\n",
                                             DRIVER_NAME);
                                        data->error = MMC_ERR_BADCRC;
                                        writel(STATUS_WRITE_CRC_ERR,
                                               host->base + MMC_STATUS);
                                }
                                writel(STATUS_WRITE_OP_DONE, host->base +
                                       MMC_STATUS);
                        }
                        DBG("%s: Written %u words\n", DRIVER_NAME, i);
                }

                /* complete the data transfer request */
                mxcmci_data_done(host, status);
#endif
        } else {
                /* complete the command */
                mxcmci_stop_clock(host);
                mxcmci_finish_request(host, host->req);
        }

        return 1;
}

/*!
 * This function is called when the data transfer is completed either by SDMA
 * or by core. This function is called to clean up the DMA buffer and to send
 * STOP transmission command for commands to transfer data. This function
 * completes request issued by the MMC/SD core driver.
 *
 * @param host   pointer to MMC/SD host structure.
 * @param stat   content of SDHC Status Register
 *
 * @return This function returns 0 if no data transfer otherwise return 1
 * always.
 */
static int mxcmci_data_done(struct mxcmci_host *host, unsigned int stat)
{
        struct mmc_data *data = host->data;

        if (!data) {
                return 0;
        }

#ifdef MXC_MMC_DMA_ENABLE
        dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, 
                     host->dma_dir);
#endif
        if (readl(host->base + MMC_STATUS) & STATUS_ERR_MASK) {
                DBG("%s: request failed. status: 0x%08x\n",
                     DRIVER_NAME, readl(host->base + MMC_STATUS));
        }

        host->data = NULL;
        data->bytes_xfered = host->dma_size;

        if (host->req->stop && data->error == MMC_ERR_NONE) {
                mxcmci_stop_clock(host);
                mxcmci_start_cmd(host, host->req->stop, 0);
        } else {
                mxcmci_finish_request(host, host->req);
        }

        return 1;
}

/*!
 * GPIO interrupt service routine registered to handle the SDHC interrupts.
 * This interrupt routine handles card insertion and card removal interrupts. 
 *
 * @param   irq    the interrupt number
 * @param   devid  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_RETVAL(1) 
 */
static irqreturn_t mxcmci_gpio_irq(int irq, void *devid, struct pt_regs *regs)
{
        struct mxcmci_host *host = devid;
        static int flag = 0;
        DBG("%s: Card inserted/Removed \n", DRIVER_NAME);
        sdhc_intr_clear(&flag);
        mmc_detect_change(host->mmc);
        return IRQ_RETVAL(1);
}

/*!
 * Interrupt service routine registered to handle the SDHC interrupts.
 * This interrupt routine handles end of command, card insertion and
 * card removal interrupts. If the interrupt is card insertion or removal then
 * inform the MMC/SD core driver to detect the change in physical connections.
 * If the command is END_CMD_RESP read the Response FIFO. If DMA is not enabled
 * and data transfer is associated with the command then read or write the data
 * from or to the BUFFER_ACCESS FIFO.
 *
 * @param   irq    the interrupt number
 * @param   devid  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_RETVAL(1) if interrupt was handled,
 *          returns \b IRQ_RETVAL(0) if the interrupt was not handled.
 */
static irqreturn_t mxcmci_irq(int irq, void *devid, struct pt_regs *regs)
{
        struct mxcmci_host *host = devid;
        unsigned int status = 0;
        ulong flags;
       
        spin_lock_irqsave(&host->lock, flags);
        
        status = readl(host->base + MMC_STATUS);
#ifdef CONFIG_MMC_DEBUG
        dump_status(status);
#endif
        if (status & STATUS_END_CMD_RESP) {
                if (status & STATUS_RESP_CRC_ERR){
                    writel(STATUS_RESP_CRC_ERR, host->base + 
                           MMC_STATUS);
                }
                writel(STATUS_END_CMD_RESP, host->base + MMC_STATUS);
                if (status & STATUS_TIME_OUT_RESP){
                    writel(STATUS_TIME_OUT_RESP, host->base + 
                           MMC_STATUS);
                }
                        
                mxcmci_cmd_done(host, status);
        }

        spin_unlock_irqrestore(&host->lock, flags);
        
        return IRQ_RETVAL(1);
}

/*!
 * This function is called by MMC/SD Bus Protocol driver to issue a MMC
 * and SD commands to the SDHC.
 *
 * @param  mmc  Pointer to MMC/SD host structure
 * @param  req  Pointer to MMC/SD command request structure
 */
static void mxcmci_request(struct mmc_host *mmc, struct mmc_request *req)
{
        struct mxcmci_host *host = mmc_priv(mmc);
        /* Holds the value of Command and Data Control Register */
        unsigned long cmdat;

        WARN_ON(host->req != NULL);

        host->req = req;
#ifdef CONFIG_MMC_DEBUG
        dump_cmd(req->cmd);
#endif
        cmdat = 0;
        if (req->data) {
                mxcmci_setup_data(host, req->data);

                cmdat |= CMD_DAT_CONT_DATA_ENABLE;

                if (req->data->flags & MMC_DATA_WRITE) {
                        cmdat |= CMD_DAT_CONT_WRITE;
                }
                if (req->data->flags & MMC_DATA_STREAM) {
                        printk("MXC MMC does not support stream mode\n");
                }
        }
        mxcmci_start_cmd(host, req->cmd, cmdat);
}

/*!
 * This function is called by MMC/SD Bus Protocol driver to change the clock
 * speed of MMC or SD card
 *
 * @param mmc Pointer to MMC/SD host structure
 * @param ios Pointer to MMC/SD I/O type structure
 */
static void mxcmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
        struct mxcmci_host *host = mmc_priv(mmc);
        /*This variable holds the value of clock prescaler */
        int prescaler;
        int clk_rate = mxc_get_clocks(host->clock_id);

        DBG("%s: clock %u power %u vdd %u.%02u\n", DRIVER_NAME,
                        ios->clock, ios->power_mode, ios->vdd / 100,
                        ios->vdd % 100);

        /*
         *  Keep always, prescaler = 0
         *  vary clk_dev, start with 1 to F
         **/

        if (ios->clock) {
                unsigned int clk_dev;
                
                /* 
                 * when prescaler = 16, CLK_20M = CLK_DIV / 2
                 */
                if(ios->clock == mmc->f_min)
                        prescaler = 16; 
                else
                        prescaler = 0;

                /* clk_dev =1, CLK_DIV = ipg_perclk/2 */ 
                for (clk_dev = 1; clk_dev <= 0xF; clk_dev++) {
                        int x;
                        if(ios->clock == mmc->f_min)
                                x = (clk_rate / (clk_dev + 1)) / (prescaler*2);
                        else 
                                x = clk_rate / (clk_dev + 1);

                        DBG("x=%d, clock=%d %d\n", x, ios->clock, clk_dev);
                        if (x <= ios->clock) {
                                break;
                        }
                }

                mxcmci_stop_clock(host);
                writel((prescaler << 4) | (clk_dev+1), host->base + MMC_CLK_RATE);
                mxcmci_start_clock(host);
        } else {
                mxcmci_stop_clock(host);
        }
}

/*!
 * MMC/SD host operations structure.
 * These functions are registered with MMC/SD Bus protocol driver.
 */
static struct mmc_host_ops mxcmci_ops = {
        .request        = mxcmci_request,
        .set_ios        = mxcmci_set_ios
};


#ifdef MXC_MMC_DMA_ENABLE
/*!
 * This function is called by SDMA Interrupt Service Routine to indicate
 * requested DMA transfer is completed.
 *
 * @param   devid  pointer to device specific structure
 */
static void mxcmci_dma_irq(void *devid)
{
        struct mxcmci_host *host = devid;
        struct mmc_data *data = host->data;
        u32 status;
        ulong nob, blk_size, i, blk_len;
        /* This variable holds DMA configuration parameters */
        dma_request_t sdma_request;

        mxc_dma_stop(host->dma);

        mxc_dma_get_config(host->dma, &sdma_request, 0);
        if (sdma_request.bd_done == 1) {
                /* BD is not done yet */
                mxc_dma_start(host->dma);
                return;
        }
        
        DBG("%s: Transfered bytes:%d\n", DRIVER_NAME, sdma_request.count);
        nob = readl(host->base + MMC_REM_NOB);
        blk_size = readl(host->base + MMC_REM_BLK_SIZE);
        blk_len = readl(host->base + MMC_BLK_LEN);
        DBG("%s: REM_NOB:%lu REM_BLK_SIZE:%lu\n",DRIVER_NAME, nob, blk_size);
        i = 0;               
        do {
                status = readl(host->base + MMC_STATUS);
                if(i++ > 10000)
                        break;
        } while (!(status & (STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE)));
#ifdef CONFIG_MMC_DEBUG
        dump_status(status); 
#endif
        if (status & (STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE)) {
                DBG("%s:READ/WRITE OPERATION DONE\n", DRIVER_NAME);
                /* check for time out and CRC errors */
                status = readl(host->base + MMC_STATUS);
                if (status & STATUS_READ_OP_DONE) {
                        if (status & STATUS_TIME_OUT_READ) {
                                DBG("%s: Read time out occurred\n", 
                                                DRIVER_NAME);
                                data->error = MMC_ERR_TIMEOUT;
                                writel(STATUS_TIME_OUT_READ,
                                                host->base + MMC_STATUS);
                        }
                        else if (status & STATUS_READ_CRC_ERR) {
                                DBG("%s: Read CRC error occurred\n", 
                                                DRIVER_NAME);
                                data->error = MMC_ERR_BADCRC;
                                writel(STATUS_READ_CRC_ERR,
                                                host->base + MMC_STATUS);
                        }
                        writel(STATUS_READ_OP_DONE,
                                        host->base + MMC_STATUS);
                }

                /* check for CRC errors */
                if (status & STATUS_WRITE_OP_DONE) {
                        if (status & STATUS_WRITE_CRC_ERR){
                                DBG("%s: Write CRC error occurred\n",
                                                DRIVER_NAME);
                                data->error = MMC_ERR_BADCRC;
                                writel(STATUS_WRITE_CRC_ERR,
                                                host->base + MMC_STATUS);
                        }
                        writel(STATUS_WRITE_OP_DONE, host->base +
                                        MMC_STATUS);
                }
        }

        mxcmci_data_done(host, status);
}
#endif

/*!
 * Get ownership of this SDHC shared peripheral. Master is decided based on
 * whether we need DMA transfer or not.
 *
 * @param  pdev   the device structure used to give information on SDHC module.
 *
 * @return  The function returns 0 on success and a non-zero value on failure.
 */
static int mxcsdhc_getownership(struct platform_device *pdev)
{
        if (pdev->id == 0) {
                return spba_take_ownership(SPBA_SDHC1, 
                                           SPBA_MASTER_A | SPBA_MASTER_C);
        } else if (pdev->id == 1){
                return spba_take_ownership(SPBA_SDHC2, 
                                           SPBA_MASTER_A | SPBA_MASTER_C);
        } else {
                printk("%s:ERROR:Invalid SDHC number\n",DRIVER_NAME);
                return -1;
        }
        
}

/*!
 * Release ownership of this SDHC shared peripheral. Master is decided 
 * based on whether we need DMA transfer or not.
 *
 * @param  pdev   the device structure used to give information on SDHC module.
 *
 * @return  The function returns 0 on success and a non-zero value on failure.
 *
 */
static int mxcsdhc_relownership(struct platform_device *pdev)
{
        if (pdev->id == 0) {
                return spba_rel_ownership(SPBA_SDHC1, 
                                          SPBA_MASTER_A | SPBA_MASTER_C);
        } else if (pdev->id == 1){
                return spba_rel_ownership(SPBA_SDHC2, 
                                          SPBA_MASTER_A | SPBA_MASTER_C);
        } else {
                printk("%s:ERROR:Invalid SDHC number\n",DRIVER_NAME);
                return -1;
        }
        
}

/*!
 * This function is called during the driver binding process. Based on the SDHC
 * module that is being probed this function adds the appropriate SDHC module
 * structure in the core driver.
 *
 * @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 successful registration and initialization
 *          of SDHC module. Otherwise returns specific error code.
 */
static int mxcmci_probe(struct device *dev)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct mmc_host *mmc;
        struct mxcmci_host *host = NULL;
        struct resource *r;
        int ret = 0, irq;
#ifdef MXC_MMC_DMA_ENABLE
        int err;
        printk("%s: SDMA transfer is enabled\n", DRIVER_NAME);
#endif
        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        irq = platform_get_irq(pdev, 0);
        if (!r || irq == NO_IRQ) {
                return -ENXIO;
        }

        r = request_mem_region(r->start, SDHC_MEM_SIZE, DRIVER_NAME);
        if (!r) {
                return -EBUSY;
        }

        mmc = mmc_alloc_host(sizeof(struct mxcmci_host), dev);
        if (!mmc) {
                ret = -ENOMEM;
                goto out;
        }
        
        mmc->ops = &mxcmci_ops;
        mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
        mmc->max_phys_segs = NR_SG;

        host = mmc_priv(mmc);
        host->mmc = mmc;
        host->dma = -1;

        if (pdev->id == 0) {
                host->event_id = DMA_REQ_SDHC1;
                host->clock_id = SDHC1_CLK;
        } else {
                host->event_id = DMA_REQ_SDHC2;
                host->clock_id = SDHC2_CLK;
        }

        mmc->f_min = sdhc_get_min_clock(host->clock_id);
        mmc->f_max = sdhc_get_max_clock(host->clock_id);
        DBG("SDHC1 clock:%lu\n", mxc_get_clocks(host->clock_id));

        spin_lock_init(&host->lock);
        host->res = r;
        host->irq = irq;
        host->base = ioremap(r->start, SDHC_MEM_SIZE);

        if (!host->base) {
                ret = -ENOMEM;
                goto out;
        }

        /* Get the ownership */
        ret = mxcsdhc_getownership(pdev);
        if (ret != 0) {
                printk("%s: Failed to get SPBA ownership\n", DRIVER_NAME);
                return ret;
        }

        gpio_sdhc_active(pdev->id);
        ret = sdhc_intr_setup(host, mxcmci_gpio_irq);
        if (ret != 0) {
                goto out;
        }
        mxcmci_softreset(host);

        if (readl(host->base + MMC_REV_NO) != SDHC_REV_NO) {
                printk("%s: wrong rev.no. 0x%08x. aborting.\n",
                                DRIVER_NAME, MMC_REV_NO);
                goto out;
        }
        writel(READ_TO_VALUE, host->base + MMC_READ_TO);

        host->icntr =  INT_CNTR_END_CMD_RES;

        writel(host->icntr, host->base + MMC_INT_CNTR);

#ifdef MXC_MMC_DMA_ENABLE
        host->dma = 0;
        if ((err = mxc_request_dma(&host->dma, DRIVER_NAME)) < 0) {
                printk("%s: %s:err = %d\n", __FUNCTION__, DRIVER_NAME, err);
                goto out;
        }
#endif
        ret = request_irq(host->irq, mxcmci_irq, 0,
                          DRIVER_NAME, host);
        if (ret) {
                goto out;
        }

        dev_set_drvdata(dev, mmc);
        mmc_add_host(mmc);

        ret = sdhc_find_card();
        if(!ret) {
                printk("Card Already Present");
                mmc_detect_change(host->mmc);
        }

        return 0;

out:
        DBG("%s: Error in initializing....", DRIVER_NAME);
        if (host) {
#ifdef MXC_MMC_DMA_ENABLE
                if (host->dma >= 0) {
                        mxc_free_dma(host->dma);
                }
#endif
                if (host->base) {
                        iounmap(host->base);
                }
        }
        if (mmc) {
                mmc_free_host(mmc);
        }
        release_resource(r);
        return ret;
}

/*!
 * Dissociates the driver from the SDHC device. Removes the appropriate SDHC
 * module structure from the core driver.
 *
 * @param   dev   the device structure used to give information on which SDHC
 *                to remove
 *
 * @return  The function always returns 0.
 */
static int mxcmci_remove(struct device *dev)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct mmc_host *mmc = dev_get_drvdata(dev);
        int ret = 0;
        dev_set_drvdata(dev, NULL);

        if (mmc) {
                struct mxcmci_host *host = mmc_priv(mmc);

                mmc_remove_host(mmc);
                free_irq(host->irq, host);
                mxc_free_dma(host->dma);
                iounmap(host->base);
                release_resource(host->res);
                mmc_free_host(mmc);
                gpio_sdhc_inactive(pdev->id);
                
                /* release the ownership */
                ret = mxcsdhc_relownership(pdev);
                if (ret != 0) {
                        printk("%s: Failed to release SPBA ownership\n", 
                               DRIVER_NAME);
                        return ret;
                }

        }
        return 0;
}

#ifdef CONFIG_PM

/*!
 * This function is called to put the SDHC 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 SDHC
 *                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 mxcmci_suspend(struct device *dev, u32 state, u32 level)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct mmc_host *mmc = dev_get_drvdata(dev);
        struct mxcmci_host *host = mmc_priv(mmc);
        int ret = 0;

        if (mmc && level == SUSPEND_DISABLE) {
                ret = mmc_suspend_host(mmc, state);
        } else if (level == SUSPEND_POWER_DOWN) {
                mxc_clks_disable(host->clock_id);
                gpio_sdhc_inactive(pdev->id);
        }
        return ret;
}

/*!
 * This function is called to bring the SDHC 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 SDHC
 *                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 mxcmci_resume(struct device *dev, u32 level)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct mmc_host *mmc = dev_get_drvdata(dev);
         struct mxcmci_host *host = mmc_priv(mmc);
        int ret = 0;

        if (mmc && level == RESUME_ENABLE) {
                ret = mmc_resume_host(mmc);
        } else if (level == RESUME_POWER_ON) {
                mxc_clks_enable(host->clock_id);
                gpio_sdhc_active(pdev->id);
        }
        return ret;
}
#else
#define mxcmci_suspend  NULL
#define mxcmci_resume   NULL
#endif /* CONFIG_PM */

/*!
 * This structure contains pointers to the power management callback functions.
 */
static struct device_driver mxcmci_driver = {
        .name           = "mxcmci",
        .bus            = &platform_bus_type,
        .probe          = mxcmci_probe,
        .remove         = mxcmci_remove,
        .suspend        = mxcmci_suspend,
        .resume         = mxcmci_resume,
};

/*!
 * Resource definition for the SDHC1
 */
static struct resource mxcsdhc1_resources[] = {
        [0] = {
                .start  = MMC_SDHC1_BASE_ADDR,
                .end    = MMC_SDHC1_BASE_ADDR + SZ_16K - 1,
                .flags  = IORESOURCE_MEM,
        },
        [1] = {
                .start  = INT_MMC_SDHC1,
                .end    = INT_MMC_SDHC1,
                .flags  = IORESOURCE_IRQ,
        }
};

/*!
 * Resource definition for the SDHC2
 */
static struct resource mxcsdhc2_resources[] = {
        [0] = {
                .start  = MMC_SDHC2_BASE_ADDR,
                .end    = MMC_SDHC2_BASE_ADDR + SZ_16K - 1,
                .flags  = IORESOURCE_MEM,
        },
        [1] = {
                .start  = INT_MMC_SDHC2,
                .end    = INT_MMC_SDHC2,
                .flags  = IORESOURCE_IRQ,
        }
};

/*! Device Definition for MXC SDHC1 */
static struct platform_device mxcsdhc1_device = {
        .name           = "mxcmci",
        .id             = 0,
        .num_resources  = ARRAY_SIZE(mxcsdhc1_resources),
        .resource       = mxcsdhc1_resources,
};

/*! Device Definition for MXC SDHC2 */
static struct platform_device mxcsdhc2_device = {
        .name           = "mxcmci",
        .id             = 1,
        .num_resources  = ARRAY_SIZE(mxcsdhc2_resources),
        .resource       = mxcsdhc2_resources,
};

/*! Platform device definition for MXC SDHC's */
static struct platform_device *mxcsdhc_devices[SDHC_NR + 1] = {
        &mxcsdhc1_device,
        &mxcsdhc2_device
};

/*!
 * This function is used to initialize the MMC/SD driver module. The function
 * registers the power management callback functions with the kernel and also
 * registers the MMC/SD callback functions with the core MMC/SD driver.
 *
 * @return  The function returns 0 on success and a non-zero value on failure.
 */
static int __init mxcmci_init(void)
{
        int i, ret, fail = 0;

        printk(KERN_INFO "MXC MMC/SD driver\n");
        ret = driver_register(&mxcmci_driver);
        if (ret == 0) {
                for (i = 0; i < SDHC_NR; i++) {
                        /* Register all the devices */
                        ret = platform_device_register(mxcsdhc_devices[i]);
                        if (ret != 0) {
                                fail++;
                        }
                }
                /*
                 * Failed to register at least one device, hence unregister
                 * the device driver structure.
                 */
                if (fail == SDHC_NR) {
                        driver_unregister(&mxcmci_driver);
                }
        }

        return ret;
}

/*!
 * This function is used to cleanup all resources before the driver exits.
 */
static void __exit mxcmci_exit(void)
{
        int i;

        driver_unregister(&mxcmci_driver);
        for (i = 0; i < SDHC_NR; i++) {
                platform_device_unregister(mxcsdhc_devices[i]);
        }
}

module_init(mxcmci_init);
module_exit(mxcmci_exit);

MODULE_DESCRIPTION("MXC Multimedia Card Interface Driver");
MODULE_AUTHOR("Freescale Semiconductor");
MODULE_LICENSE("GPL");
