MMC: OMAP: Lazy clock shutdown
[safe/jmp/linux-2.6] / drivers / mmc / host / omap.c
index 535499f..3d59c5d 100644 (file)
@@ -94,7 +94,7 @@
 
 /* Specifies how often in millisecs to poll for card status changes
  * when the cover switch is open */
-#define OMAP_MMC_SWITCH_POLL_DELAY     500
+#define OMAP_MMC_COVER_POLL_DELAY      500
 
 struct mmc_omap_host;
 
@@ -106,8 +106,8 @@ struct mmc_omap_slot {
        unsigned int            fclk_freq;
        unsigned                powered:1;
 
-       struct work_struct      switch_work;
-       struct timer_list       switch_timer;
+       struct tasklet_struct   cover_tasklet;
+       struct timer_list       cover_timer;
        unsigned                cover_open;
 
        struct mmc_request      *mrq;
@@ -134,6 +134,10 @@ struct mmc_omap_host {
        unsigned char           bus_mode;
        unsigned char           hw_bus_mode;
 
+       struct work_struct      cmd_abort_work;
+       unsigned                abort:1;
+       struct timer_list       cmd_abort_timer;
+
        unsigned int            sg_len;
        int                     sg_idx;
        u16 *                   buffer;
@@ -157,9 +161,38 @@ struct mmc_omap_host {
        wait_queue_head_t       slot_wq;
        int                     nr_slots;
 
+       struct timer_list       clk_timer;
+       spinlock_t              clk_lock;     /* for changing enabled state */
+       unsigned int            fclk_enabled:1;
+
        struct omap_mmc_platform_data *pdata;
 };
 
+void mmc_omap_fclk_offdelay(struct mmc_omap_slot *slot)
+{
+       unsigned long tick_ns;
+
+       if (slot != NULL && slot->host->fclk_enabled && slot->fclk_freq > 0) {
+               tick_ns = (1000000000 + slot->fclk_freq - 1) / slot->fclk_freq;
+               ndelay(8 * tick_ns);
+       }
+}
+
+void mmc_omap_fclk_enable(struct mmc_omap_host *host, unsigned int enable)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->clk_lock, flags);
+       if (host->fclk_enabled != enable) {
+               host->fclk_enabled = enable;
+               if (enable)
+                       clk_enable(host->fclk);
+               else
+                       clk_disable(host->fclk);
+       }
+       spin_unlock_irqrestore(&host->clk_lock, flags);
+}
+
 static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed)
 {
        struct mmc_omap_host *host = slot->host;
@@ -176,32 +209,49 @@ static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed)
        host->mmc = slot->mmc;
        spin_unlock_irqrestore(&host->slot_lock, flags);
 no_claim:
-       clk_enable(host->fclk);
+       del_timer(&host->clk_timer);
+       if (host->current_slot != slot || !claimed)
+               mmc_omap_fclk_offdelay(host->current_slot);
+
        if (host->current_slot != slot) {
+               OMAP_MMC_WRITE(host, CON, slot->saved_con & 0xFC00);
                if (host->pdata->switch_slot != NULL)
                        host->pdata->switch_slot(mmc_dev(slot->mmc), slot->id);
                host->current_slot = slot;
        }
 
-       /* Doing the dummy read here seems to work around some bug
-        * at least in OMAP24xx silicon where the command would not
-        * start after writing the CMD register. Sigh. */
-       OMAP_MMC_READ(host, CON);
+       if (claimed) {
+               mmc_omap_fclk_enable(host, 1);
 
-       OMAP_MMC_WRITE(host, CON, slot->saved_con);
+               /* Doing the dummy read here seems to work around some bug
+                * at least in OMAP24xx silicon where the command would not
+                * start after writing the CMD register. Sigh. */
+               OMAP_MMC_READ(host, CON);
+
+               OMAP_MMC_WRITE(host, CON, slot->saved_con);
+       } else
+               mmc_omap_fclk_enable(host, 0);
 }
 
 static void mmc_omap_start_request(struct mmc_omap_host *host,
                                   struct mmc_request *req);
 
