Merge branch 'msm-mmc_sdcc' of git://codeaurora.org/quic/kernel/dwalker/linux-msm
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 21 May 2010 21:38:35 +0000 (14:38 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 21 May 2010 21:38:35 +0000 (14:38 -0700)
* 'msm-mmc_sdcc' of git://codeaurora.org/quic/kernel/dwalker/linux-msm:
  drivers: mmc: msm_sdcc: Add EMBEDDED_SDIO support
  mmc: msm_sdcc: Fix issue where clocks could be disabled mid transaction
  mmc: msm_sdcc: Fix the dma exec function to use the proper delays
  mmc: msm_sdcc: Don't set host->curr.mrq until after we're sure the busclk timer won't fire
  mmc: msm_sdcc: Enable busclk idle timer for power savings
  mmc: msm_sdcc: Don't disable interrupts while suspending
  mmc: msm_sdcc: Fix issue where we might not end a sucessfull request
  mmc: msm_sdcc: Featurize busclock power save and disable it by default
  mmc: msm_sdcc: Fix bug where busclk expiry timer was not properly disabled
  mmc: msm_sdcc: Reduce command timeouts and improve reliability.
  mmc: msm_sdcc: Schedule clock disable after probe
  mmc: msm_sdcc: Wrap readl/writel calls with appropriate clk delays
  mmc: msm_sdcc: Driver clocking/irq improvements
  msm: Add 'execute' datamover callback
  mmc: msm_sdcc: Snoop SDIO_CCCR_ABORT register
  mmc: msm_sdcc: Clean up clock management and add a 10us delay after enabling clocks

1  2 
arch/arm/mach-msm/dma.c
arch/arm/mach-msm/include/mach/dma.h
drivers/mmc/host/msm_sdcc.c

diff --combined arch/arm/mach-msm/dma.c
@@@ -13,8 -13,6 +13,8 @@@
   *
   */
  
 +#include <linux/clk.h>
 +#include <linux/err.h>
  #include <linux/io.h>
  #include <linux/interrupt.h>
  #include <mach/dma.h>
@@@ -28,7 -26,6 +28,7 @@@ enum 
  };
  
  static DEFINE_SPINLOCK(msm_dmov_lock);
 +static struct clk *msm_dmov_clk;
  static unsigned int channel_active;
  static struct list_head ready_commands[MSM_DMOV_CHANNEL_COUNT];
  static struct list_head active_commands[MSM_DMOV_CHANNEL_COUNT];
@@@ -57,9 -54,6 +57,9 @@@ void msm_dmov_enqueue_cmd(unsigned id, 
        unsigned int status;
  
        spin_lock_irqsave(&msm_dmov_lock, irq_flags);
 +      if (!channel_active)
 +              clk_enable(msm_dmov_clk);
 +      dsb();
        status = readl(DMOV_STATUS(id));
        if (list_empty(&ready_commands[id]) &&
                (status & DMOV_STATUS_CMD_PTR_RDY)) {
@@@ -69,6 -63,8 +69,8 @@@
                        writel(DMOV_CONFIG_IRQ_EN, DMOV_CONFIG(id));
                }
  #endif
+               if (cmd->execute_func)
+                       cmd->execute_func(cmd);
                PRINT_IO("msm_dmov_enqueue_cmd(%d), start command, status %x\n", id, status);
                list_add_tail(&cmd->list, &active_commands[id]);
                if (!channel_active)
@@@ -76,8 -72,6 +78,8 @@@
                channel_active |= 1U << id;
                writel(cmd->cmdptr, DMOV_CMD_PTR(id));
        } else {
 +              if (!channel_active)
 +                      clk_disable(msm_dmov_clk);
                if (list_empty(&active_commands[id]))
                        PRINT_ERROR("msm_dmov_enqueue_cmd(%d), error datamover stalled, status %x\n", id, status);
  
@@@ -116,6 -110,7 +118,7 @@@ int msm_dmov_exec_cmd(unsigned id, unsi
  
        cmd.dmov_cmd.cmdptr = cmdptr;
        cmd.dmov_cmd.complete_func = dmov_exec_cmdptr_complete_func;
+       cmd.dmov_cmd.execute_func = NULL;
        cmd.id = id;
        init_completion(&cmd.complete);
  
@@@ -173,7 -168,6 +176,7 @@@ static irqreturn_t msm_datamover_irq_ha
                                        "for %p, result %x\n", id, cmd, ch_result);
                                if (cmd) {
                                        list_del(&cmd->list);
 +                                      dsb();
                                        cmd->complete_func(cmd, ch_result, NULL);
                                }
                        }
                                PRINT_FLOW("msm_datamover_irq_handler id %d, flush, result %x, flush0 %x\n", id, ch_result, errdata.flush[0]);
                                if (cmd) {
                                        list_del(&cmd->list);
 +                                      dsb();
                                        cmd->complete_func(cmd, ch_result, &errdata);
                                }
                        }
                                PRINT_ERROR("msm_datamover_irq_handler id %d, error, result %x, flush0 %x\n", id, ch_result, errdata.flush[0]);
                                if (cmd) {
                                        list_del(&cmd->list);
 +                                      dsb();
                                        cmd->complete_func(cmd, ch_result, &errdata);
                                }
                                /* this does not seem to work, once we get an error */
                                cmd = list_entry(ready_commands[id].next, typeof(*cmd), list);
                                list_del(&cmd->list);
                                list_add_tail(&cmd->list, &active_commands[id]);
