]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mmc/host/omap.c
Merge current mainline tree into linux-omap tree
[linux-2.6-omap-h63xx.git] / drivers / mmc / host / omap.c
index cf9d31ce51c2a412cc4519b0f59f67251c2235bf..6e4896397fed0061e87626f3fa808953bb9be442 100644 (file)
@@ -2,7 +2,7 @@
  *  linux/drivers/mmc/host/omap.c
  *
  *  Copyright (C) 2004 Nokia Corporation
- *  Written by Tuukka Tikkanen and Juha Yrjölä<juha.yrjola@nokia.com>
+ *  Written by Tuukka Tikkanen and Juha Yrj�l�<juha.yrjola@nokia.com>
  *  Misc hacks here and there by Tony Lindgren <tony@atomide.com>
  *  Other hacks (DMA, SD, etc) by David Brownell
  *
@@ -26,6 +26,7 @@
 #include <linux/mmc/card.h>
 #include <linux/clk.h>
 #include <linux/scatterlist.h>
+#include <linux/i2c/tps65010.h>
 
 #include <asm/io.h>
 #include <asm/irq.h>
@@ -37,7 +38,6 @@
 #include <asm/arch/dma.h>
 #include <asm/arch/mux.h>
 #include <asm/arch/fpga.h>
-#include <asm/arch/tps65010.h>
 #include <asm/arch/board-sx1.h>
 
 #define        OMAP_MMC_REG_CMD        0x00
@@ -96,7 +96,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;
 
@@ -108,8 +108,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;
@@ -136,8 +136,14 @@ struct mmc_omap_host {
        unsigned char           bus_mode;
        unsigned char           hw_bus_mode;
 
-       struct work_struct      cmd_abort;
-       struct timer_list       cmd_timer;
+       struct work_struct      cmd_abort_work;
+       unsigned                abort:1;
+       struct timer_list       cmd_abort_timer;
+
+       struct work_struct      slot_release_work;
+       struct mmc_omap_slot    *next_slot;
+       struct work_struct      send_stop_work;
+       struct mmc_data         *stop_data;
 
        unsigned int            sg_len;
        int                     sg_idx;
@@ -162,9 +168,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;
@@ -181,52 +216,82 @@ 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_slot_release_work(struct work_struct *work)
+{
+       struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+                                                 slot_release_work);
+       struct mmc_omap_slot *next_slot = host->next_slot;
+       struct mmc_request *rq;
+
+       host->next_slot = NULL;
+       mmc_omap_select_slot(next_slot, 1);
+
+       rq = next_slot->mrq;
+       next_slot->mrq = NULL;
+       mmc_omap_start_request(host, rq);
+}
+
+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 */
        for (i = 0; i < host->nr_slots; i++) {
                struct mmc_omap_slot *new_slot;
-               struct mmc_request *rq;
 
                if (host->slots[i] == NULL || host->slots[i]->mrq == NULL)
                        continue;
 
+               BUG_ON(host->next_slot != NULL);
                new_slot = host->slots[i];
                /* The current slot should not have a request in queue */
                BUG_ON(new_slot == host->current_slot);
 
+               host->next_slot = new_slot;
                host->mmc = new_slot->mmc;
                spin_unlock_irqrestore(&host->slot_lock, flags);
-               mmc_omap_select_slot(new_slot, 1);
-               rq = new_slot->mrq;
-               new_slot->mrq = NULL;
-               mmc_omap_start_request(host, rq);
+               schedule_work(&host->slot_release_work);
                return;
        }
 
@@ -238,7 +303,9 @@ 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
@@ -333,7 +400,7 @@ 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_timer, jiffies + HZ/2);
+       mod_timer(&host->cmd_abort_timer, jiffies + HZ/2);
 
        OMAP_MMC_WRITE(host, CTO, 200);
        OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
@@ -366,6 +433,20 @@ mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data,
                     dma_data_dir);
 }
 
+static void mmc_omap_send_stop_work(struct work_struct *work)
+{
+       struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+                                                 send_stop_work);
+       struct mmc_omap_slot *slot = host->current_slot;
+       struct mmc_data *data = host->stop_data;
+       unsigned long tick_ns;
+
+       tick_ns = (1000000000 + slot->fclk_freq - 1)/slot->fclk_freq;
+       ndelay(8*tick_ns);
+
+       mmc_omap_start_command(host, data->stop);
+}
+
 static void
 mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
 {
@@ -385,16 +466,17 @@ 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;
        }
 
-       mmc_omap_start_command(host, data->stop);
+       host->stop_data = data;
+       schedule_work(&host->send_stop_work);
 }
 
 static void