-static void mmc_omap_release_slot(struct mmc_omap_slot *slot)
+static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled)
 {
        struct mmc_omap_host *host = slot->host;
        unsigned long flags;
        int i;
 
        BUG_ON(slot == NULL || host->mmc == NULL);
-       clk_disable(host->fclk);
+
+       if (clk_enabled)
+               /* Keeps clock running for at least 8 cycles on valid freq */
+               mod_timer(&host->clk_timer, jiffies  + HZ/10);
+       else {
+               del_timer(&host->clk_timer);
+               mmc_omap_fclk_offdelay(slot);
+               mmc_omap_fclk_enable(host, 0);
+       }
 
        spin_lock_irqsave(&host->slot_lock, flags);
        /* Check for any pending requests */
@@ -233,7 +283,10 @@ static void mmc_omap_release_slot(struct mmc_omap_slot *slot)
 static inline
 int mmc_omap_cover_is_open(struct mmc_omap_slot *slot)
 {
-       return slot->pdata->get_cover_state(mmc_dev(slot->mmc), slot->id);
+       if (slot->pdata->get_cover_state)
+               return slot->pdata->get_cover_state(mmc_dev(slot->mmc),
+                                                   slot->id);
+       return 0;
 }
 
 static ssize_t
@@ -314,6 +367,8 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
        if (host->data && !(host->data->flags & MMC_DATA_WRITE))
                cmdreg |= 1 << 15;
 
+       mod_timer(&host->cmd_abort_timer, jiffies + HZ/2);
+
        OMAP_MMC_WRITE(host, CTO, 200);
        OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
        OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16);
@@ -364,7 +419,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
 
                host->mrq = NULL;
                mmc = host->mmc;
-               mmc_omap_release_slot(host->current_slot);
+               mmc_omap_release_slot(host->current_slot, 1);
                mmc_request_done(mmc, data->mrq);
                return;
        }
@@ -373,29 +428,44 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
 }
 
 static void
-mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data)
+mmc_omap_send_abort(struct mmc_omap_host *host, int maxloops)
 {
-       int loops;
-       u16 ie;
+       struct mmc_omap_slot *slot = host->current_slot;
+       unsigned int restarts, passes, timeout;
+       u16 stat = 0;
+
+       /* Sending abort takes 80 clocks. Have some extra and round up */
+       timeout = (120*1000000 + slot->fclk_freq - 1)/slot->fclk_freq;
+       restarts = 0;
+       while (restarts < maxloops) {
+               OMAP_MMC_WRITE(host, STAT, 0xFFFF);
+               OMAP_MMC_WRITE(host, CMD, (3 << 12) | (1 << 7));
+
+               passes = 0;
+               while (passes < timeout) {
+                       stat = OMAP_MMC_READ(host, STAT);
+                       if (stat & OMAP_MMC_STAT_END_OF_CMD)
+                               goto out;
+                       udelay(1);
+                       passes++;
+               }
 
+               restarts++;
+       }
+out:
+       OMAP_MMC_WRITE(host, STAT, stat);
+}
+
+static void
+mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data)
+{
        if (host->dma_in_use)
                mmc_omap_release_dma(host, data, 1);
 
        host->data = NULL;
        host->sg_len = 0;
 
-       ie = OMAP_MMC_READ(host, IE);
-       OMAP_MMC_WRITE(host, IE, 0);
-       OMAP_MMC_WRITE(host, CMD, 1 << 7);
-       loops = 0;
-       while (!(OMAP_MMC_READ(host, STAT) & OMAP_MMC_STAT_END_OF_CMD)) {
-               udelay(1);
-               loops++;
-               if (loops == 100000)
-                       break;
-       }
-       OMAP_MMC_WRITE(host, STAT, OMAP_MMC_STAT_END_OF_CMD);
-       OMAP_MMC_WRITE(host, IE, ie);
+       mmc_omap_send_abort(host, 10000);
 }
 
 static void