+                               if (cmd->execute_func)
+                                       cmd->execute_func(cmd);
                                PRINT_FLOW("msm_datamover_irq_handler id %d, start command\n", id);
                                writel(cmd->cmdptr, DMOV_CMD_PTR(id));
                        }
                PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status);
        }
  
 -      if (!channel_active)
 -              disable_irq(INT_ADM_AARM);
 +      if (!channel_active) {
 +              disable_irq_nosync(INT_ADM_AARM);
 +              clk_disable(msm_dmov_clk);
 +      }
  
        spin_unlock_irqrestore(&msm_dmov_lock, irq_flags);
        return IRQ_HANDLED;
@@@ -243,17 -235,11 +248,17 @@@ static int __init msm_init_datamover(vo
  {
        int i;
        int ret;
 +      struct clk *clk;
 +
        for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) {
                INIT_LIST_HEAD(&ready_commands[i]);
                INIT_LIST_HEAD(&active_commands[i]);
                writel(DMOV_CONFIG_IRQ_EN | DMOV_CONFIG_FORCE_TOP_PTR_RSLT | DMOV_CONFIG_FORCE_FLUSH_RSLT, DMOV_CONFIG(i));
        }
 +      clk = clk_get(NULL, "adm_clk");
 +      if (IS_ERR(clk))
 +              return PTR_ERR(clk);
 +      msm_dmov_clk = clk;
        ret = request_irq(INT_ADM_AARM, msm_datamover_irq_handler, 0, "msmdatamover", NULL);
        if (ret)
                return ret;
@@@ -28,6 -28,8 +28,8 @@@ struct msm_dmov_cmd 
        void (*complete_func)(struct msm_dmov_cmd *cmd,
                              unsigned int result,
                              struct msm_dmov_errdata *err);
+       void (*execute_func)(struct msm_dmov_cmd *cmd);
+       void *data;
  };
  
  void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd);
