|
@@ -32,6 +32,7 @@
|
|
|
#include <asm/mach-types.h>
|
|
|
|
|
|
#include <asm/arch/board.h>
|
|
|
+#include <asm/arch/mmc.h>
|
|
|
#include <asm/arch/gpio.h>
|
|
|
#include <asm/arch/dma.h>
|
|
|
#include <asm/arch/mux.h>
|
|
@@ -93,9 +94,27 @@
|
|
|
|
|
|
/* Specifies how often in millisecs to poll for card status changes
|
|
|
* when the cover switch is open */
|
|
|
-#define OMAP_MMC_SWITCH_POLL_DELAY 500
|
|
|
-
|
|
|
-static int mmc_omap_enable_poll = 1;
|
|
|
+#define OMAP_MMC_COVER_POLL_DELAY 500
|
|
|
+
|
|
|
+struct mmc_omap_host;
|
|
|
+
|
|
|
+struct mmc_omap_slot {
|
|
|
+ int id;
|
|
|
+ unsigned int vdd;
|
|
|
+ u16 saved_con;
|
|
|
+ u16 bus_mode;
|
|
|
+ unsigned int fclk_freq;
|
|
|
+ unsigned powered:1;
|
|
|
+
|
|
|
+ struct tasklet_struct cover_tasklet;
|
|
|
+ struct timer_list cover_timer;
|
|
|
+ unsigned cover_open;
|
|
|
+
|
|
|
+ struct mmc_request *mrq;
|
|
|
+ struct mmc_omap_host *host;
|
|
|
+ struct mmc_host *mmc;
|
|
|
+ struct omap_mmc_slot_data *pdata;
|
|
|
+};
|
|
|
|
|
|
struct mmc_omap_host {
|
|
|
int initialized;
|
|
@@ -115,6 +134,15 @@ 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;
|
|
|
+
|
|
|
+ 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;
|
|
|
u16 * buffer;
|
|
@@ -131,63 +159,178 @@ struct mmc_omap_host {
|
|
|
unsigned dma_len;
|
|
|
|
|
|
short power_pin;
|
|
|
- short wp_pin;
|
|
|
|
|
|
- int switch_pin;
|
|
|
- struct work_struct switch_work;
|
|
|
- struct timer_list switch_timer;
|
|
|
- int switch_last_state;
|
|
|
+ struct mmc_omap_slot *slots[OMAP_MMC_MAX_SLOTS];
|
|
|
+ struct mmc_omap_slot *current_slot;
|
|
|
+ spinlock_t slot_lock;
|
|
|
+ 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;
|
|
|
};
|
|
|
|
|
|
-static inline int
|
|
|
-mmc_omap_cover_is_open(struct mmc_omap_host *host)
|
|
|
+void mmc_omap_fclk_offdelay(struct mmc_omap_slot *slot)
|
|
|
{
|
|
|
- if (host->switch_pin < 0)
|
|
|
- return 0;
|
|
|
- return omap_get_gpio_datain(host->switch_pin);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-static ssize_t
|
|
|
-mmc_omap_show_cover_switch(struct device *dev,
|
|
|
- struct device_attribute *attr, char *buf)
|
|
|
+void mmc_omap_fclk_enable(struct mmc_omap_host *host, unsigned int enable)
|
|
|
{
|
|
|
- struct mmc_omap_host *host = dev_get_drvdata(dev);
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
- return sprintf(buf, "%s\n", mmc_omap_cover_is_open(host) ? "open" :
|
|
|
- "closed");
|
|
|
+ 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 DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
|
|
|
+static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed)
|
|
|
+{
|
|
|
+ struct mmc_omap_host *host = slot->host;
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
-static ssize_t
|
|
|
-mmc_omap_show_enable_poll(struct device *dev,
|
|
|
- struct device_attribute *attr, char *buf)
|
|
|
+ if (claimed)
|
|
|
+ goto no_claim;
|
|
|
+ spin_lock_irqsave(&host->slot_lock, flags);
|
|
|
+ while (host->mmc != NULL) {
|
|
|
+ spin_unlock_irqrestore(&host->slot_lock, flags);
|
|
|
+ wait_event(host->slot_wq, host->mmc == NULL);
|
|
|
+ spin_lock_irqsave(&host->slot_lock, flags);
|
|
|
+ }
|
|
|
+ host->mmc = slot->mmc;
|
|
|
+ spin_unlock_irqrestore(&host->slot_lock, flags);
|
|
|
+no_claim:
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (claimed) {
|
|
|
+ mmc_omap_fclk_enable(host, 1);
|
|
|
+
|
|
|
+ /* 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_slot_release_work(struct work_struct *work)
|
|
|
{
|
|
|
- return snprintf(buf, PAGE_SIZE, "%d\n", mmc_omap_enable_poll);
|
|
|
+ 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 ssize_t
|
|
|
-mmc_omap_store_enable_poll(struct device *dev,
|
|
|
- struct device_attribute *attr, const char *buf,
|
|
|
- size_t size)
|
|
|
+static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled)
|
|
|
{
|
|
|
- int enable_poll;
|
|
|
+ struct mmc_omap_host *host = slot->host;
|
|
|
+ unsigned long flags;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ BUG_ON(slot == NULL || host->mmc == NULL);
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
|
|
|
- if (sscanf(buf, "%10d", &enable_poll) != 1)
|
|
|
- return -EINVAL;
|
|
|
+ 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;
|
|
|
|
|
|
- if (enable_poll != mmc_omap_enable_poll) {
|
|
|
- struct mmc_omap_host *host = dev_get_drvdata(dev);
|
|
|
+ if (host->slots[i] == NULL || host->slots[i]->mrq == NULL)
|
|
|
+ continue;
|
|
|
|
|
|
- mmc_omap_enable_poll = enable_poll;
|
|
|
- if (enable_poll && host->switch_pin >= 0)
|
|
|
- schedule_work(&host->switch_work);
|
|
|
+ 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);
|
|
|
+ schedule_work(&host->slot_release_work);
|
|
|
+ return;
|
|
|
}
|
|
|
- return size;
|
|
|
+
|
|
|
+ host->mmc = NULL;
|
|
|
+ wake_up(&host->slot_wq);
|
|
|
+ spin_unlock_irqrestore(&host->slot_lock, flags);
|
|
|
+}
|
|
|
+
|
|
|
+static inline
|
|
|
+int mmc_omap_cover_is_open(struct mmc_omap_slot *slot)
|
|
|
+{
|
|
|
+ if (slot->pdata->get_cover_state)
|
|
|
+ return slot->pdata->get_cover_state(mmc_dev(slot->mmc),
|
|
|
+ slot->id);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
|
|
|
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
|
|
|
+
|
|
|
+ return sprintf(buf, "%s\n", mmc_omap_cover_is_open(slot) ? "open" :
|
|
|
+ "closed");
|
|
|
}
|
|
|
|
|
|
-static DEVICE_ATTR(enable_poll, 0664,
|
|
|
- mmc_omap_show_enable_poll, mmc_omap_store_enable_poll);
|
|
|
+static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr,
|
|
|
+ char *buf)
|
|
|
+{
|
|
|
+ struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
|
|
|
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
|
|
|
+
|
|
|
+ return sprintf(buf, "%s\n", slot->pdata->name);
|
|
|
+}
|
|
|
+
|
|
|
+static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL);
|
|
|
|
|
|
static void
|
|
|
mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
|
|
@@ -233,7 +376,7 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
|
|
|
|
|
|
cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12);
|
|
|
|
|
|
- if (host->bus_mode == MMC_BUSMODE_OPENDRAIN)
|
|
|
+ if (host->current_slot->bus_mode == MMC_BUSMODE_OPENDRAIN)
|
|
|
cmdreg |= 1 << 6;
|
|
|
|
|
|
if (cmd->flags & MMC_RSP_BUSY)
|
|
@@ -242,7 +385,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;
|
|
|
|
|
|
- clk_enable(host->fclk);
|
|
|
+ mod_timer(&host->cmd_abort_timer, jiffies + HZ/2);
|
|
|
|
|
|
OMAP_MMC_WRITE(host, CTO, 200);
|
|
|
OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
|
|
@@ -256,27 +399,47 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
|
|
|
OMAP_MMC_WRITE(host, CMD, cmdreg);
|
|
|
}
|
|
|
|
|
|
+static void
|
|
|
+mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data,
|
|
|
+ int abort)
|
|
|
+{
|
|
|
+ enum dma_data_direction dma_data_dir;
|
|
|
+
|
|
|
+ BUG_ON(host->dma_ch < 0);
|
|
|
+ if (data->error)
|
|
|
+ omap_stop_dma(host->dma_ch);
|
|
|
+ /* Release DMA channel lazily */
|
|
|
+ mod_timer(&host->dma_timer, jiffies + HZ);
|
|
|
+ if (data->flags & MMC_DATA_WRITE)
|
|
|
+ dma_data_dir = DMA_TO_DEVICE;
|
|
|
+ else
|
|
|
+ dma_data_dir = DMA_FROM_DEVICE;
|
|
|
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
|
|
|
+ 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)
|
|
|
{
|
|
|
- if (host->dma_in_use) {
|
|
|
- enum dma_data_direction dma_data_dir;
|
|
|
-
|
|
|
- BUG_ON(host->dma_ch < 0);
|
|
|
- if (data->error)
|
|
|
- omap_stop_dma(host->dma_ch);
|
|
|
- /* Release DMA channel lazily */
|
|
|
- mod_timer(&host->dma_timer, jiffies + HZ);
|
|
|
- if (data->flags & MMC_DATA_WRITE)
|
|
|
- dma_data_dir = DMA_TO_DEVICE;
|
|
|
- else
|
|
|
- dma_data_dir = DMA_FROM_DEVICE;
|
|
|
- dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
|
|
|
- dma_data_dir);
|
|
|
- }
|
|
|
+ if (host->dma_in_use)
|
|
|
+ mmc_omap_release_dma(host, data, data->error);
|
|
|
+
|
|
|
host->data = NULL;
|
|
|
host->sg_len = 0;
|
|
|
- clk_disable(host->fclk);
|
|
|
|
|
|
/* NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing
|
|
|
* dozens of requests until the card finishes writing data.
|
|
@@ -284,12 +447,58 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
|
|
|
*/
|
|
|
|
|
|
if (!data->stop) {
|
|
|
+ struct mmc_host *mmc;
|
|
|
+
|
|
|
host->mrq = NULL;
|
|
|
- mmc_request_done(host->mmc, data->mrq);
|
|
|
+ mmc = host->mmc;
|
|
|
+ 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, int maxloops)
|
|
|
+{
|
|
|
+ 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;
|
|
|
+
|
|
|
+ mmc_omap_send_abort(host, 10000);
|
|
|
}
|
|
|
|
|
|
static void
|
|
@@ -345,6 +554,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 */
|
|
@@ -369,10 +580,66 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
|
|
|
}
|
|
|
|
|
|
if (host->data == NULL || cmd->error) {
|
|
|
+ struct mmc_host *mmc;
|
|
|
+
|
|
|
+ if (host->data != NULL)
|
|
|
+ mmc_omap_abort_xfer(host, host->data);
|
|
|
host->mrq = NULL;
|
|
|
- clk_disable(host->fclk);
|
|
|
- mmc_request_done(host->mmc, cmd->mrq);
|
|
|
+ mmc = host->mmc;
|
|
|
+ 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 */
|
|
@@ -388,6 +655,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)
|
|
@@ -436,11 +711,12 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
u16 status;
|
|
|
int end_command;
|
|
|
int end_transfer;
|
|
|
- int transfer_error;
|
|
|
+ int transfer_error, cmd_error;
|
|
|
|
|
|
if (host->cmd == NULL && host->data == NULL) {
|
|
|
status = OMAP_MMC_READ(host, STAT);
|
|
|
- dev_info(mmc_dev(host->mmc),"spurious irq 0x%04x\n", status);
|
|
|
+ dev_info(mmc_dev(host->slots[0]->mmc),
|
|
|
+ "Spurious IRQ 0x%04x\n", status);
|
|
|
if (status != 0) {
|
|
|
OMAP_MMC_WRITE(host, STAT, status);
|
|
|
OMAP_MMC_WRITE(host, IE, 0);
|
|
@@ -451,12 +727,19 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
end_command = 0;
|
|
|
end_transfer = 0;
|
|
|
transfer_error = 0;
|
|
|
+ cmd_error = 0;
|
|
|
|
|
|
while ((status = OMAP_MMC_READ(host, STAT)) != 0) {
|
|
|
+ int cmd;
|
|
|
+
|
|
|
OMAP_MMC_WRITE(host, STAT, status);
|
|
|
+ if (host->cmd != NULL)
|
|
|
+ cmd = host->cmd->opcode;
|
|
|
+ else
|
|
|
+ cmd = -1;
|
|
|
#ifdef CONFIG_MMC_DEBUG
|
|
|
dev_dbg(mmc_dev(host->mmc), "MMC IRQ %04x (CMD %d): ",
|
|
|
- status, host->cmd != NULL ? host->cmd->opcode : -1);
|
|
|
+ status, cmd);
|
|
|
mmc_omap_report_irq(status);
|
|
|
printk("\n");
|
|
|
#endif
|
|
@@ -468,12 +751,12 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
mmc_omap_xfer_data(host, 1);
|
|
|
}
|
|
|
|
|
|
- if (status & OMAP_MMC_STAT_END_OF_DATA) {
|
|
|
+ if (status & OMAP_MMC_STAT_END_OF_DATA)
|
|
|
end_transfer = 1;
|
|
|
- }
|
|
|
|
|
|
if (status & OMAP_MMC_STAT_DATA_TOUT) {
|
|
|
- dev_dbg(mmc_dev(host->mmc), "data timeout\n");
|
|
|
+ dev_dbg(mmc_dev(host->mmc), "data timeout (CMD%d)\n",
|
|
|
+ cmd);
|
|
|
if (host->data) {
|
|
|
host->data->error = -ETIMEDOUT;
|
|
|
transfer_error = 1;
|
|
@@ -495,17 +778,16 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
if (status & OMAP_MMC_STAT_CMD_TOUT) {
|
|
|
/* Timeouts are routine with some commands */
|
|
|
if (host->cmd) {
|
|
|
- if (host->cmd->opcode != MMC_ALL_SEND_CID &&
|
|
|
- host->cmd->opcode !=
|
|
|
- MMC_SEND_OP_COND &&
|
|
|
- host->cmd->opcode !=
|
|
|
- MMC_APP_CMD &&
|
|
|
- !mmc_omap_cover_is_open(host))
|
|
|
+ struct mmc_omap_slot *slot =
|
|
|
+ host->current_slot;
|
|
|
+ if (slot == NULL ||
|
|
|
+ !mmc_omap_cover_is_open(slot))
|
|
|
dev_err(mmc_dev(host->mmc),
|
|
|
- "command timeout, CMD %d\n",
|
|
|
- host->cmd->opcode);
|
|
|
+ "command timeout (CMD%d)\n",
|
|
|
+ cmd);
|
|
|
host->cmd->error = -ETIMEDOUT;
|
|
|
end_command = 1;
|
|
|
+ cmd_error = 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -513,9 +795,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
if (host->cmd) {
|
|
|
dev_err(mmc_dev(host->mmc),
|
|
|
"command CRC error (CMD%d, arg 0x%08x)\n",
|
|
|
- host->cmd->opcode, host->cmd->arg);
|
|
|
+ cmd, host->cmd->arg);
|
|
|
host->cmd->error = -EILSEQ;
|
|
|
end_command = 1;
|
|
|
+ cmd_error = 1;
|
|
|
} else
|
|
|
dev_err(mmc_dev(host->mmc),
|
|
|
"command CRC error without cmd?\n");
|
|
@@ -524,13 +807,13 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
if (status & OMAP_MMC_STAT_CARD_ERR) {
|
|
|
dev_dbg(mmc_dev(host->mmc),
|
|
|
"ignoring card status error (CMD%d)\n",
|
|
|
- host->cmd->opcode);
|
|
|
+ cmd);
|
|
|
end_command = 1;
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* NOTE: On 1610 the END_OF_CMD may come too early when
|
|
|
- * starting a write
|
|
|
+ * starting a write
|
|
|
*/
|
|
|
if ((status & OMAP_MMC_STAT_END_OF_CMD) &&
|
|
|
(!(status & OMAP_MMC_STAT_A_EMPTY))) {
|
|
@@ -538,63 +821,72 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (end_command) {
|
|
|
+ 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) {
|
|
|
+ if (transfer_error)
|
|
|
+ mmc_omap_xfer_done(host, host->data);
|
|
|
+ else if (end_transfer)
|
|
|
+ mmc_omap_end_of_data(host, host->data);
|
|
|
}
|
|
|
- if (transfer_error)
|
|
|
- mmc_omap_xfer_done(host, host->data);
|
|
|
- else if (end_transfer)
|
|
|
- mmc_omap_end_of_data(host, host->data);
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
}
|
|
|
|
|
|
-static irqreturn_t mmc_omap_switch_irq(int irq, void *dev_id)
|
|
|
+void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed)
|
|
|
{
|
|
|
- struct mmc_omap_host *host = (struct mmc_omap_host *) dev_id;
|
|
|
+ int cover_open;
|
|
|
+ struct mmc_omap_host *host = dev_get_drvdata(dev);
|
|
|
+ struct mmc_omap_slot *slot = host->slots[num];
|
|
|
|
|
|
- schedule_work(&host->switch_work);
|
|
|
+ BUG_ON(num >= host->nr_slots);
|
|
|
|
|
|
- return IRQ_HANDLED;
|
|
|
+ /* Other subsystems can call in here before we're initialised. */
|
|
|
+ if (host->nr_slots == 0 || !host->slots[num])
|
|
|
+ return;
|
|
|
+
|
|
|
+ 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_host *host = (struct mmc_omap_host *) arg;
|
|
|
-
|
|
|
- schedule_work(&host->switch_work);
|
|
|
+ struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg;
|
|
|
+ tasklet_schedule(&slot->cover_tasklet);
|
|
|
}
|
|
|
|
|
|
-static void mmc_omap_switch_handler(struct work_struct *work)
|
|
|
+static void mmc_omap_cover_handler(unsigned long param)
|
|
|
{
|
|
|
- struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, switch_work);
|
|
|
- struct mmc_card *card;
|
|
|
- static int complained = 0;
|
|
|
- int cards = 0, cover_open;
|
|
|
+ struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param;
|
|
|
+ int cover_open = mmc_omap_cover_is_open(slot);
|
|
|
|
|
|
- if (host->switch_pin == -1)
|
|
|
+ mmc_detect_change(slot->mmc, 0);
|
|
|
+ if (!cover_open)
|
|
|
return;
|
|
|
- cover_open = mmc_omap_cover_is_open(host);
|
|
|
- if (cover_open != host->switch_last_state) {
|
|
|
- kobject_uevent(&host->dev->kobj, KOBJ_CHANGE);
|
|
|
- host->switch_last_state = cover_open;
|
|
|
- }
|
|
|
- mmc_detect_change(host->mmc, 0);
|
|
|
- list_for_each_entry(card, &host->mmc->cards, node) {
|
|
|
- if (mmc_card_present(card))
|
|
|
- cards++;
|
|
|
- }
|
|
|
- if (mmc_omap_cover_is_open(host)) {
|
|
|
- if (!complained) {
|
|
|
- dev_info(mmc_dev(host->mmc), "cover is open\n");
|
|
|
- complained = 1;
|
|
|
- }
|
|
|
- if (mmc_omap_enable_poll)
|
|
|
- mod_timer(&host->switch_timer, jiffies +
|
|
|
- msecs_to_jiffies(OMAP_MMC_SWITCH_POLL_DELAY));
|
|
|
- } else {
|
|
|
- complained = 0;
|
|
|
- }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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 */
|
|
@@ -765,13 +1057,12 @@ static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_reques
|
|
|
|
|
|
static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req)
|
|
|
{
|
|
|
- int timeout;
|
|
|
+ unsigned int timeout, cycle_ns;
|
|
|
u16 reg;
|
|
|
|
|
|
- /* Convert ns to clock cycles by assuming 20MHz frequency
|
|
|
- * 1 cycle at 20MHz = 500 ns
|
|
|
- */
|
|
|
- timeout = req->data->timeout_clks + req->data->timeout_ns / 500;
|
|
|
+ cycle_ns = 1000000000 / host->current_slot->fclk_freq;
|
|
|
+ timeout = req->data->timeout_ns / cycle_ns;
|
|
|
+ timeout += req->data->timeout_clks;
|
|
|
|
|
|
/* Check if we need to use timeout multiplier register */
|
|
|
reg = OMAP_MMC_READ(host, SDIO);
|
|
@@ -854,11 +1145,10 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req)
|
|
|
+static void mmc_omap_start_request(struct mmc_omap_host *host,
|
|
|
+ struct mmc_request *req)
|
|
|
{
|
|
|
- struct mmc_omap_host *host = mmc_priv(mmc);
|
|
|
-
|
|
|
- WARN_ON(host->mrq != NULL);
|
|
|
+ BUG_ON(host->mrq != NULL);
|
|
|
|
|
|
host->mrq = req;
|
|
|
|
|
@@ -867,60 +1157,56 @@ static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req)
|
|
|
mmc_omap_start_command(host, req->cmd);
|
|
|
if (host->dma_in_use)
|
|
|
omap_start_dma(host->dma_ch);
|
|
|
+ BUG_ON(irqs_disabled());
|
|
|
}
|
|
|
|
|
|
-static void innovator_fpga_socket_power(int on)
|
|
|
+static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req)
|
|
|
{
|
|
|
-#if defined(CONFIG_MACH_OMAP_INNOVATOR) && defined(CONFIG_ARCH_OMAP15XX)
|
|
|
- if (on) {
|
|
|
- fpga_write(fpga_read(OMAP1510_FPGA_POWER) | (1 << 3),
|
|
|
- OMAP1510_FPGA_POWER);
|
|
|
- } else {
|
|
|
- fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~(1 << 3),
|
|
|
- OMAP1510_FPGA_POWER);
|
|
|
- }
|
|
|
-#endif
|
|
|
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
|
|
|
+ struct mmc_omap_host *host = slot->host;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&host->slot_lock, flags);
|
|
|
+ if (host->mmc != NULL) {
|
|
|
+ BUG_ON(slot->mrq != NULL);
|
|
|
+ slot->mrq = req;
|
|
|
+ spin_unlock_irqrestore(&host->slot_lock, flags);
|
|
|
+ return;
|
|
|
+ } else
|
|
|
+ host->mmc = mmc;
|
|
|
+ spin_unlock_irqrestore(&host->slot_lock, flags);
|
|
|
+ mmc_omap_select_slot(slot, 1);
|
|
|
+ mmc_omap_start_request(host, req);
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Turn the socket power on/off. Innovator uses FPGA, most boards
|
|
|
- * probably use GPIO.
|
|
|
- */
|
|
|
-static void mmc_omap_power(struct mmc_omap_host *host, int on)
|
|
|
+static void mmc_omap_set_power(struct mmc_omap_slot *slot, int power_on,
|
|
|
+ int vdd)
|
|
|
{
|
|
|
- if (on) {
|
|
|
- if (machine_is_omap_innovator())
|
|
|
- innovator_fpga_socket_power(1);
|
|
|
- else if (machine_is_omap_h2())
|
|
|
- tps65010_set_gpio_out_value(GPIO3, HIGH);
|
|
|
- else if (machine_is_omap_h3())
|
|
|
- /* GPIO 4 of TPS65010 sends SD_EN signal */
|
|
|
- tps65010_set_gpio_out_value(GPIO4, HIGH);
|
|
|
- else if (cpu_is_omap24xx()) {
|
|
|
- u16 reg = OMAP_MMC_READ(host, CON);
|
|
|
- OMAP_MMC_WRITE(host, CON, reg | (1 << 11));
|
|
|
- } else
|
|
|
- if (host->power_pin >= 0)
|
|
|
- omap_set_gpio_dataout(host->power_pin, 1);
|
|
|
- } else {
|
|
|
- if (machine_is_omap_innovator())
|
|
|
- innovator_fpga_socket_power(0);
|
|
|
- else if (machine_is_omap_h2())
|
|
|
- tps65010_set_gpio_out_value(GPIO3, LOW);
|
|
|
- else if (machine_is_omap_h3())
|
|
|
- tps65010_set_gpio_out_value(GPIO4, LOW);
|
|
|
- else if (cpu_is_omap24xx()) {
|
|
|
- u16 reg = OMAP_MMC_READ(host, CON);
|
|
|
- OMAP_MMC_WRITE(host, CON, reg & ~(1 << 11));
|
|
|
- } else
|
|
|
- if (host->power_pin >= 0)
|
|
|
- omap_set_gpio_dataout(host->power_pin, 0);
|
|
|
+ struct mmc_omap_host *host;
|
|
|
+
|
|
|
+ host = slot->host;
|
|
|
+
|
|
|
+ if (slot->pdata->set_power != NULL)
|
|
|
+ slot->pdata->set_power(mmc_dev(slot->mmc), slot->id, power_on,
|
|
|
+ vdd);
|
|
|
+
|
|
|
+ if (cpu_is_omap24xx()) {
|
|
|
+ u16 w;
|
|
|
+
|
|
|
+ if (power_on) {
|
|
|
+ w = OMAP_MMC_READ(host, CON);
|
|
|
+ OMAP_MMC_WRITE(host, CON, w | (1 << 11));
|
|
|
+ } else {
|
|
|
+ w = OMAP_MMC_READ(host, CON);
|
|
|
+ OMAP_MMC_WRITE(host, CON, w & ~(1 << 11));
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
|
{
|
|
|
- struct mmc_omap_host *host = mmc_priv(mmc);
|
|
|
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
|
|
|
+ struct mmc_omap_host *host = slot->host;
|
|
|
int func_clk_rate = clk_get_rate(host->fclk);
|
|
|
int dsor;
|
|
|
|
|
@@ -936,7 +1222,8 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
|
|
|
|
if (dsor > 250)
|
|
|
dsor = 250;
|
|
|
- dsor++;
|
|
|
+
|
|
|
+ slot->fclk_freq = func_clk_rate / dsor;
|
|
|
|
|
|
if (ios->bus_width == MMC_BUS_WIDTH_4)
|
|
|
dsor |= 1 << 15;
|
|
@@ -946,28 +1233,40 @@ static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
|
|
|
|
static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
|
{
|
|
|
- struct mmc_omap_host *host = mmc_priv(mmc);
|
|
|
- int dsor;
|
|
|
- int i;
|
|
|
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
|
|
|
+ struct mmc_omap_host *host = slot->host;
|
|
|
+ int i, dsor;
|
|
|
+ int clk_enabled;
|
|
|
+
|
|
|
+ mmc_omap_select_slot(slot, 0);
|
|
|
|
|
|
dsor = mmc_omap_calc_divisor(mmc, ios);
|
|
|
- host->bus_mode = ios->bus_mode;
|
|
|
- host->hw_bus_mode = host->bus_mode;
|
|
|
|
|
|
+ if (ios->vdd != slot->vdd)
|
|
|
+ slot->vdd = ios->vdd;
|
|
|
+
|
|
|
+ clk_enabled = 0;
|
|
|
switch (ios->power_mode) {
|
|
|
case MMC_POWER_OFF:
|
|
|
- mmc_omap_power(host, 0);
|
|
|
+ mmc_omap_set_power(slot, 0, ios->vdd);
|
|
|
break;
|
|
|
case MMC_POWER_UP:
|
|
|
/* Cannot touch dsor yet, just power up MMC */
|
|
|
- mmc_omap_power(host, 1);
|
|
|
- return;
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
- clk_enable(host->fclk);
|
|
|
+ if (slot->bus_mode != ios->bus_mode) {
|
|
|
+ if (slot->pdata->set_bus_mode != NULL)
|
|
|
+ slot->pdata->set_bus_mode(mmc_dev(mmc), slot->id,
|
|
|
+ ios->bus_mode);
|
|
|
+ slot->bus_mode = ios->bus_mode;
|
|
|
+ }
|
|
|
|
|
|
/* On insanely high arm_per frequencies something sometimes
|
|
|
* goes somehow out of sync, and the POW bit is not being set,
|
|
@@ -975,43 +1274,143 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
|
* Writing to the CON register twice seems to do the trick. */
|
|
|
for (i = 0; i < 2; i++)
|
|
|
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);
|
|
|
}
|
|
|
- clk_disable(host->fclk);
|
|
|
-}
|
|
|
-
|
|
|
-static int mmc_omap_get_ro(struct mmc_host *mmc)
|
|
|
-{
|
|
|
- struct mmc_omap_host *host = mmc_priv(mmc);
|
|
|
|
|
|
- return host->wp_pin && omap_get_gpio_datain(host->wp_pin);
|
|
|
+exit:
|
|
|
+ mmc_omap_release_slot(slot, clk_enabled);
|
|
|
}
|
|
|
|
|
|
static const struct mmc_host_ops mmc_omap_ops = {
|
|
|
.request = mmc_omap_request,
|
|
|
.set_ios = mmc_omap_set_ios,
|
|
|
- .get_ro = mmc_omap_get_ro,
|
|
|
};
|
|
|
|
|
|
-static int __init mmc_omap_probe(struct platform_device *pdev)
|
|
|
+static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id)
|
|
|
{
|
|
|
- struct omap_mmc_conf *minfo = pdev->dev.platform_data;
|
|
|
+ struct mmc_omap_slot *slot = NULL;
|
|
|
struct mmc_host *mmc;
|
|
|
+ int r;
|
|
|
+
|
|
|
+ mmc = mmc_alloc_host(sizeof(struct mmc_omap_slot), host->dev);
|
|
|
+ if (mmc == NULL)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ slot = mmc_priv(mmc);
|
|
|
+ slot->host = host;
|
|
|
+ slot->mmc = mmc;
|
|
|
+ slot->id = id;
|
|
|
+ slot->pdata = &host->pdata->slots[id];
|
|
|
+
|
|
|
+ host->slots[id] = slot;
|
|
|
+
|
|
|
+ mmc->caps = MMC_CAP_MULTIWRITE;
|
|
|
+ if (host->pdata->conf.wire4)
|
|
|
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
|
|
|
+
|
|
|
+ mmc->ops = &mmc_omap_ops;
|
|
|
+ mmc->f_min = 400000;
|
|
|
+
|
|
|
+ if (cpu_class_is_omap2())
|
|
|
+ mmc->f_max = 48000000;
|
|
|
+ else
|
|
|
+ mmc->f_max = 24000000;
|
|
|
+ if (host->pdata->max_freq)
|
|
|
+ mmc->f_max = min(host->pdata->max_freq, mmc->f_max);
|
|
|
+ mmc->ocr_avail = slot->pdata->ocr_mask;
|
|
|
+
|
|
|
+ /* Use scatterlist DMA to reduce per-transfer costs.
|
|
|
+ * NOTE max_seg_size assumption that small blocks aren't
|
|
|
+ * normally used (except e.g. for reading SD registers).
|
|
|
+ */
|
|
|
+ mmc->max_phys_segs = 32;
|
|
|
+ mmc->max_hw_segs = 32;
|
|
|
+ mmc->max_blk_size = 2048; /* BLEN is 11 bits (+1) */
|
|
|
+ mmc->max_blk_count = 2048; /* NBLK is 11 bits (+1) */
|
|
|
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
|
|
|
+ mmc->max_seg_size = mmc->max_req_size;
|
|
|
+
|
|
|
+ r = mmc_add_host(mmc);
|
|
|
+ if (r < 0)
|
|
|
+ goto err_remove_host;
|
|
|
+
|
|
|
+ if (slot->pdata->name != NULL) {
|
|
|
+ r = device_create_file(&mmc->class_dev,
|
|
|
+ &dev_attr_slot_name);
|
|
|
+ if (r < 0)
|
|
|
+ goto err_remove_host;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (slot->pdata->get_cover_state != NULL) {
|
|
|
+ r = device_create_file(&mmc->class_dev,
|
|
|
+ &dev_attr_cover_switch);
|
|
|
+ if (r < 0)
|
|
|
+ goto err_remove_slot_name;
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+err_remove_slot_name:
|
|
|
+ if (slot->pdata->name != NULL)
|
|
|
+ device_remove_file(&mmc->class_dev, &dev_attr_slot_name);
|
|
|
+err_remove_host:
|
|
|
+ mmc_remove_host(mmc);
|
|
|
+ mmc_free_host(mmc);
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
+static void mmc_omap_remove_slot(struct mmc_omap_slot *slot)
|
|
|
+{
|
|
|
+ struct mmc_host *mmc = slot->mmc;
|
|
|
+
|
|
|
+ if (slot->pdata->name != NULL)
|
|
|
+ device_remove_file(&mmc->class_dev, &dev_attr_slot_name);
|
|
|
+ if (slot->pdata->get_cover_state != NULL)
|
|
|
+ device_remove_file(&mmc->class_dev, &dev_attr_cover_switch);
|
|
|
+
|
|
|
+ tasklet_kill(&slot->cover_tasklet);
|
|
|
+ del_timer_sync(&slot->cover_timer);
|
|
|
+ flush_scheduled_work();
|
|
|
+
|
|
|
+ mmc_remove_host(mmc);
|
|
|
+ mmc_free_host(mmc);
|
|
|
+}
|
|
|
+
|
|
|
+static int __init mmc_omap_probe(struct platform_device *pdev)
|
|
|
+{
|
|
|
+ struct omap_mmc_platform_data *pdata = pdev->dev.platform_data;
|
|
|
struct mmc_omap_host *host = NULL;
|
|
|
struct resource *res;
|
|
|
- int ret = 0;
|
|
|
+ int i, ret = 0;
|
|
|
int irq;
|
|
|
|
|
|
- if (minfo == NULL) {
|
|
|
+ if (pdata == NULL) {
|
|
|
dev_err(&pdev->dev, "platform data missing\n");
|
|
|
return -ENXIO;
|
|
|
}
|
|
|
+ if (pdata->nr_slots == 0) {
|
|
|
+ dev_err(&pdev->dev, "no slots\n");
|
|
|
+ return -ENXIO;
|
|
|
+ }
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
@@ -1019,28 +1418,46 @@ static int __init mmc_omap_probe(struct platform_device *pdev)
|
|
|
return -ENXIO;
|
|
|
|
|
|
res = request_mem_region(res->start, res->end - res->start + 1,
|
|
|
- pdev->name);
|
|
|
+ pdev->name);
|
|
|
if (res == NULL)
|
|
|
return -EBUSY;
|
|
|
|
|
|
- mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev);
|
|
|
- if (mmc == NULL) {
|
|
|
+ host = kzalloc(sizeof(struct mmc_omap_host), GFP_KERNEL);
|
|
|
+ if (host == NULL) {
|
|
|
ret = -ENOMEM;
|
|
|
goto err_free_mem_region;
|
|
|
}
|
|
|
|
|
|
- host = mmc_priv(mmc);
|
|
|
- host->mmc = mmc;
|
|
|
+ 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);
|
|
|
- init_timer(&host->dma_timer);
|
|
|
- host->dma_timer.function = mmc_omap_dma_timer;
|
|
|
- host->dma_timer.data = (unsigned long) host;
|
|
|
+ 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->pdata = pdata;
|
|
|
+ host->dev = &pdev->dev;
|
|
|
+ platform_set_drvdata(pdev, host);
|
|
|
|
|
|
host->id = pdev->id;
|
|
|
host->mem_res = res;
|
|
|
host->irq = irq;
|
|
|
|
|
|
+ host->use_dma = 1;
|
|
|
+ host->dma_ch = -1;
|
|
|
+
|
|
|
+ host->irq = irq;
|
|
|
+ host->phys_base = host->mem_res->start;
|
|
|
+ host->virt_base = (void __iomem *) IO_ADDRESS(host->phys_base);
|
|
|
+
|
|
|
if (cpu_is_omap24xx()) {
|
|
|
host->iclk = clk_get(&pdev->dev, "mmc_ick");
|
|
|
if (IS_ERR(host->iclk))
|
|
@@ -1058,109 +1475,34 @@ static int __init mmc_omap_probe(struct platform_device *pdev)
|
|
|
goto err_free_iclk;
|
|
|
}
|
|
|
|
|
|
- /* REVISIT:
|
|
|
- * Also, use minfo->cover to decide how to manage
|
|
|
- * the card detect sensing.
|
|
|
- */
|
|
|
- host->power_pin = minfo->power_pin;
|
|
|
- host->switch_pin = minfo->switch_pin;
|
|
|
- host->wp_pin = minfo->wp_pin;
|
|
|
- host->use_dma = 1;
|
|
|
- host->dma_ch = -1;
|
|
|
-
|
|
|
- host->irq = irq;
|
|
|
- host->phys_base = host->mem_res->start;
|
|
|
- host->virt_base = (void __iomem *) IO_ADDRESS(host->phys_base);
|
|
|
-
|
|
|
- mmc->ops = &mmc_omap_ops;
|
|
|
- mmc->f_min = 400000;
|
|
|
- mmc->f_max = 24000000;
|
|
|
- mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
|
- mmc->caps = MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK;
|
|
|
-
|
|
|
- if (minfo->wire4)
|
|
|
- mmc->caps |= MMC_CAP_4_BIT_DATA;
|
|
|
-
|
|
|
- /* Use scatterlist DMA to reduce per-transfer costs.
|
|
|
- * NOTE max_seg_size assumption that small blocks aren't
|
|
|
- * normally used (except e.g. for reading SD registers).
|
|
|
- */
|
|
|
- mmc->max_phys_segs = 32;
|
|
|
- mmc->max_hw_segs = 32;
|
|
|
- mmc->max_blk_size = 2048; /* BLEN is 11 bits (+1) */
|
|
|
- mmc->max_blk_count = 2048; /* NBLK is 11 bits (+1) */
|
|
|
- mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
|
|
|
- mmc->max_seg_size = mmc->max_req_size;
|
|
|
-
|
|
|
- if (host->power_pin >= 0) {
|
|
|
- if ((ret = omap_request_gpio(host->power_pin)) != 0) {
|
|
|
- dev_err(mmc_dev(host->mmc),
|
|
|
- "Unable to get GPIO pin for MMC power\n");
|
|
|
- goto err_free_fclk;
|
|
|
- }
|
|
|
- omap_set_gpio_direction(host->power_pin, 0);
|
|
|
- }
|
|
|
-
|
|
|
ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host);
|
|
|
if (ret)
|
|
|
- goto err_free_power_gpio;
|
|
|
+ goto err_free_fclk;
|
|
|
|
|
|
- host->dev = &pdev->dev;
|
|
|
- platform_set_drvdata(pdev, host);
|
|
|
+ if (pdata->init != NULL) {
|
|
|
+ ret = pdata->init(&pdev->dev);
|
|
|
+ if (ret < 0)
|
|
|
+ goto err_free_irq;
|
|
|
+ }
|
|
|
|
|
|
- if (host->switch_pin >= 0) {
|
|
|
- INIT_WORK(&host->switch_work, mmc_omap_switch_handler);
|
|
|
- init_timer(&host->switch_timer);
|
|
|
- host->switch_timer.function = mmc_omap_switch_timer;
|
|
|
- host->switch_timer.data = (unsigned long) host;
|
|
|
- if (omap_request_gpio(host->switch_pin) != 0) {
|
|
|
- dev_warn(mmc_dev(host->mmc), "Unable to get GPIO pin for MMC cover switch\n");
|
|
|
- host->switch_pin = -1;
|
|
|
- goto no_switch;
|
|
|
- }
|
|
|
+ host->nr_slots = pdata->nr_slots;
|
|
|
+ for (i = 0; i < pdata->nr_slots; i++) {
|
|
|
+ ret = mmc_omap_new_slot(host, i);
|
|
|
+ if (ret < 0) {
|
|
|
+ while (--i >= 0)
|
|
|
+ mmc_omap_remove_slot(host->slots[i]);
|
|
|
|
|
|
- omap_set_gpio_direction(host->switch_pin, 1);
|
|
|
- ret = request_irq(OMAP_GPIO_IRQ(host->switch_pin),
|
|
|
- mmc_omap_switch_irq, IRQF_TRIGGER_RISING, DRIVER_NAME, host);
|
|
|
- if (ret) {
|
|
|
- dev_warn(mmc_dev(host->mmc), "Unable to get IRQ for MMC cover switch\n");
|
|
|
- omap_free_gpio(host->switch_pin);
|
|
|
- host->switch_pin = -1;
|
|
|
- goto no_switch;
|
|
|
- }
|
|
|
- ret = device_create_file(&pdev->dev, &dev_attr_cover_switch);
|
|
|
- if (ret == 0) {
|
|
|
- ret = device_create_file(&pdev->dev, &dev_attr_enable_poll);
|
|
|
- if (ret != 0)
|
|
|
- device_remove_file(&pdev->dev, &dev_attr_cover_switch);
|
|
|
+ goto err_plat_cleanup;
|
|
|
}
|
|
|
- if (ret) {
|
|
|
- dev_warn(mmc_dev(host->mmc), "Unable to create sysfs attributes\n");
|
|
|
- free_irq(OMAP_GPIO_IRQ(host->switch_pin), host);
|
|
|
- omap_free_gpio(host->switch_pin);
|
|
|
- host->switch_pin = -1;
|
|
|
- goto no_switch;
|
|
|
- }
|
|
|
- if (mmc_omap_enable_poll && mmc_omap_cover_is_open(host))
|
|
|
- schedule_work(&host->switch_work);
|
|
|
}
|
|
|
|
|
|
- mmc_add_host(mmc);
|
|
|
-
|
|
|
return 0;
|
|
|
|
|
|
-no_switch:
|
|
|
- /* FIXME: Free other resources too. */
|
|
|
- if (host) {
|
|
|
- if (host->iclk && !IS_ERR(host->iclk))
|
|
|
- clk_put(host->iclk);
|
|
|
- if (host->fclk && !IS_ERR(host->fclk))
|
|
|
- clk_put(host->fclk);
|
|
|
- mmc_free_host(host->mmc);
|
|
|
- }
|
|
|
-err_free_power_gpio:
|
|
|
- if (host->power_pin >= 0)
|
|
|
- omap_free_gpio(host->power_pin);
|
|
|
+err_plat_cleanup:
|
|
|
+ if (pdata->cleanup)
|
|
|
+ pdata->cleanup(&pdev->dev);
|
|
|
+err_free_irq:
|
|
|
+ free_irq(host->irq, host);
|
|
|
err_free_fclk:
|
|
|
clk_put(host->fclk);
|
|
|
err_free_iclk:
|
|
@@ -1169,7 +1511,7 @@ err_free_iclk:
|
|
|
clk_put(host->iclk);
|
|
|
}
|
|
|
err_free_mmc_host:
|
|
|
- mmc_free_host(host->mmc);
|
|
|
+ kfree(host);
|
|
|
err_free_mem_region:
|
|
|
release_mem_region(res->start, res->end - res->start + 1);
|
|
|
return ret;
|
|
@@ -1178,25 +1520,18 @@ err_free_mem_region:
|
|
|
static int mmc_omap_remove(struct platform_device *pdev)
|
|
|
{
|
|
|
struct mmc_omap_host *host = platform_get_drvdata(pdev);
|
|
|
+ int i;
|
|
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
|
|
BUG_ON(host == NULL);
|
|
|
|
|
|
- mmc_remove_host(host->mmc);
|
|
|
- free_irq(host->irq, host);
|
|
|
+ for (i = 0; i < host->nr_slots; i++)
|
|
|
+ mmc_omap_remove_slot(host->slots[i]);
|
|
|
+
|
|
|
+ if (host->pdata->cleanup)
|
|
|
+ host->pdata->cleanup(&pdev->dev);
|
|
|
|
|
|
- if (host->power_pin >= 0)
|
|
|
- omap_free_gpio(host->power_pin);
|
|
|
- if (host->switch_pin >= 0) {
|
|
|
- device_remove_file(&pdev->dev, &dev_attr_enable_poll);
|
|
|
- device_remove_file(&pdev->dev, &dev_attr_cover_switch);
|
|
|
- free_irq(OMAP_GPIO_IRQ(host->switch_pin), host);
|
|
|
- omap_free_gpio(host->switch_pin);
|
|
|
- host->switch_pin = -1;
|
|
|
- del_timer_sync(&host->switch_timer);
|
|
|
- flush_scheduled_work();
|
|
|
- }
|
|
|
if (host->iclk && !IS_ERR(host->iclk))
|
|
|
clk_put(host->iclk);
|
|
|
if (host->fclk && !IS_ERR(host->fclk))
|
|
@@ -1205,7 +1540,7 @@ static int mmc_omap_remove(struct platform_device *pdev)
|
|
|
release_mem_region(pdev->resource[0].start,
|
|
|
pdev->resource[0].end - pdev->resource[0].start + 1);
|
|
|
|
|
|
- mmc_free_host(host->mmc);
|
|
|
+ kfree(host);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -1213,35 +1548,47 @@ static int mmc_omap_remove(struct platform_device *pdev)
|
|
|
#ifdef CONFIG_PM
|
|
|
static int mmc_omap_suspend(struct platform_device *pdev, pm_message_t mesg)
|
|
|
{
|
|
|
- int ret = 0;
|
|
|
+ int i, ret = 0;
|
|
|
struct mmc_omap_host *host = platform_get_drvdata(pdev);
|
|
|
|
|
|
- if (host && host->suspended)
|
|
|
+ if (host == NULL || host->suspended)
|
|
|
return 0;
|
|
|
|
|
|
- if (host) {
|
|
|
- ret = mmc_suspend_host(host->mmc, mesg);
|
|
|
- if (ret == 0)
|
|
|
- host->suspended = 1;
|
|
|
+ for (i = 0; i < host->nr_slots; i++) {
|
|
|
+ struct mmc_omap_slot *slot;
|
|
|
+
|
|
|
+ slot = host->slots[i];
|
|
|
+ ret = mmc_suspend_host(slot->mmc, mesg);
|
|
|
+ if (ret < 0) {
|
|
|
+ while (--i >= 0) {
|
|
|
+ slot = host->slots[i];
|
|
|
+ mmc_resume_host(slot->mmc);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
}
|
|
|
- return ret;
|
|
|
+ host->suspended = 1;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static int mmc_omap_resume(struct platform_device *pdev)
|
|
|
{
|
|
|
- int ret = 0;
|
|
|
+ int i, ret = 0;
|
|
|
struct mmc_omap_host *host = platform_get_drvdata(pdev);
|
|
|
|
|
|
- if (host && !host->suspended)
|
|
|
+ if (host == NULL || !host->suspended)
|
|
|
return 0;
|
|
|
|
|
|
- if (host) {
|
|
|
- ret = mmc_resume_host(host->mmc);
|
|
|
- if (ret == 0)
|
|
|
- host->suspended = 0;
|
|
|
- }
|
|
|
+ for (i = 0; i < host->nr_slots; i++) {
|
|
|
+ struct mmc_omap_slot *slot;
|
|
|
+ slot = host->slots[i];
|
|
|
+ ret = mmc_resume_host(slot->mmc);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
|
|
|
- return ret;
|
|
|
+ host->suspended = 0;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
}
|
|
|
#else
|
|
|
#define mmc_omap_suspend NULL
|