@@ -451,6 +521,8 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
 {
        host->cmd = NULL;
 
+       del_timer(&host->cmd_abort_timer);
+
        if (cmd->flags & MMC_RSP_PRESENT) {
                if (cmd->flags & MMC_RSP_136) {
                        /* response type 2 */
@@ -481,11 +553,62 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
                        mmc_omap_abort_xfer(host, host->data);
                host->mrq = NULL;
                mmc = host->mmc;
-               mmc_omap_release_slot(host->current_slot);
+               mmc_omap_release_slot(host->current_slot, 1);
                mmc_request_done(mmc, cmd->mrq);
        }
 }
 
+/*
+ * Abort stuck command. Can occur when card is removed while it is being
+ * read.
+ */
+static void mmc_omap_abort_command(struct work_struct *work)
+{
+       struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+                                                 cmd_abort_work);
+       BUG_ON(!host->cmd);
+
+       dev_dbg(mmc_dev(host->mmc), "Aborting stuck command CMD%d\n",
+               host->cmd->opcode);
+
+       if (host->cmd->error == 0)
+               host->cmd->error = -ETIMEDOUT;
+
+       if (host->data == NULL) {
+               struct mmc_command *cmd;
+               struct mmc_host    *mmc;
+
+               cmd = host->cmd;
+               host->cmd = NULL;
+               mmc_omap_send_abort(host, 10000);
+
+               host->mrq = NULL;
+               mmc = host->mmc;
+               mmc_omap_release_slot(host->current_slot, 1);
+               mmc_request_done(mmc, cmd->mrq);
+       } else
+               mmc_omap_cmd_done(host, host->cmd);
+
+       host->abort = 0;
+       enable_irq(host->irq);
+}
+
+static void
+mmc_omap_cmd_timer(unsigned long data)
+{
+       struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->slot_lock, flags);
+       if (host->cmd != NULL && !host->abort) {
+               OMAP_MMC_WRITE(host, IE, 0);
+               disable_irq(host->irq);
+               host->abort = 1;
+               schedule_work(&host->cmd_abort_work);
+       }
+       spin_unlock_irqrestore(&host->slot_lock, flags);
+}
+
 /* PIO only */
 static void
 mmc_omap_sg_to_buf(struct mmc_omap_host *host)
@@ -499,6 +622,14 @@ mmc_omap_sg_to_buf(struct mmc_omap_host *host)
                host->buffer_bytes_left = host->total_bytes_left;
 }
 
+static void
+mmc_omap_clk_timer(unsigned long data)
+{
+       struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+       mmc_omap_fclk_enable(host, 0);
+}
+
 /* PIO only */
 static void
 mmc_omap_xfer_data(struct mmc_omap_host *host, int write)
@@ -657,6 +788,15 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
                }
        }
 
+       if (cmd_error && host->data) {
+               del_timer(&host->cmd_abort_timer);
+               host->abort = 1;
+               OMAP_MMC_WRITE(host, IE, 0);
+               disable_irq(host->irq);
+               schedule_work(&host->cmd_abort_work);
+               return IRQ_HANDLED;
+       }
+
        if (end_command)
                mmc_omap_cmd_done(host, host->cmd);
        if (host->data != NULL) {
@@ -669,40 +809,51 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
-void omap_mmc_notify_cover_event(struct device *dev, int slot, int is_closed)
+void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed)
 {
+       int cover_open;
        struct mmc_omap_host *host = dev_get_drvdata(dev);
+       struct mmc_omap_slot *slot = host->slots[num];
 
-       BUG_ON(slot >= host->nr_slots);
+       BUG_ON(num >= host->nr_slots);
 
        /* Other subsystems can call in here before we're initialised. */
-       if (host->nr_slots == 0 || !host->slots[slot])
+       if (host->nr_slots == 0 || !host->slots[num])
                return;
 
-       schedule_work(&host->slots[slot]->switch_work);
+       cover_open = mmc_omap_cover_is_open(slot);
+       if (cover_open != slot->cover_open) {
+               slot->cover_open = cover_open;
+               sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch");
+       }
+
+       tasklet_hi_schedule(&slot->cover_tasklet);
 }
 
-static void mmc_omap_switch_timer(unsigned long arg)
+static void mmc_omap_cover_timer(unsigned long arg)
 {
        struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg;
-
-       schedule_work(&slot->switch_work);
+       tasklet_schedule(&slot->cover_tasklet);
 }
 