-mmc_omap_send_abort(struct mmc_omap_host *host)
+mmc_omap_send_abort(struct mmc_omap_host *host, int maxloops)
 {
        struct mmc_omap_slot *slot = host->current_slot;
        unsigned int restarts, passes, timeout;
@@ -403,7 +485,7 @@ mmc_omap_send_abort(struct mmc_omap_host *host)
        /* 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 < 10000) {
+       while (restarts < maxloops) {
                OMAP_MMC_WRITE(host, STAT, 0xFFFF);
                OMAP_MMC_WRITE(host, CMD, (3 << 12) | (1 << 7));
 
@@ -425,18 +507,13 @@ out:
 static void
 mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data)
 {
-       u16 ie;
-
        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, IE, ie);
-       mmc_omap_send_abort(host);
+       mmc_omap_send_abort(host, 10000);
 }
 
 static void
@@ -492,7 +569,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
 {
        host->cmd = NULL;
 
-       del_timer(&host->cmd_timer);
+       del_timer(&host->cmd_abort_timer);
 
        if (cmd->flags & MMC_RSP_PRESENT) {
                if (cmd->flags & MMC_RSP_136) {
@@ -524,7 +601,7 @@ 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);
        }
 }
@@ -536,38 +613,48 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
 static void mmc_omap_abort_command(struct work_struct *work)
 {
        struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
-                                                 cmd_abort);
-       u16 ie;
-
-       ie = OMAP_MMC_READ(host, IE);
-       OMAP_MMC_WRITE(host, IE, 0);
-
-       if (!host->cmd) {
-               OMAP_MMC_WRITE(host, IE, ie);
-               return;
-       }
+                                                 cmd_abort_work);
+       BUG_ON(!host->cmd);
 
        dev_dbg(mmc_dev(host->mmc), "Aborting stuck command CMD%d\n",
                host->cmd->opcode);
 
-       if (host->data && host->dma_in_use)
-               mmc_omap_release_dma(host, host->data, 1);
+       if (host->cmd->error == 0)
+               host->cmd->error = -ETIMEDOUT;
 
-       host->data = NULL;
-       host->sg_len = 0;
+       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);
 
-       mmc_omap_send_abort(host);
-       host->cmd->error = -ETIMEDOUT;
-       mmc_omap_cmd_done(host, host->cmd);
-       OMAP_MMC_WRITE(host, IE, ie);
+       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;
 
-       schedule_work(&host->cmd_abort);
+       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 */
@@ -583,6 +670,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)
@@ -744,6 +839,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) {
@@ -756,40 +860,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 */
@@ -1139,14 +1254,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);
@@ -1156,6 +1273,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;
        }
@@ -1175,16 +1294,22 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                OMAP_MMC_WRITE(host, CON, dsor);
        slot->saved_con = dsor;
        if (ios->power_mode == MMC_POWER_ON) {
+               /* worst case at 400kHz, 80 cycles makes 200 microsecs */
+               int usecs = 250;
+
                /* Send clock cycles, poll completion */
                OMAP_MMC_WRITE(host, IE, 0);
                OMAP_MMC_WRITE(host, STAT, 0xffff);
                OMAP_MMC_WRITE(host, CMD, 1 << 7);
-               while ((OMAP_MMC_READ(host, STAT) & 1) == 0);
+               while (usecs > 0 && (OMAP_MMC_READ(host, STAT) & 1) == 0) {
+                       udelay(1);
+                       usecs--;
+               }
                OMAP_MMC_WRITE(host, STAT, 1);
        }
 
 exit:
-       mmc_omap_release_slot(slot);
+       mmc_omap_release_slot(slot, clk_enabled);
 }
 
 static int mmc_omap_get_ro(struct mmc_host *mmc)
@@ -1264,10 +1389,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);
-               setup_timer(&slot->switch_timer, mmc_omap_switch_timer,
-                           (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);
        }
 
        if (slot->pdata->get_ro != NULL) {
@@ -1301,7 +1427,8 @@ static void mmc_omap_remove_slot(struct mmc_omap_slot *slot)
        if (slot->pdata->get_ro != NULL)
                device_remove_file(&mmc->class_dev, &dev_attr_ro);
 
-       del_timer_sync(&slot->switch_timer);
+       tasklet_kill(&slot->cover_tasklet);
+       del_timer_sync(&slot->cover_timer);
        flush_scheduled_work();
 
        mmc_remove_host(mmc);
@@ -1341,8 +1468,15 @@ static int __init mmc_omap_probe(struct platform_device *pdev)
                goto err_free_mem_region;
        }
 
-       INIT_WORK(&host->cmd_abort, mmc_omap_abort_command);
-       setup_timer(&host->cmd_timer, mmc_omap_cmd_timer, (unsigned long) host);
+       INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work);
+       INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work);
+
+       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);
        setup_timer(&host->dma_timer, mmc_omap_dma_timer, (unsigned long) host);
@@ -1527,4 +1661,4 @@ module_exit(mmc_omap_exit);
 MODULE_DESCRIPTION("OMAP Multimedia Card driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS(DRIVER_NAME);
-MODULE_AUTHOR("Juha Yrjölä");
+MODULE_AUTHOR("Juha Yrj�l�");