@@@ -41,42 -43,40 +43,42 @@@ int msm_dmov_exec_cmd(unsigned id, unsi
  #define DMOV_SD2(off, ch) (MSM_DMOV_BASE + 0x0800 + (off) + ((ch) << 2))
  #define DMOV_SD3(off, ch) (MSM_DMOV_BASE + 0x0C00 + (off) + ((ch) << 2))
  
 -/* only security domain 3 is available to the ARM11
 - * SD0 -> mARM trusted, SD1 -> mARM nontrusted, SD2 -> aDSP, SD3 -> aARM
 - */
 +#if defined(CONFIG_ARCH_MSM7X30)
 +#define DMOV_SD_AARM DMOV_SD2
 +#else
 +#define DMOV_SD_AARM DMOV_SD3
 +#endif
  
 -#define DMOV_CMD_PTR(ch)      DMOV_SD3(0x000, ch)
 +#define DMOV_CMD_PTR(ch)      DMOV_SD_AARM(0x000, ch)
  #define DMOV_CMD_LIST         (0 << 29) /* does not work */
  #define DMOV_CMD_PTR_LIST     (1 << 29) /* works */
  #define DMOV_CMD_INPUT_CFG    (2 << 29) /* untested */
  #define DMOV_CMD_OUTPUT_CFG   (3 << 29) /* untested */
  #define DMOV_CMD_ADDR(addr)   ((addr) >> 3)
  
 -#define DMOV_RSLT(ch)         DMOV_SD3(0x040, ch)
 +#define DMOV_RSLT(ch)         DMOV_SD_AARM(0x040, ch)
  #define DMOV_RSLT_VALID       (1 << 31) /* 0 == host has empties result fifo */
  #define DMOV_RSLT_ERROR       (1 << 3)
  #define DMOV_RSLT_FLUSH       (1 << 2)
  #define DMOV_RSLT_DONE        (1 << 1)  /* top pointer done */
  #define DMOV_RSLT_USER        (1 << 0)  /* command with FR force result */
  
 -#define DMOV_FLUSH0(ch)       DMOV_SD3(0x080, ch)
 -#define DMOV_FLUSH1(ch)       DMOV_SD3(0x0C0, ch)
 -#define DMOV_FLUSH2(ch)       DMOV_SD3(0x100, ch)
 -#define DMOV_FLUSH3(ch)       DMOV_SD3(0x140, ch)
 -#define DMOV_FLUSH4(ch)       DMOV_SD3(0x180, ch)
 -#define DMOV_FLUSH5(ch)       DMOV_SD3(0x1C0, ch)
 +#define DMOV_FLUSH0(ch)       DMOV_SD_AARM(0x080, ch)
 +#define DMOV_FLUSH1(ch)       DMOV_SD_AARM(0x0C0, ch)
 +#define DMOV_FLUSH2(ch)       DMOV_SD_AARM(0x100, ch)
 +#define DMOV_FLUSH3(ch)       DMOV_SD_AARM(0x140, ch)
 +#define DMOV_FLUSH4(ch)       DMOV_SD_AARM(0x180, ch)
 +#define DMOV_FLUSH5(ch)       DMOV_SD_AARM(0x1C0, ch)
  
 -#define DMOV_STATUS(ch)       DMOV_SD3(0x200, ch)
 +#define DMOV_STATUS(ch)       DMOV_SD_AARM(0x200, ch)
  #define DMOV_STATUS_RSLT_COUNT(n)    (((n) >> 29))
  #define DMOV_STATUS_CMD_COUNT(n)     (((n) >> 27) & 3)
  #define DMOV_STATUS_RSLT_VALID       (1 << 1)
  #define DMOV_STATUS_CMD_PTR_RDY      (1 << 0)
  
 -#define DMOV_ISR              DMOV_SD3(0x380, 0)
 -
 -#define DMOV_CONFIG(ch)       DMOV_SD3(0x300, ch)
 +#define DMOV_ISR              DMOV_SD_AARM(0x380, 0)
 +  
 +#define DMOV_CONFIG(ch)       DMOV_SD_AARM(0x300, ch)
  #define DMOV_CONFIG_FORCE_TOP_PTR_RSLT (1 << 2)
  #define DMOV_CONFIG_FORCE_FLUSH_RSLT   (1 << 1)
  #define DMOV_CONFIG_IRQ_EN             (1 << 0)
@@@ -3,6 -3,7 +3,7 @@@
   *
   *  Copyright (C) 2007 Google Inc,
   *  Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
+  *  Copyright (C) 2009, Code Aurora Forum. All Rights Reserved.
   *
   * 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
@@@ -26,6 -27,7 +27,7 @@@
  #include <linux/log2.h>
  #include <linux/mmc/host.h>
  #include <linux/mmc/card.h>
+ #include <linux/mmc/sdio.h>
  #include <linux/clk.h>
  #include <linux/scatterlist.h>
  #include <linux/platform_device.h>
@@@ -33,7 -35,6 +35,7 @@@
  #include <linux/debugfs.h>
  #include <linux/io.h>
  #include <linux/memory.h>
 +#include <linux/gfp.h>
  
  #include <asm/cacheflush.h>
  #include <asm/div64.h>
@@@ -47,6 -48,8 +49,8 @@@
  
  #define DRIVER_NAME "msm-sdcc"
  
+ #define BUSCLK_PWRSAVE 1
+ #define BUSCLK_TIMEOUT (HZ)
  static unsigned int msmsdcc_fmin = 144000;
  static unsigned int msmsdcc_fmax = 50000000;
  static unsigned int msmsdcc_4bit = 1;
@@@ -57,6 -60,67 +61,67 @@@ static unsigned int msmsdcc_sdioirq
  #define PIO_SPINMAX 30
  #define CMD_SPINMAX 20
  
+ static inline void
+ msmsdcc_disable_clocks(struct msmsdcc_host *host, int deferr)
+ {
+       WARN_ON(!host->clks_on);
+       BUG_ON(host->curr.mrq);
+       if (deferr) {
+               mod_timer(&host->busclk_timer, jiffies + BUSCLK_TIMEOUT);
+       } else {
+               del_timer_sync(&host->busclk_timer);
+               /* Need to check clks_on again in case the busclk
+                * timer fired
+                */
+               if (host->clks_on) {
+                       clk_disable(host->clk);
+                       clk_disable(host->pclk);
+                       host->clks_on = 0;
+               }
+       }
+ }
+ static inline int
+ msmsdcc_enable_clocks(struct msmsdcc_host *host)
+ {
+       int rc;
+       del_timer_sync(&host->busclk_timer);
+       if (!host->clks_on) {
+               rc = clk_enable(host->pclk);
+               if (rc)
+                       return rc;
+               rc = clk_enable(host->clk);
+               if (rc) {
+                       clk_disable(host->pclk);
+                       return rc;
+               }
+               udelay(1 + ((3 * USEC_PER_SEC) /
+                      (host->clk_rate ? host->clk_rate : msmsdcc_fmin)));
+               host->clks_on = 1;
+       }
+       return 0;
+ }
+ static inline unsigned int
+ msmsdcc_readl(struct msmsdcc_host *host, unsigned int reg)
+ {
+       return readl(host->base + reg);
+ }
+ static inline void
+ msmsdcc_writel(struct msmsdcc_host *host, u32 data, unsigned int reg)
+ {
+       writel(data, host->base + reg);
+       /* 3 clk delay required! */
+       udelay(1 + ((3 * USEC_PER_SEC) /
+              (host->clk_rate ? host->clk_rate : msmsdcc_fmin)));
+ }
  static void
  msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd,
                      u32 c);
  static void
  msmsdcc_request_end(struct msmsdcc_host *host, struct mmc_request *mrq)
  {
-       writel(0, host->base + MMCICOMMAND);
        BUG_ON(host->curr.data);
  
        host->curr.mrq = NULL;
        if (mrq->cmd->error == -ETIMEDOUT)
                mdelay(5);
  
+ #if BUSCLK_PWRSAVE
+       msmsdcc_disable_clocks(host, 1);
+ #endif
        /*
         * Need to drop the host lock here; mmc_request_done may call
         * back into the driver...
  static void
  msmsdcc_stop_data(struct msmsdcc_host *host)
  {
-       writel(0, host->base + MMCIDATACTRL);
        host->curr.data = NULL;
        host->curr.got_dataend = host->curr.got_datablkend = 0;
  }
@@@ -109,6 -173,31 +174,31 @@@ uint32_t msmsdcc_fifo_addr(struct msmsd
        return 0;
  }
  
+ static inline void
+ msmsdcc_start_command_exec(struct msmsdcc_host *host, u32 arg, u32 c) {
+        msmsdcc_writel(host, arg, MMCIARGUMENT);
+        msmsdcc_writel(host, c, MMCICOMMAND);
+ }
+ static void
+ msmsdcc_dma_exec_func(struct msm_dmov_cmd *cmd)
+ {
+       struct msmsdcc_host *host = (struct msmsdcc_host *)cmd->data;
+       msmsdcc_writel(host, host->cmd_timeout, MMCIDATATIMER);
+       msmsdcc_writel(host, (unsigned int)host->curr.xfer_size,
+                      MMCIDATALENGTH);
+       msmsdcc_writel(host, host->cmd_pio_irqmask, MMCIMASK1);
+       msmsdcc_writel(host, host->cmd_datactrl, MMCIDATACTRL);
+       if (host->cmd_cmd) {
+               msmsdcc_start_command_exec(host,
+                                          (u32) host->cmd_cmd->arg,
+                                          (u32) host->cmd_c);
+       }
+       host->dma.active = 1;
+ }
  static void
  msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd,
                          unsigned int result,
        struct mmc_request      *mrq;
  
        spin_lock_irqsave(&host->lock, flags);
+       host->dma.active = 0;
        mrq = host->curr.mrq;
        BUG_ON(!mrq);
+       WARN_ON(!mrq->data);
  
        if (!(result & DMOV_RSLT_VALID)) {
                pr_err("msmsdcc: Invalid DataMover result\n");
                if (!mrq->data->error)
                        mrq->data->error = -EIO;
        }
-       host->dma.busy = 0;
        dma_unmap_sg(mmc_dev(host->mmc), host->dma.sg, host->dma.num_ents,
                     host->dma.dir);
  
        }
  
        host->dma.sg = NULL;
+       host->dma.busy = 0;
  
        if ((host->curr.got_dataend && host->curr.got_datablkend)
             || mrq->data->error) {
                if (!mrq->data->error)
                        host->curr.data_xfered = host->curr.xfer_size;
                if (!mrq->data->stop || mrq->cmd->error) {
-                       writel(0, host->base + MMCICOMMAND);
                        host->curr.mrq = NULL;
                        host->curr.cmd = NULL;
                        mrq->data->bytes_xfered = host->curr.data_xfered;
  
                        spin_unlock_irqrestore(&host->lock, flags);
+ #if BUSCLK_PWRSAVE
+                       msmsdcc_disable_clocks(host, 1);
+ #endif
                        mmc_request_done(host->mmc, mrq);
                        return;
                } else
@@@ -218,6 -312,8 +313,8 @@@ static int msmsdcc_config_dma(struct ms
        host->dma.sg = data->sg;
        host->dma.num_ents = data->sg_len;
  
+        BUG_ON(host->dma.num_ents > NR_SG); /* Prevent memory corruption */
        nc = host->dma.nc;
  
        switch (host->pdev_id) {
  
        host->curr.user_pages = 0;
  
-       n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg,
-                      host->dma.num_ents, host->dma.dir);
-       if (n != host->dma.num_ents) {
-               pr_err("%s: Unable to map in all sg elements\n",
-                      mmc_hostname(host->mmc));
-               host->dma.sg = NULL;
-               host->dma.num_ents = 0;
-               return -ENOMEM;
-       }
        box = &nc->cmd[0];
        for (i = 0; i < host->dma.num_ents; i++) {
                box->cmd = CMD_MODE_BOX;
  
-               if (i == (host->dma.num_ents - 1))
+       /* Initialize sg dma address */
+       sg->dma_address = page_to_dma(mmc_dev(host->mmc), sg_page(sg))
+                               + sg->offset;
+       if (i == (host->dma.num_ents - 1))
                        box->cmd |= CMD_LC;
                rows = (sg_dma_len(sg) % MCI_FIFOSIZE) ?
                        (sg_dma_len(sg) / MCI_FIFOSIZE) + 1 :
                               DMOV_CMD_ADDR(host->dma.cmdptr_busaddr);
        host->dma.hdr.complete_func = msmsdcc_dma_complete_func;
  
+       n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg,
+                       host->dma.num_ents, host->dma.dir);
+ /* dsb inside dma_map_sg will write nc out to mem as well */
+       if (n != host->dma.num_ents) {
+               printk(KERN_ERR "%s: Unable to map in all sg elements\n",
+                       mmc_hostname(host->mmc));
+               host->dma.sg = NULL;
+               host->dma.num_ents = 0;
+               return -ENOMEM;
+       }
+       return 0;
+ }
+ static int
+ snoop_cccr_abort(struct mmc_command *cmd)
+ {
+       if ((cmd->opcode == 52) &&
+           (cmd->arg & 0x80000000) &&
+           (((cmd->arg >> 9) & 0x1ffff) == SDIO_CCCR_ABORT))
+               return 1;
        return 0;
  }
  
  static void
- msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data)
+ msmsdcc_start_command_deferred(struct msmsdcc_host *host,
+                               struct mmc_command *cmd, u32 *c)
+ {
+       *c |= (cmd->opcode | MCI_CPSM_ENABLE);
+       if (cmd->flags & MMC_RSP_PRESENT) {
+               if (cmd->flags & MMC_RSP_136)
+                       *c |= MCI_CPSM_LONGRSP;
+               *c |= MCI_CPSM_RESPONSE;
+       }
+       if (/*interrupt*/0)
+               *c |= MCI_CPSM_INTERRUPT;
+       if ((((cmd->opcode == 17) || (cmd->opcode == 18))  ||
+            ((cmd->opcode == 24) || (cmd->opcode == 25))) ||
+             (cmd->opcode == 53))
+               *c |= MCI_CSPM_DATCMD;
+       if (cmd == cmd->mrq->stop)
+               *c |= MCI_CSPM_MCIABORT;
+       if (snoop_cccr_abort(cmd))
+               *c |= MCI_CSPM_MCIABORT;
+       if (host->curr.cmd != NULL) {
+               printk(KERN_ERR "%s: Overlapping command requests\n",
+                       mmc_hostname(host->mmc));
+       }
+       host->curr.cmd = cmd;
+ }
+ static void
+ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data,
+                       struct mmc_command *cmd, u32 c)
  {
        unsigned int datactrl, timeout;
        unsigned long long clks;
-       void __iomem *base = host->base;
        unsigned int pio_irqmask = 0;
  
        host->curr.data = data;
  
        memset(&host->pio, 0, sizeof(host->pio));
  
-       clks = (unsigned long long)data->timeout_ns * host->clk_rate;
-       do_div(clks, NSEC_PER_SEC);
-       timeout = data->timeout_clks + (unsigned int)clks;
-       writel(timeout, base + MMCIDATATIMER);
-       writel(host->curr.xfer_size, base + MMCIDATALENGTH);
        datactrl = MCI_DPSM_ENABLE | (data->blksz << 4);
  
        if (!msmsdcc_config_dma(host, data))
        if (data->flags & MMC_DATA_READ)
                datactrl |= MCI_DPSM_DIRECTION;
  
-       writel(pio_irqmask, base + MMCIMASK1);
-       writel(datactrl, base + MMCIDATACTRL);
+       clks = (unsigned long long)data->timeout_ns * host->clk_rate;
+       do_div(clks, NSEC_PER_SEC);
+       timeout = data->timeout_clks + (unsigned int)clks*2 ;
  
        if (datactrl & MCI_DPSM_DMAENABLE) {
+               /* Save parameters for the exec function */
+               host->cmd_timeout = timeout;
+               host->cmd_pio_irqmask = pio_irqmask;
+               host->cmd_datactrl = datactrl;
+               host->cmd_cmd = cmd;
+               host->dma.hdr.execute_func = msmsdcc_dma_exec_func;
+               host->dma.hdr.data = (void *)host;
                host->dma.busy = 1;
+               if (cmd) {
+                       msmsdcc_start_command_deferred(host, cmd, &c);
+                       host->cmd_c = c;
+               }
                msm_dmov_enqueue_cmd(host->dma.channel, &host->dma.hdr);
+       } else {
+               msmsdcc_writel(host, timeout, MMCIDATATIMER);
+               msmsdcc_writel(host, host->curr.xfer_size, MMCIDATALENGTH);
+               msmsdcc_writel(host, pio_irqmask, MMCIMASK1);
+               msmsdcc_writel(host, datactrl, MMCIDATACTRL);
+               if (cmd) {
+                       /* Daisy-chain the command if requested */
+                       msmsdcc_start_command(host, cmd, c);
+               }
        }
  }
  
  static void
  msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, u32 c)
  {
-       void __iomem *base = host->base;
-       if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
-               writel(0, base + MMCICOMMAND);
-               udelay(2 + ((5 * 1000000) / host->clk_rate));
-       }
-       c |= cmd->opcode | MCI_CPSM_ENABLE;
-       if (cmd->flags & MMC_RSP_PRESENT) {
-               if (cmd->flags & MMC_RSP_136)
-                       c |= MCI_CPSM_LONGRSP;
-               c |= MCI_CPSM_RESPONSE;
-       }
-       if (cmd->opcode == 17 || cmd->opcode == 18 ||
-           cmd->opcode == 24 || cmd->opcode == 25 ||
-           cmd->opcode == 53)
-               c |= MCI_CSPM_DATCMD;
        if (cmd == cmd->mrq->stop)
                c |= MCI_CSPM_MCIABORT;
  