-static void mmc_omap_cover_handler(struct work_struct *work)
+static void mmc_omap_cover_handler(unsigned long param)
 {
-       struct mmc_omap_slot *slot = container_of(work, struct mmc_omap_slot,
-                                                 switch_work);
-       int cover_open;
+       struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param;
+       int cover_open = mmc_omap_cover_is_open(slot);
 
-       cover_open = mmc_omap_cover_is_open(slot);
-       if (cover_open != slot->cover_open) {
-               sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch");
-               slot->cover_open = cover_open;
-               dev_info(mmc_dev(slot->mmc), "cover is now %s\n",
-                        cover_open ? "open" : "closed");
-       }
-       mmc_detect_change(slot->mmc, slot->id);
+       mmc_detect_change(slot->mmc, 0);
+       if (!cover_open)
+               return;
+
+       /*
+        * If no card is inserted, we postpone polling until
+        * the cover has been closed.
+        */
+       if (slot->mmc->card == NULL || !mmc_card_present(slot->mmc->card))
+               return;
+
+       mod_timer(&slot->cover_timer,
+                 jiffies + msecs_to_jiffies(OMAP_MMC_COVER_POLL_DELAY));
 }
 
 /* Prepare to transfer the next segment of a scatterlist */
@@ -1052,14 +1203,16 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        struct mmc_omap_slot *slot = mmc_priv(mmc);
        struct mmc_omap_host *host = slot->host;
        int i, dsor;
-
-       dsor = mmc_omap_calc_divisor(mmc, ios);
+       int clk_enabled;
 
        mmc_omap_select_slot(slot, 0);
 
+       dsor = mmc_omap_calc_divisor(mmc, ios);
+
        if (ios->vdd != slot->vdd)
                slot->vdd = ios->vdd;
 
+       clk_enabled = 0;
        switch (ios->power_mode) {
        case MMC_POWER_OFF:
                mmc_omap_set_power(slot, 0, ios->vdd);
@@ -1069,6 +1222,8 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                mmc_omap_set_power(slot, 1, ios->vdd);
                goto exit;
        case MMC_POWER_ON:
+               mmc_omap_fclk_enable(host, 1);
+               clk_enabled = 1;
                dsor |= 1 << 11;
                break;
        }
@@ -1097,7 +1252,7 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        }
 
 exit:
-       mmc_omap_release_slot(slot);
+       mmc_omap_release_slot(slot, clk_enabled);
 }
 
 static const struct mmc_host_ops mmc_omap_ops = {
@@ -1166,11 +1321,11 @@ static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id)
                if (r < 0)
                        goto err_remove_slot_name;
 
-               INIT_WORK(&slot->switch_work, mmc_omap_cover_handler);
-               init_timer(&slot->switch_timer);
-               slot->switch_timer.function = mmc_omap_switch_timer;
-               slot->switch_timer.data = (unsigned long) slot;
-               schedule_work(&slot->switch_work);
+               setup_timer(&slot->cover_timer, mmc_omap_cover_timer,
+                           (unsigned long)slot);
+               tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler,
+                            (unsigned long)slot);
+               tasklet_schedule(&slot->cover_tasklet);
        }
 
        return 0;
@@ -1193,7 +1348,8 @@ static void mmc_omap_remove_slot(struct mmc_omap_slot *slot)
        if (slot->pdata->get_cover_state != NULL)
                device_remove_file(&mmc->class_dev, &dev_attr_cover_switch);
 
-       del_timer_sync(&slot->switch_timer);
+       tasklet_kill(&slot->cover_tasklet);
+       del_timer_sync(&slot->cover_timer);
        flush_scheduled_work();
 
        mmc_remove_host(mmc);
@@ -1233,14 +1389,18 @@ static int __init mmc_omap_probe(struct platform_device *pdev)
                goto err_free_mem_region;
        }
 
+       INIT_WORK(&host->cmd_abort_work, mmc_omap_abort_command);
+       setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer,
+                   (unsigned long) host);
+
+       spin_lock_init(&host->clk_lock);
+       setup_timer(&host->clk_timer, mmc_omap_clk_timer, (unsigned long) host);
+
        spin_lock_init(&host->dma_lock);
-       init_timer(&host->dma_timer);
+       setup_timer(&host->dma_timer, mmc_omap_dma_timer, (unsigned long) host);
        spin_lock_init(&host->slot_lock);
        init_waitqueue_head(&host->slot_wq);
 
-       host->dma_timer.function = mmc_omap_dma_timer;
-       host->dma_timer.data = (unsigned long) host;
-
        host->pdata = pdata;
        host->dev = &pdev->dev;
        platform_set_drvdata(pdev, host);