-       host->curr.cmd = cmd;
        host->stats.cmds++;
  
-       writel(cmd->arg, base + MMCIARGUMENT);
-       writel(c, base + MMCICOMMAND);
+       msmsdcc_start_command_deferred(host, cmd, &c);
+       msmsdcc_start_command_exec(host, cmd->arg, c);
  }
  
  static void
@@@ -421,13 -562,11 +563,11 @@@ msmsdcc_data_err(struct msmsdcc_host *h
  static int
  msmsdcc_pio_read(struct msmsdcc_host *host, char *buffer, unsigned int remain)
  {
-       void __iomem    *base = host->base;
        uint32_t        *ptr = (uint32_t *) buffer;
        int             count = 0;
  
-       while (readl(base + MMCISTATUS) & MCI_RXDATAAVLBL) {
-               *ptr = readl(base + MMCIFIFO + (count % MCI_FIFOSIZE));
+       while (msmsdcc_readl(host, MMCISTATUS) & MCI_RXDATAAVLBL) {
+               *ptr = msmsdcc_readl(host, MMCIFIFO + (count % MCI_FIFOSIZE));
                ptr++;
                count += sizeof(uint32_t);
  
@@@ -459,7 -598,7 +599,7 @@@ msmsdcc_pio_write(struct msmsdcc_host *
                if (remain == 0)
                        break;
  
-               status = readl(base + MMCISTATUS);
+               status = msmsdcc_readl(host, MMCISTATUS);
        } while (status & MCI_TXFIFOHALFEMPTY);
  
        return ptr - buffer;
@@@ -469,7 -608,7 +609,7 @@@ static in
  msmsdcc_spin_on_status(struct msmsdcc_host *host, uint32_t mask, int maxspin)
  {
        while (maxspin) {
-               if ((readl(host->base + MMCISTATUS) & mask))
+               if ((msmsdcc_readl(host, MMCISTATUS) & mask))
                        return 0;
                udelay(1);
                --maxspin;
        return -ETIMEDOUT;
  }
  
- static int
+ static irqreturn_t
  msmsdcc_pio_irq(int irq, void *dev_id)
  {
        struct msmsdcc_host     *host = dev_id;
-       void __iomem            *base = host->base;
        uint32_t                status;
  
-       status = readl(base + MMCISTATUS);
+       status = msmsdcc_readl(host, MMCISTATUS);
  
        do {
                unsigned long flags;
                        host->pio.sg_off = 0;
                }
  
-               status = readl(base + MMCISTATUS);
+               status = msmsdcc_readl(host, MMCISTATUS);
        } while (1);
  
        if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE)
-               writel(MCI_RXDATAAVLBLMASK, base + MMCIMASK1);
+               msmsdcc_writel(host, MCI_RXDATAAVLBLMASK, MMCIMASK1);
  
        if (!host->curr.xfer_remain)
-               writel(0, base + MMCIMASK1);
+               msmsdcc_writel(host, 0, MMCIMASK1);
  
        return IRQ_HANDLED;
  }
  static void msmsdcc_do_cmdirq(struct msmsdcc_host *host, uint32_t status)
  {
        struct mmc_command *cmd = host->curr.cmd;
-       void __iomem       *base = host->base;
  
        host->curr.cmd = NULL;
-       cmd->resp[0] = readl(base + MMCIRESPONSE0);
-       cmd->resp[1] = readl(base + MMCIRESPONSE1);
-       cmd->resp[2] = readl(base + MMCIRESPONSE2);
-       cmd->resp[3] = readl(base + MMCIRESPONSE3);
+       cmd->resp[0] = msmsdcc_readl(host, MMCIRESPONSE0);
+       cmd->resp[1] = msmsdcc_readl(host, MMCIRESPONSE1);
+       cmd->resp[2] = msmsdcc_readl(host, MMCIRESPONSE2);
+       cmd->resp[3] = msmsdcc_readl(host, MMCIRESPONSE3);
  
-       del_timer(&host->command_timer);
        if (status & MCI_CMDTIMEOUT) {
                cmd->error = -ETIMEDOUT;
        } else if (status & MCI_CMDCRCFAIL &&
                        msmsdcc_request_end(host, cmd->mrq);
                } else /* host->data == NULL */
                        msmsdcc_request_end(host, cmd->mrq);
-       } else if (!(cmd->data->flags & MMC_DATA_READ))
-               msmsdcc_start_data(host, cmd->data);
+       } else if (cmd->data)
+               if (!(cmd->data->flags & MMC_DATA_READ))
+                       msmsdcc_start_data(host, cmd->data,
+                                               NULL, 0);
  }
  
  static void
@@@ -590,6 -728,11 +729,11 @@@ msmsdcc_handle_irq_data(struct msmsdcc_
  {
        struct mmc_data *data = host->curr.data;
  
+       if (status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL |
+                     MCI_CMDTIMEOUT) && host->curr.cmd) {
+               msmsdcc_do_cmdirq(host, status);
+       }
        if (!data)
                return;
  
                        msm_dmov_stop_cmd(host->dma.channel,
                                          &host->dma.hdr, 0);
                else {
-                       msmsdcc_stop_data(host);
+                       if (host->curr.data)
+                               msmsdcc_stop_data(host);
                        if (!data->stop)
                                msmsdcc_request_end(host, data->mrq);
                        else
@@@ -657,17 -801,18 +802,18 @@@ msmsdcc_irq(int irq, void *dev_id
        spin_lock(&host->lock);
  
        do {
-               status = readl(base + MMCISTATUS);
+               status = msmsdcc_readl(host, MMCISTATUS);
+               status &= (msmsdcc_readl(host, MMCIMASK0) |
+                                             MCI_DATABLOCKENDMASK);
+               msmsdcc_writel(host, status, MMCICLEAR);
  
-               status &= (readl(base + MMCIMASK0) | MCI_DATABLOCKENDMASK);
-               writel(status, base + MMCICLEAR);
+               if (status & MCI_SDIOINTR)
+                       status &= ~MCI_SDIOINTR;
  
-               msmsdcc_handle_irq_data(host, status, base);
+               if (!status)
+                       break;
  
-               if (status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL |
-                             MCI_CMDTIMEOUT) && host->curr.cmd) {
-                       msmsdcc_do_cmdirq(host, status);
-               }
+               msmsdcc_handle_irq_data(host, status, base);
  
                if (status & MCI_SDIOINTOPER) {
                        cardint = 1;
@@@ -714,24 -859,27 +860,27 @@@ msmsdcc_request(struct mmc_host *mmc, s
                return;
        }
  
+       msmsdcc_enable_clocks(host);
        host->curr.mrq = mrq;
  
        if (mrq->data && mrq->data->flags & MMC_DATA_READ)
-               msmsdcc_start_data(host, mrq->data);
-       msmsdcc_start_command(host, mrq->cmd, 0);
+               /* Queue/read data, daisy-chain command when data starts */
+               msmsdcc_start_data(host, mrq->data, mrq->cmd, 0);
+       else
+               msmsdcc_start_command(host, mrq->cmd, 0);
  
        if (host->cmdpoll && !msmsdcc_spin_on_status(host,
                                MCI_CMDRESPEND|MCI_CMDCRCFAIL|MCI_CMDTIMEOUT,
                                CMD_SPINMAX)) {
-               uint32_t status = readl(host->base + MMCISTATUS);
+               uint32_t status = msmsdcc_readl(host, MMCISTATUS);
                msmsdcc_do_cmdirq(host, status);
-               writel(MCI_CMDRESPEND | MCI_CMDCRCFAIL | MCI_CMDTIMEOUT,
-                      host->base + MMCICLEAR);
+               msmsdcc_writel(host,
+                              MCI_CMDRESPEND | MCI_CMDCRCFAIL | MCI_CMDTIMEOUT,
+                              MMCICLEAR);
                host->stats.cmdpoll_hits++;
        } else {
                host->stats.cmdpoll_misses++;
-               mod_timer(&host->command_timer, jiffies + HZ);
        }
        spin_unlock_irqrestore(&host->lock, flags);
  }
@@@ -742,14 -890,13 +891,13 @@@ msmsdcc_set_ios(struct mmc_host *mmc, s
        struct msmsdcc_host *host = mmc_priv(mmc);
        u32 clk = 0, pwr = 0;
        int rc;
+       unsigned long flags;
  
-       if (ios->clock) {
+       spin_lock_irqsave(&host->lock, flags);
  
-               if (!host->clks_on) {
-                       clk_enable(host->pclk);
-                       clk_enable(host->clk);
-                       host->clks_on = 1;
-               }
+       msmsdcc_enable_clocks(host);
+       if (ios->clock) {
                if (ios->clock != host->clk_rate) {
                        rc = clk_set_rate(host->clk, ios->clock);
                        if (rc < 0)
        if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
                pwr |= MCI_OD;
  
-       writel(clk, host->base + MMCICLOCK);
+       msmsdcc_writel(host, clk, MMCICLOCK);
  
        if (host->pwr != pwr) {
                host->pwr = pwr;
-               writel(pwr, host->base + MMCIPOWER);
-       }
-       if (!(clk & MCI_CLK_ENABLE) && host->clks_on) {
-               clk_disable(host->clk);
-               clk_disable(host->pclk);
-               host->clks_on = 0;
+               msmsdcc_writel(host, pwr, MMCIPOWER);
        }
+ #if BUSCLK_PWRSAVE
+       msmsdcc_disable_clocks(host, 1);
+ #endif
+       spin_unlock_irqrestore(&host->lock, flags);
  }
  
  static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable)
  
        spin_lock_irqsave(&host->lock, flags);
        if (msmsdcc_sdioirq == 1) {
-               status = readl(host->base + MMCIMASK0);
+               status = msmsdcc_readl(host, MMCIMASK0);
                if (enable)
                        status |= MCI_SDIOINTOPERMASK;
                else
                        status &= ~MCI_SDIOINTOPERMASK;
                host->saved_irq0mask = status;
-               writel(status, host->base + MMCIMASK0);
+               msmsdcc_writel(host, status, MMCIMASK0);
        }
        spin_unlock_irqrestore(&host->lock, flags);
  }
@@@ -875,42 -1020,13 +1021,13 @@@ msmsdcc_status_notify_cb(int card_prese
        msmsdcc_check_status((unsigned long) host);
  }
  
- /*
-  * called when a command expires.
-  * Dump some debugging, and then error
-  * out the transaction.
-  */
  static void
- msmsdcc_command_expired(unsigned long _data)
+ msmsdcc_busclk_expired(unsigned long _data)
  {
        struct msmsdcc_host     *host = (struct msmsdcc_host *) _data;
-       struct mmc_request      *mrq;
-       unsigned long           flags;
-       spin_lock_irqsave(&host->lock, flags);
-       mrq = host->curr.mrq;
-       if (!mrq) {
-               pr_info("%s: Command expiry misfire\n",
-                       mmc_hostname(host->mmc));
-               spin_unlock_irqrestore(&host->lock, flags);
-               return;
-       }
-       pr_err("%s: Command timeout (%p %p %p %p)\n",
-              mmc_hostname(host->mmc), mrq, mrq->cmd,
-              mrq->data, host->dma.sg);
-       mrq->cmd->error = -ETIMEDOUT;
-       msmsdcc_stop_data(host);
  
-       writel(0, host->base + MMCICOMMAND);
-       host->curr.mrq = NULL;
-       host->curr.cmd = NULL;
-       spin_unlock_irqrestore(&host->lock, flags);
-       mmc_request_done(host->mmc, mrq);
+       if (host->clks_on)
+               msmsdcc_disable_clocks(host, 0);
  }
  
  static int
@@@ -1012,6 -1128,7 +1129,7 @@@ msmsdcc_probe(struct platform_device *p
        host->pdev_id = pdev->id;
        host->plat = plat;
        host->mmc = mmc;
+       host->curr.cmd = NULL;
  
        host->cmdpoll = 1;
  
        host->dmares = dmares;
        spin_lock_init(&host->lock);
  
+ #ifdef CONFIG_MMC_EMBEDDED_SDIO
+       if (plat->embedded_sdio)
+               mmc_set_embedded_sdio_data(mmc,
+                                          &plat->embedded_sdio->cis,
+                                          &plat->embedded_sdio->cccr,
+                                          plat->embedded_sdio->funcs,
+                                          plat->embedded_sdio->num_funcs);
+ #endif
        /*
         * Setup DMA
         */
        msmsdcc_init_dma(host);
  
-       /*
-        * Setup main peripheral bus clock
-        */
+       /* Get our clocks */
        host->pclk = clk_get(&pdev->dev, "sdc_pclk");
        if (IS_ERR(host->pclk)) {
                ret = PTR_ERR(host->pclk);
                goto host_free;
        }
  
-       ret = clk_enable(host->pclk);
-       if (ret)
-               goto pclk_put;
-       host->pclk_rate = clk_get_rate(host->pclk);
-       /*
-        * Setup SDC MMC clock
-        */
        host->clk = clk_get(&pdev->dev, "sdc_clk");
        if (IS_ERR(host->clk)) {
                ret = PTR_ERR(host->clk);
-               goto pclk_disable;
+               goto pclk_put;
        }
  
-       ret = clk_enable(host->clk);
+       /* Enable clocks */
+       ret = msmsdcc_enable_clocks(host);
        if (ret)
                goto clk_put;
  
                goto clk_disable;
        }
  
+       host->pclk_rate = clk_get_rate(host->pclk);
        host->clk_rate = clk_get_rate(host->clk);
  
-       host->clks_on = 1;
        /*
         * Setup MMC host structure
         */
        mmc->max_req_size = 33554432;   /* MCI_DATA_LENGTH is 25 bits */
        mmc->max_seg_size = mmc->max_req_size;
  
-       writel(0, host->base + MMCIMASK0);
-       writel(0x5e007ff, host->base + MMCICLEAR); /* Add: 1 << 25 */
+       msmsdcc_writel(host, 0, MMCIMASK0);
+       msmsdcc_writel(host, 0x5e007ff, MMCICLEAR);
  
-       writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+       msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0);
        host->saved_irq0mask = MCI_IRQENABLE;
  
        /*
                host->eject = !host->oldstat;
        }
  
-       /*
-        * Setup a command timer. We currently need this due to
-        * some 'strange' timeout / error handling situations.
-        */
-       init_timer(&host->command_timer);
-       host->command_timer.data = (unsigned long) host;
-       host->command_timer.function = msmsdcc_command_expired;
+       init_timer(&host->busclk_timer);
+       host->busclk_timer.data = (unsigned long) host;
+       host->busclk_timer.function = msmsdcc_busclk_expired;
  
        ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED,
                          DRIVER_NAME " (cmd)", host);
        if (host->timer.function)
                pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc));
  
+ #if BUSCLK_PWRSAVE
+       msmsdcc_disable_clocks(host, 1);
+ #endif
        return 0;
   cmd_irq_free:
        free_irq(cmd_irqres->start, host);
        if (host->stat_irq)
                free_irq(host->stat_irq, host);
   clk_disable:
-       clk_disable(host->clk);
+       msmsdcc_disable_clocks(host, 0);
   clk_put:
        clk_put(host->clk);
-  pclk_disable:
-       clk_disable(host->pclk);
   pclk_put:
        clk_put(host->pclk);
   host_free:
@@@ -1215,15 -1327,10 +1328,10 @@@ msmsdcc_suspend(struct platform_device 
  
                if (mmc->card && mmc->card->type != MMC_TYPE_SDIO)
                        rc = mmc_suspend_host(mmc, state);
-               if (!rc) {
-                       writel(0, host->base + MMCIMASK0);
-                       if (host->clks_on) {
-                               clk_disable(host->clk);
-                               clk_disable(host->pclk);
-                               host->clks_on = 0;
-                       }
-               }
+               if (!rc)
+                       msmsdcc_writel(host, 0, MMCIMASK0);
+               if (host->clks_on)
+                       msmsdcc_disable_clocks(host, 0);
        }
        return rc;
  }
@@@ -1232,27 -1339,21 +1340,21 @@@ static in
  msmsdcc_resume(struct platform_device *dev)
  {
        struct mmc_host *mmc = mmc_get_drvdata(dev);
-       unsigned long flags;
  
        if (mmc) {
                struct msmsdcc_host *host = mmc_priv(mmc);
  
-               spin_lock_irqsave(&host->lock, flags);
+               msmsdcc_enable_clocks(host);
  
-               if (!host->clks_on) {
-                       clk_enable(host->pclk);
-                       clk_enable(host->clk);
-                       host->clks_on = 1;
-               }
-               writel(host->saved_irq0mask, host->base + MMCIMASK0);
-               spin_unlock_irqrestore(&host->lock, flags);
+               msmsdcc_writel(host, host->saved_irq0mask, MMCIMASK0);
  
                if (mmc->card && mmc->card->type != MMC_TYPE_SDIO)
                        mmc_resume_host(mmc);
                if (host->stat_irq)
                        enable_irq(host->stat_irq);
+ #if BUSCLK_PWRSAVE
+               msmsdcc_disable_clocks(host, 1);
+ #endif
        }
        return 0;
  }