diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-09-24 21:22:33 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-09-24 21:22:33 +0100 |
commit | baea7b946f00a291b166ccae7fcfed6c01530cc6 (patch) | |
tree | 4aa275fbdbec9c7b9b4629e8bee2bbecd3c6a6af /drivers/mmc/host/omap_hsmmc.c | |
parent | ae19ffbadc1b2100285a5b5b3d0a4e0a11390904 (diff) | |
parent | 94e0fb086fc5663c38bbc0fe86d698be8314f82f (diff) |
Merge branch 'origin' into for-linus
Conflicts:
MAINTAINERS
Diffstat (limited to 'drivers/mmc/host/omap_hsmmc.c')
-rw-r--r-- | drivers/mmc/host/omap_hsmmc.c | 1083 |
1 files changed, 888 insertions, 195 deletions
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 1cf9cfb3b64..4487cc09791 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -17,6 +17,8 @@ #include <linux/module.h> #include <linux/init.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/dma-mapping.h> @@ -25,6 +27,7 @@ #include <linux/timer.h> #include <linux/clk.h> #include <linux/mmc/host.h> +#include <linux/mmc/core.h> #include <linux/io.h> #include <linux/semaphore.h> #include <mach/dma.h> @@ -35,6 +38,7 @@ /* OMAP HSMMC Host Controller Registers */ #define OMAP_HSMMC_SYSCONFIG 0x0010 +#define OMAP_HSMMC_SYSSTATUS 0x0014 #define OMAP_HSMMC_CON 0x002C #define OMAP_HSMMC_BLK 0x0104 #define OMAP_HSMMC_ARG 0x0108 @@ -70,6 +74,8 @@ #define DTO_MASK 0x000F0000 #define DTO_SHIFT 16 #define INT_EN_MASK 0x307F0033 +#define BWR_ENABLE (1 << 4) +#define BRR_ENABLE (1 << 5) #define INIT_STREAM (1 << 1) #define DP_SELECT (1 << 21) #define DDIR (1 << 4) @@ -92,6 +98,8 @@ #define DUAL_VOLT_OCR_BIT 7 #define SRC (1 << 25) #define SRD (1 << 26) +#define SOFTRESET (1 << 1) +#define RESETDONE (1 << 0) /* * FIXME: Most likely all the data using these _DEVID defines should come @@ -101,11 +109,18 @@ #define OMAP_MMC1_DEVID 0 #define OMAP_MMC2_DEVID 1 #define OMAP_MMC3_DEVID 2 +#define OMAP_MMC4_DEVID 3 +#define OMAP_MMC5_DEVID 4 #define MMC_TIMEOUT_MS 20 #define OMAP_MMC_MASTER_CLOCK 96000000 #define DRIVER_NAME "mmci-omap-hs" +/* Timeouts for entering power saving states on inactivity, msec */ +#define OMAP_MMC_DISABLED_TIMEOUT 100 +#define OMAP_MMC_SLEEP_TIMEOUT 1000 +#define OMAP_MMC_OFF_TIMEOUT 8000 + /* * One controller can have multiple slots, like on some omap boards using * omap.c controller driver. Luckily this is not currently done on any known @@ -122,7 +137,7 @@ #define OMAP_HSMMC_WRITE(base, reg, val) \ __raw_writel((val), (base) + OMAP_HSMMC_##reg) -struct mmc_omap_host { +struct omap_hsmmc_host { struct device *dev; struct mmc_host *mmc; struct mmc_request *mrq; @@ -135,27 +150,35 @@ struct mmc_omap_host { struct work_struct mmc_carddetect_work; void __iomem *base; resource_size_t mapbase; + spinlock_t irq_lock; /* Prevent races with irq handler */ + unsigned long flags; unsigned int id; unsigned int dma_len; unsigned int dma_sg_idx; unsigned char bus_mode; + unsigned char power_mode; u32 *buffer; u32 bytesleft; int suspended; int irq; - int carddetect; int use_dma, dma_ch; int dma_line_tx, dma_line_rx; int slot_id; - int dbclk_enabled; + int got_dbclk; int response_busy; + int context_loss; + int dpm_state; + int vdd; + int protect_card; + int reqs_blocked; + struct omap_mmc_platform_data *pdata; }; /* * Stop clock to the card */ -static void omap_mmc_stop_clock(struct mmc_omap_host *host) +static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host) { OMAP_HSMMC_WRITE(host->base, SYSCTL, OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN); @@ -163,15 +186,178 @@ static void omap_mmc_stop_clock(struct mmc_omap_host *host) dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n"); } +#ifdef CONFIG_PM + +/* + * Restore the MMC host context, if it was lost as result of a + * power state change. + */ +static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host) +{ + struct mmc_ios *ios = &host->mmc->ios; + struct omap_mmc_platform_data *pdata = host->pdata; + int context_loss = 0; + u32 hctl, capa, con; + u16 dsor = 0; + unsigned long timeout; + + if (pdata->get_context_loss_count) { + context_loss = pdata->get_context_loss_count(host->dev); + if (context_loss < 0) + return 1; + } + + dev_dbg(mmc_dev(host->mmc), "context was %slost\n", + context_loss == host->context_loss ? "not " : ""); + if (host->context_loss == context_loss) + return 1; + + /* Wait for hardware reset */ + timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); + while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE + && time_before(jiffies, timeout)) + ; + + /* Do software reset */ + OMAP_HSMMC_WRITE(host->base, SYSCONFIG, SOFTRESET); + timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); + while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE + && time_before(jiffies, timeout)) + ; + + OMAP_HSMMC_WRITE(host->base, SYSCONFIG, + OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE); + + if (host->id == OMAP_MMC1_DEVID) { + if (host->power_mode != MMC_POWER_OFF && + (1 << ios->vdd) <= MMC_VDD_23_24) + hctl = SDVS18; + else + hctl = SDVS30; + capa = VS30 | VS18; + } else { + hctl = SDVS18; + capa = VS18; + } + + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | hctl); + + OMAP_HSMMC_WRITE(host->base, CAPA, + OMAP_HSMMC_READ(host->base, CAPA) | capa); + + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | SDBP); + + timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); + while ((OMAP_HSMMC_READ(host->base, HCTL) & SDBP) != SDBP + && time_before(jiffies, timeout)) + ; + + OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR); + OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); + OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); + + /* Do not initialize card-specific things if the power is off */ + if (host->power_mode == MMC_POWER_OFF) + goto out; + + con = OMAP_HSMMC_READ(host->base, CON); + switch (ios->bus_width) { + case MMC_BUS_WIDTH_8: + OMAP_HSMMC_WRITE(host->base, CON, con | DW8); + break; + case MMC_BUS_WIDTH_4: + OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8); + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT); + break; + case MMC_BUS_WIDTH_1: + OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8); + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT); + break; + } + + if (ios->clock) { + dsor = OMAP_MMC_MASTER_CLOCK / ios->clock; + if (dsor < 1) + dsor = 1; + + if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock) + dsor++; + + if (dsor > 250) + dsor = 250; + } + + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN); + OMAP_HSMMC_WRITE(host->base, SYSCTL, (dsor << 6) | (DTO << 16)); + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) | ICE); + + timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); + while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS + && time_before(jiffies, timeout)) + ; + + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) | CEN); + + con = OMAP_HSMMC_READ(host->base, CON); + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) + OMAP_HSMMC_WRITE(host->base, CON, con | OD); + else + OMAP_HSMMC_WRITE(host->base, CON, con & ~OD); +out: + host->context_loss = context_loss; + + dev_dbg(mmc_dev(host->mmc), "context is restored\n"); + return 0; +} + +/* + * Save the MMC host context (store the number of power state changes so far). + */ +static void omap_hsmmc_context_save(struct omap_hsmmc_host *host) +{ + struct omap_mmc_platform_data *pdata = host->pdata; + int context_loss; + + if (pdata->get_context_loss_count) { + context_loss = pdata->get_context_loss_count(host->dev); + if (context_loss < 0) + return; + host->context_loss = context_loss; + } +} + +#else + +static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host) +{ + return 0; +} + +static void omap_hsmmc_context_save(struct omap_hsmmc_host *host) +{ +} + +#endif + /* * Send init stream sequence to card * before sending IDLE command */ -static void send_init_stream(struct mmc_omap_host *host) +static void send_init_stream(struct omap_hsmmc_host *host) { int reg = 0; unsigned long timeout; + if (host->protect_card) + return; + disable_irq(host->irq); OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM); @@ -183,51 +369,53 @@ static void send_init_stream(struct mmc_omap_host *host) OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM); + + OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR); + OMAP_HSMMC_READ(host->base, STAT); + enable_irq(host->irq); } static inline -int mmc_omap_cover_is_closed(struct mmc_omap_host *host) +int omap_hsmmc_cover_is_closed(struct omap_hsmmc_host *host) { int r = 1; - if (host->pdata->slots[host->slot_id].get_cover_state) - r = host->pdata->slots[host->slot_id].get_cover_state(host->dev, - host->slot_id); + if (mmc_slot(host).get_cover_state) + r = mmc_slot(host).get_cover_state(host->dev, host->slot_id); return r; } static ssize_t -mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr, +omap_hsmmc_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_host *host = mmc_priv(mmc); + struct omap_hsmmc_host *host = mmc_priv(mmc); - return sprintf(buf, "%s\n", mmc_omap_cover_is_closed(host) ? "closed" : - "open"); + return sprintf(buf, "%s\n", + omap_hsmmc_cover_is_closed(host) ? "closed" : "open"); } -static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); +static DEVICE_ATTR(cover_switch, S_IRUGO, omap_hsmmc_show_cover_switch, NULL); static ssize_t -mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr, +omap_hsmmc_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_host *host = mmc_priv(mmc); - struct omap_mmc_slot_data slot = host->pdata->slots[host->slot_id]; + struct omap_hsmmc_host *host = mmc_priv(mmc); - return sprintf(buf, "%s\n", slot.name); + return sprintf(buf, "%s\n", mmc_slot(host).name); } -static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL); +static DEVICE_ATTR(slot_name, S_IRUGO, omap_hsmmc_show_slot_name, NULL); /* * Configure the response type and send the cmd. */ static void -mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd, +omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd, struct mmc_data *data) { int cmdreg = 0, resptype = 0, cmdtype = 0; @@ -241,7 +429,12 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd, */ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR); OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); - OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); + + if (host->use_dma) + OMAP_HSMMC_WRITE(host->base, IE, + INT_EN_MASK & ~(BRR_ENABLE | BWR_ENABLE)); + else + OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); host->response_busy = 0; if (cmd->flags & MMC_RSP_PRESENT) { @@ -275,12 +468,20 @@ mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd, if (host->use_dma) cmdreg |= DMA_EN; + /* + * In an interrupt context (i.e. STOP command), the spinlock is unlocked + * by the interrupt handler, otherwise (i.e. for a new request) it is + * unlocked here. + */ + if (!in_interrupt()) + spin_unlock_irqrestore(&host->irq_lock, host->flags); + OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg); OMAP_HSMMC_WRITE(host->base, CMD, cmdreg); } static int -mmc_omap_get_dma_dir(struct mmc_omap_host *host, struct mmc_data *data) +omap_hsmmc_get_dma_dir(struct omap_hsmmc_host *host, struct mmc_data *data) { if (data->flags & MMC_DATA_WRITE) return DMA_TO_DEVICE; @@ -292,11 +493,18 @@ mmc_omap_get_dma_dir(struct mmc_omap_host *host, struct mmc_data *data) * Notify the transfer complete to MMC core */ static void -mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) +omap_hsmmc_xfer_done(struct omap_hsmmc_host *host, struct mmc_data *data) { if (!data) { struct mmc_request *mrq = host->mrq; + /* TC before CC from CMD6 - don't know why, but it happens */ + if (host->cmd && host->cmd->opcode == 6 && + host->response_busy) { + host->response_busy = 0; + return; + } + host->mrq = NULL; mmc_request_done(host->mmc, mrq); return; @@ -306,7 +514,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) if (host->use_dma && host->dma_ch != -1) dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, - mmc_omap_get_dma_dir(host, data)); + omap_hsmmc_get_dma_dir(host, data)); if (!data->error) data->bytes_xfered += data->blocks * (data->blksz); @@ -318,14 +526,14 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) mmc_request_done(host->mmc, data->mrq); return; } - mmc_omap_start_command(host, data->stop, NULL); + omap_hsmmc_start_command(host, data->stop, NULL); } /* * Notify the core about command completion */ static void -mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) +omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd) { host->cmd = NULL; @@ -350,13 +558,13 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) /* * DMA clean up for command errors */ -static void mmc_dma_cleanup(struct mmc_omap_host *host, int errno) +static void omap_hsmmc_dma_cleanup(struct omap_hsmmc_host *host, int errno) { host->data->error = errno; if (host->use_dma && host->dma_ch != -1) { dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len, - mmc_omap_get_dma_dir(host, host->data)); + omap_hsmmc_get_dma_dir(host, host->data)); omap_free_dma(host->dma_ch); host->dma_ch = -1; up(&host->sem); @@ -368,10 +576,10 @@ static void mmc_dma_cleanup(struct mmc_omap_host *host, int errno) * Readable error output */ #ifdef CONFIG_MMC_DEBUG -static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status) +static void omap_hsmmc_report_irq(struct omap_hsmmc_host *host, u32 status) { /* --- means reserved bit without definition at documentation */ - static const char *mmc_omap_status_bits[] = { + static const char *omap_hsmmc_status_bits[] = { "CC", "TC", "BGE", "---", "BWR", "BRR", "---", "---", "CIRQ", "OBI", "---", "---", "---", "---", "---", "ERRI", "CTO", "CCRC", "CEB", "CIE", "DTO", "DCRC", "DEB", "---", "ACE", "---", @@ -384,9 +592,9 @@ static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status) len = sprintf(buf, "MMC IRQ 0x%x :", status); buf += len; - for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++) + for (i = 0; i < ARRAY_SIZE(omap_hsmmc_status_bits); i++) if (status & (1 << i)) { - len = sprintf(buf, " %s", mmc_omap_status_bits[i]); + len = sprintf(buf, " %s", omap_hsmmc_status_bits[i]); buf += len; } @@ -401,8 +609,8 @@ static void mmc_omap_report_irq(struct mmc_omap_host *host, u32 status) * SRC or SRD bit of SYSCTL register * Can be called from interrupt context */ -static inline void mmc_omap_reset_controller_fsm(struct mmc_omap_host *host, - unsigned long bit) +static inline void omap_hsmmc_reset_controller_fsm(struct omap_hsmmc_host *host, + unsigned long bit) { unsigned long i = 0; unsigned long limit = (loops_per_jiffy * @@ -424,17 +632,20 @@ static inline void mmc_omap_reset_controller_fsm(struct mmc_omap_host *host, /* * MMC controller IRQ handler */ -static irqreturn_t mmc_omap_irq(int irq, void *dev_id) +static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id) { - struct mmc_omap_host *host = dev_id; + struct omap_hsmmc_host *host = dev_id; struct mmc_data *data; int end_cmd = 0, end_trans = 0, status; + spin_lock(&host->irq_lock); + if (host->mrq == NULL) { OMAP_HSMMC_WRITE(host->base, STAT, OMAP_HSMMC_READ(host->base, STAT)); /* Flush posted write */ OMAP_HSMMC_READ(host->base, STAT); + spin_unlock(&host->irq_lock); return IRQ_HANDLED; } @@ -444,13 +655,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & ERR) { #ifdef CONFIG_MMC_DEBUG - mmc_omap_report_irq(host, status); + omap_hsmmc_report_irq(host, status); #endif if ((status & CMD_TIMEOUT) || (status & CMD_CRC)) { if (host->cmd) { if (status & CMD_TIMEOUT) { - mmc_omap_reset_controller_fsm(host, SRC); + omap_hsmmc_reset_controller_fsm(host, + SRC); host->cmd->error = -ETIMEDOUT; } else { host->cmd->error = -EILSEQ; @@ -459,9 +671,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } if (host->data || host->response_busy) { if (host->data) - mmc_dma_cleanup(host, -ETIMEDOUT); + omap_hsmmc_dma_cleanup(host, + -ETIMEDOUT); host->response_busy = 0; - mmc_omap_reset_controller_fsm(host, SRD); + omap_hsmmc_reset_controller_fsm(host, SRD); } } if ((status & DATA_TIMEOUT) || @@ -471,11 +684,11 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) -ETIMEDOUT : -EILSEQ; if (host->data) - mmc_dma_cleanup(host, err); + omap_hsmmc_dma_cleanup(host, err); else host->mrq->cmd->error = err; host->response_busy = 0; - mmc_omap_reset_controller_fsm(host, SRD); + omap_hsmmc_reset_controller_fsm(host, SRD); end_trans = 1; } } @@ -494,14 +707,16 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) OMAP_HSMMC_READ(host->base, STAT); if (end_cmd || ((status & CC) && host->cmd)) - mmc_omap_cmd_done(host, host->cmd); - if (end_trans || (status & TC)) - mmc_omap_xfer_done(host, data); + omap_hsmmc_cmd_done(host, host->cmd); + if ((end_trans || (status & TC)) && host->mrq) + omap_hsmmc_xfer_done(host, data); + + spin_unlock(&host->irq_lock); return IRQ_HANDLED; } -static void set_sd_bus_power(struct mmc_omap_host *host) +static void set_sd_bus_power(struct omap_hsmmc_host *host) { unsigned long i; @@ -521,7 +736,7 @@ static void set_sd_bus_power(struct mmc_omap_host *host) * The MMC2 transceiver controls are used instead of DAT4..DAT7. * Some chips, like eMMC ones, use internal transceivers. */ -static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd) +static int omap_hsmmc_switch_opcond(struct omap_hsmmc_host *host, int vdd) { u32 reg_val = 0; int ret; @@ -529,22 +744,24 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd) /* Disable the clocks */ clk_disable(host->fclk); clk_disable(host->iclk); - clk_disable(host->dbclk); + if (host->got_dbclk) + clk_disable(host->dbclk); /* Turn the power off */ ret = mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); - if (ret != 0) - goto err; /* Turn the power ON with given VDD 1.8 or 3.0v */ - ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd); + if (!ret) + ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1, + vdd); + clk_enable(host->iclk); + clk_enable(host->fclk); + if (host->got_dbclk) + clk_enable(host->dbclk); + if (ret != 0) goto err; - clk_enable(host->fclk); - clk_enable(host->iclk); - clk_enable(host->dbclk); - OMAP_HSMMC_WRITE(host->base, HCTL, OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR); reg_val = OMAP_HSMMC_READ(host->base, HCTL); @@ -552,7 +769,7 @@ static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd) /* * If a MMC dual voltage card is detected, the set_ios fn calls * this fn with VDD bit set for 1.8V. Upon card removal from the - * slot, omap_mmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF. + * slot, omap_hsmmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF. * * Cope with a bit of slop in the range ... per data sheets: * - "1.8V" for vdds_mmc1/vdds_mmc1a can be up to 2.45V max, @@ -578,25 +795,59 @@ err: return ret; } +/* Protect the card while the cover is open */ +static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host) +{ + if (!mmc_slot(host).get_cover_state) + return; + + host->reqs_blocked = 0; + if (mmc_slot(host).get_cover_state(host->dev, host->slot_id)) { + if (host->protect_card) { + printk(KERN_INFO "%s: cover is closed, " + "card is now accessible\n", + mmc_hostname(host->mmc)); + host->protect_card = 0; + } + } else { + if (!host->protect_card) { + printk(KERN_INFO "%s: cover is open, " + "card is now inaccessible\n", + mmc_hostname(host->mmc)); + host->protect_card = 1; + } + } +} + /* * Work Item to notify the core about card insertion/removal */ -static void mmc_omap_detect(struct work_struct *work) +static void omap_hsmmc_detect(struct work_struct *work) { - struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, - mmc_carddetect_work); + struct omap_hsmmc_host *host = + container_of(work, struct omap_hsmmc_host, mmc_carddetect_work); struct omap_mmc_slot_data *slot = &mmc_slot(host); + int carddetect; - if (mmc_slot(host).card_detect) - host->carddetect = slot->card_detect(slot->card_detect_irq); - else - host->carddetect = -ENOSYS; + if (host->suspended) + return; sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch"); - if (host->carddetect) { + + if (slot->card_detect) + carddetect = slot->card_detect(slot->card_detect_irq); + else { + omap_hsmmc_protect_card(host); + carddetect = -ENOSYS; + } + + if (carddetect) { mmc_detect_change(host->mmc, (HZ * 200) / 1000); } else { - mmc_omap_reset_controller_fsm(host, SRD); + mmc_host_enable(host->mmc); + omap_hsmmc_reset_controller_fsm(host, SRD); + mmc_host_lazy_disable(host->mmc); + mmc_detect_change(host->mmc, (HZ * 50) / 1000); } } @@ -604,16 +855,18 @@ static void mmc_omap_detect(struct work_struct *work) /* * ISR for handling card insertion and removal */ -static irqreturn_t omap_mmc_cd_handler(int irq, void *dev_id) +static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id) { - struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id; + struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id; + if (host->suspended) + return IRQ_HANDLED; schedule_work(&host->mmc_carddetect_work); return IRQ_HANDLED; } -static int mmc_omap_get_dma_sync_dev(struct mmc_omap_host *host, +static int omap_hsmmc_get_dma_sync_dev(struct omap_hsmmc_host *host, struct mmc_data *data) { int sync_dev; @@ -625,7 +878,7 @@ static int mmc_omap_get_dma_sync_dev(struct mmc_omap_host *host, return sync_dev; } -static void mmc_omap_config_dma_params(struct mmc_omap_host *host, +static void omap_hsmmc_config_dma_params(struct omap_hsmmc_host *host, struct mmc_data *data, struct scatterlist *sgl) { @@ -639,7 +892,7 @@ static void mmc_omap_config_dma_params(struct mmc_omap_host *host, sg_dma_address(sgl), 0, 0); } else { omap_set_dma_src_params(dma_ch, 0, OMAP_DMA_AMODE_CONSTANT, - (host->mapbase + OMAP_HSMMC_DATA), 0, 0); + (host->mapbase + OMAP_HSMMC_DATA), 0, 0); omap_set_dma_dest_params(dma_ch, 0, OMAP_DMA_AMODE_POST_INC, sg_dma_address(sgl), 0, 0); } @@ -649,7 +902,7 @@ static void mmc_omap_config_dma_params(struct mmc_omap_host *host, omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32, blksz / 4, nblk, OMAP_DMA_SYNC_FRAME, - mmc_omap_get_dma_sync_dev(host, data), + omap_hsmmc_get_dma_sync_dev(host, data), !(data->flags & MMC_DATA_WRITE)); omap_start_dma(dma_ch); @@ -658,9 +911,9 @@ static void mmc_omap_config_dma_params(struct mmc_omap_host *host, /* * DMA call back function */ -static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data) +static void omap_hsmmc_dma_cb(int lch, u16 ch_status, void *data) { - struct mmc_omap_host *host = data; + struct omap_hsmmc_host *host = data; if (ch_status & OMAP2_DMA_MISALIGNED_ERR_IRQ) dev_dbg(mmc_dev(host->mmc), "MISALIGNED_ADRS_ERR\n"); @@ -671,7 +924,7 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data) host->dma_sg_idx++; if (host->dma_sg_idx < host->dma_len) { /* Fire up the next transfer. */ - mmc_omap_config_dma_params(host, host->data, + omap_hsmmc_config_dma_params(host, host->data, host->data->sg + host->dma_sg_idx); return; } @@ -688,14 +941,14 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data) /* * Routine to configure and start DMA for the MMC card */ -static int -mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req) +static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host, + struct mmc_request *req) { int dma_ch = 0, ret = 0, err = 1, i; struct mmc_data *data = req->data; /* Sanity check: all the SG entries must be aligned by block size. */ - for (i = 0; i < host->dma_len; i++) { + for (i = 0; i < data->sg_len; i++) { struct scatterlist *sgl; sgl = data->sg + i; @@ -726,8 +979,8 @@ mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req) return err; } - ret = omap_request_dma(mmc_omap_get_dma_sync_dev(host, data), "MMC/SD", - mmc_omap_dma_cb,host, &dma_ch); + ret = omap_request_dma(omap_hsmmc_get_dma_sync_dev(host, data), + "MMC/SD", omap_hsmmc_dma_cb, host, &dma_ch); if (ret != 0) { dev_err(mmc_dev(host->mmc), "%s: omap_request_dma() failed with %d\n", @@ -736,17 +989,18 @@ mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req) } host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, mmc_omap_get_dma_dir(host, data)); + data->sg_len, omap_hsmmc_get_dma_dir(host, data)); host->dma_ch = dma_ch; host->dma_sg_idx = 0; - mmc_omap_config_dma_params(host, data, data->sg); + omap_hsmmc_config_dma_params(host, data, data->sg); return 0; } -static void set_data_timeout(struct mmc_omap_host *host, - struct mmc_request *req) +static void set_data_timeout(struct omap_hsmmc_host *host, + unsigned int timeout_ns, + unsigned int timeout_clks) { unsigned int timeout, cycle_ns; uint32_t reg, clkd, dto = 0; @@ -757,8 +1011,8 @@ static void set_data_timeout(struct mmc_omap_host *host, clkd = 1; cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd); - timeout = req->data->timeout_ns / cycle_ns; - timeout += req->data->timeout_clks; + timeout = timeout_ns / cycle_ns; + timeout += timeout_clks; if (timeout) { while ((timeout & 0x80000000) == 0) { dto += 1; @@ -785,22 +1039,28 @@ static void set_data_timeout(struct mmc_omap_host *host, * Configure block length for MMC/SD cards and initiate the transfer. */ static int -mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) +omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req) { int ret; host->data = req->data; if (req->data == NULL) { OMAP_HSMMC_WRITE(host->base, BLK, 0); + /* + * Set an arbitrary 100ms data timeout for commands with + * busy signal. + */ + if (req->cmd->flags & MMC_RSP_BUSY) + set_data_timeout(host, 100000000U, 0); return 0; } OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz) | (req->data->blocks << 16)); - set_data_timeout(host, req); + set_data_timeout(host, req->data->timeout_ns, req->data->timeout_clks); if (host->use_dma) { - ret = mmc_omap_start_dma_transfer(host, req); + ret = omap_hsmmc_start_dma_transfer(host, req); if (ret != 0) { dev_dbg(mmc_dev(host->mmc), "MMC start dma failure\n"); return ret; @@ -812,35 +1072,92 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) /* * Request function. for read/write operation */ -static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req) { - struct mmc_omap_host *host = mmc_priv(mmc); + struct omap_hsmmc_host *host = mmc_priv(mmc); + int err; + /* + * Prevent races with the interrupt handler because of unexpected + * interrupts, but not if we are already in interrupt context i.e. + * retries. + */ + if (!in_interrupt()) { + spin_lock_irqsave(&host->irq_lock, host->flags); + /* + * Protect the card from I/O if there is a possibility + * it can be removed. + */ + if (host->protect_card) { + if (host->reqs_blocked < 3) { + /* + * Ensure the controller is left in a consistent + * state by resetting the command and data state + * machines. + */ + omap_hsmmc_reset_controller_fsm(host, SRD); + omap_hsmmc_reset_controller_fsm(host, SRC); + host->reqs_blocked += 1; + } + req->cmd->error = -EBADF; + if (req->data) + req->data->error = -EBADF; + spin_unlock_irqrestore(&host->irq_lock, host->flags); + mmc_request_done(mmc, req); + return; + } else if (host->reqs_blocked) + host->reqs_blocked = 0; + } WARN_ON(host->mrq != NULL); host->mrq = req; - mmc_omap_prepare_data(host, req); - mmc_omap_start_command(host, req->cmd, req->data); -} + err = omap_hsmmc_prepare_data(host, req); + if (err) { + req->cmd->error = err; + if (req->data) + req->data->error = err; + host->mrq = NULL; + if (!in_interrupt()) + spin_unlock_irqrestore(&host->irq_lock, host->flags); + mmc_request_done(mmc, req); + return; + } + omap_hsmmc_start_command(host, req->cmd, req->data); +} /* Routine to configure clock values. Exposed API to core */ -static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { - struct mmc_omap_host *host = mmc_priv(mmc); + struct omap_hsmmc_host *host = mmc_priv(mmc); u16 dsor = 0; unsigned long regval; unsigned long timeout; u32 con; + int do_send_init_stream = 0; - switch (ios->power_mode) { - case MMC_POWER_OFF: - mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); - break; - case MMC_POWER_UP: - mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd); - break; + mmc_host_enable(host->mmc); + + if (ios->power_mode != host->power_mode) { + switch (ios->power_mode) { + case MMC_POWER_OFF: + mmc_slot(host).set_power(host->dev, host->slot_id, + 0, 0); + host->vdd = 0; + break; + case MMC_POWER_UP: + mmc_slot(host).set_power(host->dev, host->slot_id, + 1, ios->vdd); + host->vdd = ios->vdd; + break; + case MMC_POWER_ON: + do_send_init_stream = 1; + break; + } + host->power_mode = ios->power_mode; } + /* FIXME: set registers based only on changes to ios */ + con = OMAP_HSMMC_READ(host->base, CON); switch (mmc->ios.bus_width) { case MMC_BUS_WIDTH_8: @@ -870,8 +1187,8 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * MMC_POWER_UP upon recalculating the voltage. * vdd 1.8v. */ - if (omap_mmc_switch_opcond(host, ios->vdd) != 0) - dev_dbg(mmc_dev(host->mmc), + if (omap_hsmmc_switch_opcond(host, ios->vdd) != 0) + dev_dbg(mmc_dev(host->mmc), "Switch operation failed\n"); } } @@ -887,7 +1204,7 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) if (dsor > 250) dsor = 250; } - omap_mmc_stop_clock(host); + omap_hsmmc_stop_clock(host); regval = OMAP_HSMMC_READ(host->base, SYSCTL); regval = regval & ~(CLKD_MASK); regval = regval | (dsor << 6) | (DTO << 16); @@ -897,42 +1214,47 @@ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) /* Wait till the ICS bit is set */ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); - while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2 + while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS && time_before(jiffies, timeout)) msleep(1); OMAP_HSMMC_WRITE(host->base, SYSCTL, OMAP_HSMMC_READ(host->base, SYSCTL) | CEN); - if (ios->power_mode == MMC_POWER_ON) + if (do_send_init_stream) send_init_stream(host); + con = OMAP_HSMMC_READ(host->base, CON); if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) - OMAP_HSMMC_WRITE(host->base, CON, - OMAP_HSMMC_READ(host->base, CON) | OD); + OMAP_HSMMC_WRITE(host->base, CON, con | OD); + else + OMAP_HSMMC_WRITE(host->base, CON, con & ~OD); + + if (host->power_mode == MMC_POWER_OFF) + mmc_host_disable(host->mmc); + else + mmc_host_lazy_disable(host->mmc); } static int omap_hsmmc_get_cd(struct mmc_host *mmc) { - struct mmc_omap_host *host = mmc_priv(mmc); - struct omap_mmc_platform_data *pdata = host->pdata; + struct omap_hsmmc_host *host = mmc_priv(mmc); - if (!pdata->slots[0].card_detect) + if (!mmc_slot(host).card_detect) return -ENOSYS; - return pdata->slots[0].card_detect(pdata->slots[0].card_detect_irq); + return mmc_slot(host).card_detect(mmc_slot(host).card_detect_irq); } static int omap_hsmmc_get_ro(struct mmc_host *mmc) { - struct mmc_omap_host *host = mmc_priv(mmc); - struct omap_mmc_platform_data *pdata = host->pdata; + struct omap_hsmmc_host *host = mmc_priv(mmc); - if (!pdata->slots[0].get_ro) + if (!mmc_slot(host).get_ro) return -ENOSYS; - return pdata->slots[0].get_ro(host->dev, 0); + return mmc_slot(host).get_ro(host->dev, 0); } -static void omap_hsmmc_init(struct mmc_omap_host *host) +static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host) { u32 hctl, capa, value; @@ -959,19 +1281,340 @@ static void omap_hsmmc_init(struct mmc_omap_host *host) set_sd_bus_power(host); } -static struct mmc_host_ops mmc_omap_ops = { - .request = omap_mmc_request, - .set_ios = omap_mmc_set_ios, +/* + * Dynamic power saving handling, FSM: + * ENABLED -> DISABLED -> CARDSLEEP / REGSLEEP -> OFF + * ^___________| | | + * |______________________|______________________| + * + * ENABLED: mmc host is fully functional + * DISABLED: fclk is off + * CARDSLEEP: fclk is off, card is asleep, voltage regulator is asleep + * REGSLEEP: fclk is off, voltage regulator is asleep + * OFF: fclk is off, voltage regulator is off + * + * Transition handlers return the timeout for the next state transition + * or negative error. + */ + +enum {ENABLED = 0, DISABLED, CARDSLEEP, REGSLEEP, OFF}; + +/* Handler for [ENABLED -> DISABLED] transition */ +static int omap_hsmmc_enabled_to_disabled(struct omap_hsmmc_host *host) +{ + omap_hsmmc_context_save(host); + clk_disable(host->fclk); + host->dpm_state = DISABLED; + + dev_dbg(mmc_dev(host->mmc), "ENABLED -> DISABLED\n"); + + if (host->power_mode == MMC_POWER_OFF) + return 0; + + return msecs_to_jiffies(OMAP_MMC_SLEEP_TIMEOUT); +} + +/* Handler for [DISABLED -> REGSLEEP / CARDSLEEP] transition */ +static int omap_hsmmc_disabled_to_sleep(struct omap_hsmmc_host *host) +{ + int err, new_state; + + if (!mmc_try_claim_host(host->mmc)) + return 0; + + clk_enable(host->fclk); + omap_hsmmc_context_restore(host); + if (mmc_card_can_sleep(host->mmc)) { + err = mmc_card_sleep(host->mmc); + if (err < 0) { + clk_disable(host->fclk); + mmc_release_host(host->mmc); + return err; + } + new_state = CARDSLEEP; + } else { + new_state = REGSLEEP; + } + if (mmc_slot(host).set_sleep) + mmc_slot(host).set_sleep(host->dev, host->slot_id, 1, 0, + new_state == CARDSLEEP); + /* FIXME: turn off bus power and perhaps interrupts too */ + clk_disable(host->fclk); + host->dpm_state = new_state; + + mmc_release_host(host->mmc); + + dev_dbg(mmc_dev(host->mmc), "DISABLED -> %s\n", + host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP"); + + if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) || + mmc_slot(host).card_detect || + (mmc_slot(host).get_cover_state && + mmc_slot(host).get_cover_state(host->dev, host->slot_id))) + return msecs_to_jiffies(OMAP_MMC_OFF_TIMEOUT); + + return 0; +} + +/* Handler for [REGSLEEP / CARDSLEEP -> OFF] transition */ +static int omap_hsmmc_sleep_to_off(struct omap_hsmmc_host *host) +{ + if (!mmc_try_claim_host(host->mmc)) + return 0; + + if (!((host->mmc->caps & MMC_CAP_NONREMOVABLE) || + mmc_slot(host).card_detect || + (mmc_slot(host).get_cover_state && + mmc_slot(host).get_cover_state(host->dev, host->slot_id)))) { + mmc_release_host(host->mmc); + return 0; + } + + mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); + host->vdd = 0; + host->power_mode = MMC_POWER_OFF; + + dev_dbg(mmc_dev(host->mmc), "%s -> OFF\n", + host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP"); + + host->dpm_state = OFF; + + mmc_release_host(host->mmc); + + return 0; +} + +/* Handler for [DISABLED -> ENABLED] transition */ +static int omap_hsmmc_disabled_to_enabled(struct omap_hsmmc_host *host) +{ + int err; + + err = clk_enable(host->fclk); + if (err < 0) + return err; + + omap_hsmmc_context_restore(host); + host->dpm_state = ENABLED; + + dev_dbg(mmc_dev(host->mmc), "DISABLED -> ENABLED\n"); + + return 0; +} + +/* Handler for [SLEEP -> ENABLED] transition */ +static int omap_hsmmc_sleep_to_enabled(struct omap_hsmmc_host *host) +{ + if (!mmc_try_claim_host(host->mmc)) + return 0; + + clk_enable(host->fclk); + omap_hsmmc_context_restore(host); + if (mmc_slot(host).set_sleep) + mmc_slot(host).set_sleep(host->dev, host->slot_id, 0, + host->vdd, host->dpm_state == CARDSLEEP); + if (mmc_card_can_sleep(host->mmc)) + mmc_card_awake(host->mmc); + + dev_dbg(mmc_dev(host->mmc), "%s -> ENABLED\n", + host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP"); + + host->dpm_state = ENABLED; + + mmc_release_host(host->mmc); + + return 0; +} + +/* Handler for [OFF -> ENABLED] transition */ +static int omap_hsmmc_off_to_enabled(struct omap_hsmmc_host *host) +{ + clk_enable(host->fclk); + + omap_hsmmc_context_restore(host); + omap_hsmmc_conf_bus_power(host); + mmc_power_restore_host(host->mmc); + + host->dpm_state = ENABLED; + + dev_dbg(mmc_dev(host->mmc), "OFF -> ENABLED\n"); + + return 0; +} + +/* + * Bring MMC host to ENABLED from any other PM state. + */ +static int omap_hsmmc_enable(struct mmc_host *mmc) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + + switch (host->dpm_state) { + case DISABLED: + return omap_hsmmc_disabled_to_enabled(host); + case CARDSLEEP: + case REGSLEEP: + return omap_hsmmc_sleep_to_enabled(host); + case OFF: + return omap_hsmmc_off_to_enabled(host); + default: + dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n"); + return -EINVAL; + } +} + +/* + * Bring MMC host in PM state (one level deeper). + */ +static int omap_hsmmc_disable(struct mmc_host *mmc, int lazy) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + + switch (host->dpm_state) { + case ENABLED: { + int delay; + + delay = omap_hsmmc_enabled_to_disabled(host); + if (lazy || delay < 0) + return delay; + return 0; + } + case DISABLED: + return omap_hsmmc_disabled_to_sleep(host); + case CARDSLEEP: + case REGSLEEP: + return omap_hsmmc_sleep_to_off(host); + default: + dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n"); + return -EINVAL; + } +} + +static int omap_hsmmc_enable_fclk(struct mmc_host *mmc) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + int err; + + err = clk_enable(host->fclk); + if (err) + return err; + dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n"); + omap_hsmmc_context_restore(host); + return 0; +} + +static int omap_hsmmc_disable_fclk(struct mmc_host *mmc, int lazy) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + + omap_hsmmc_context_save(host); + clk_disable(host->fclk); + dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n"); + return 0; +} + +static const struct mmc_host_ops omap_hsmmc_ops = { + .enable = omap_hsmmc_enable_fclk, + .disable = omap_hsmmc_disable_fclk, + .request = omap_hsmmc_request, + .set_ios = omap_hsmmc_set_ios, .get_cd = omap_hsmmc_get_cd, .get_ro = omap_hsmmc_get_ro, /* NYET -- enable_sdio_irq */ }; -static int __init omap_mmc_probe(struct platform_device *pdev) +static const struct mmc_host_ops omap_hsmmc_ps_ops = { + .enable = omap_hsmmc_enable, + .disable = omap_hsmmc_disable, + .request = omap_hsmmc_request, + .set_ios = omap_hsmmc_set_ios, + .get_cd = omap_hsmmc_get_cd, + .get_ro = omap_hsmmc_get_ro, + /* NYET -- enable_sdio_irq */ +}; + +#ifdef CONFIG_DEBUG_FS + +static int omap_hsmmc_regs_show(struct seq_file *s, void *data) +{ + struct mmc_host *mmc = s->private; + struct omap_hsmmc_host *host = mmc_priv(mmc); + int context_loss = 0; + + if (host->pdata->get_context_loss_count) + context_loss = host->pdata->get_context_loss_count(host->dev); + + seq_printf(s, "mmc%d:\n" + " enabled:\t%d\n" + " dpm_state:\t%d\n" + " nesting_cnt:\t%d\n" + " ctx_loss:\t%d:%d\n" + "\nregs:\n", + mmc->index, mmc->enabled ? 1 : 0, + host->dpm_state, mmc->nesting_cnt, + host->context_loss, context_loss); + + if (host->suspended || host->dpm_state == OFF) { + seq_printf(s, "host suspended, can't read registers\n"); + return 0; + } + + if (clk_enable(host->fclk) != 0) { + seq_printf(s, "can't read the regs\n"); + return 0; + } + + seq_printf(s, "SYSCONFIG:\t0x%08x\n", + OMAP_HSMMC_READ(host->base, SYSCONFIG)); + seq_printf(s, "CON:\t\t0x%08x\n", + OMAP_HSMMC_READ(host->base, CON)); + seq_printf(s, "HCTL:\t\t0x%08x\n", + OMAP_HSMMC_READ(host->base, HCTL)); + seq_printf(s, "SYSCTL:\t\t0x%08x\n", + OMAP_HSMMC_READ(host->base, SYSCTL)); + seq_printf(s, "IE:\t\t0x%08x\n", + OMAP_HSMMC_READ(host->base, IE)); + seq_printf(s, "ISE:\t\t0x%08x\n", + OMAP_HSMMC_READ(host->base, ISE)); + seq_printf(s, "CAPA:\t\t0x%08x\n", + OMAP_HSMMC_READ(host->base, CAPA)); + + clk_disable(host->fclk); + + return 0; +} + +static int omap_hsmmc_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, omap_hsmmc_regs_show, inode->i_private); +} + +static const struct file_operations mmc_regs_fops = { + .open = omap_hsmmc_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void omap_hsmmc_debugfs(struct mmc_host *mmc) +{ + if (mmc->debugfs_root) + debugfs_create_file("regs", S_IRUSR, mmc->debugfs_root, + mmc, &mmc_regs_fops); +} + +#else + +static void omap_hsmmc_debugfs(struct mmc_host *mmc) +{ +} + +#endif + +static int __init omap_hsmmc_probe(struct platform_device *pdev) { struct omap_mmc_platform_data *pdata = pdev->dev.platform_data; struct mmc_host *mmc; - struct mmc_omap_host *host = NULL; + struct omap_hsmmc_host *host = NULL; struct resource *res; int ret = 0, irq; @@ -995,7 +1638,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev) if (res == NULL) return -EBUSY; - mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev); + mmc = mmc_alloc_host(sizeof(struct omap_hsmmc_host), &pdev->dev); if (!mmc) { ret = -ENOMEM; goto err; @@ -1013,15 +1656,21 @@ static int __init omap_mmc_probe(struct platform_device *pdev) host->slot_id = 0; host->mapbase = res->start; host->base = ioremap(host->mapbase, SZ_4K); + host->power_mode = -1; platform_set_drvdata(pdev, host); - INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect); + INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect); + + if (mmc_slot(host).power_saving) + mmc->ops = &omap_hsmmc_ps_ops; + else + mmc->ops = &omap_hsmmc_ops; - mmc->ops = &mmc_omap_ops; mmc->f_min = 400000; mmc->f_max = 52000000; sema_init(&host->sem, 1); + spin_lock_init(&host->irq_lock); host->iclk = clk_get(&pdev->dev, "ick"); if (IS_ERR(host->iclk)) { @@ -1037,31 +1686,42 @@ static int __init omap_mmc_probe(struct platform_device *pdev) goto err1; } - if (clk_enable(host->fclk) != 0) { + omap_hsmmc_context_save(host); + + mmc->caps |= MMC_CAP_DISABLE; + mmc_set_disable_delay(mmc, OMAP_MMC_DISABLED_TIMEOUT); + /* we start off in DISABLED state */ + host->dpm_state = DISABLED; + + if (mmc_host_enable(host->mmc) != 0) { clk_put(host->iclk); clk_put(host->fclk); goto err1; } if (clk_enable(host->iclk) != 0) { - clk_disable(host->fclk); + mmc_host_disable(host->mmc); clk_put(host->iclk); clk_put(host->fclk); goto err1; } - host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck"); - /* - * MMC can still work without debounce clock. - */ - if (IS_ERR(host->dbclk)) - dev_warn(mmc_dev(host->mmc), "Failed to get debounce clock\n"); - else - if (clk_enable(host->dbclk) != 0) - dev_dbg(mmc_dev(host->mmc), "Enabling debounce" - " clk failed\n"); + if (cpu_is_omap2430()) { + host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck"); + /* + * MMC can still work without debounce clock. + */ + if (IS_ERR(host->dbclk)) + dev_warn(mmc_dev(host->mmc), + "Failed to get debounce clock\n"); else - host->dbclk_enabled = 1; + host->got_dbclk = 1; + + if (host->got_dbclk) + if (clk_enable(host->dbclk) != 0) + dev_dbg(mmc_dev(host->mmc), "Enabling debounce" + " clk failed\n"); + } /* Since we do only SG emulation, we can have as many segs * as we want. */ @@ -1073,14 +1733,18 @@ static int __init omap_mmc_probe(struct platform_device *pdev) mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; mmc->max_seg_size = mmc->max_req_size; - mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | + MMC_CAP_WAIT_WHILE_BUSY; - if (pdata->slots[host->slot_id].wires >= 8) + if (mmc_slot(host).wires >= 8) mmc->caps |= MMC_CAP_8_BIT_DATA; - else if (pdata->slots[host->slot_id].wires >= 4) + else if (mmc_slot(host).wires >= 4) mmc->caps |= MMC_CAP_4_BIT_DATA; - omap_hsmmc_init(host); + if (mmc_slot(host).nonremovable) + mmc->caps |= MMC_CAP_NONREMOVABLE; + + omap_hsmmc_conf_bus_power(host); /* Select DMA lines */ switch (host->id) { @@ -1096,13 +1760,21 @@ static int __init omap_mmc_probe(struct platform_device *pdev) host->dma_line_tx = OMAP34XX_DMA_MMC3_TX; host->dma_line_rx = OMAP34XX_DMA_MMC3_RX; break; + case OMAP_MMC4_DEVID: + host->dma_line_tx = OMAP44XX_DMA_MMC4_TX; + host->dma_line_rx = OMAP44XX_DMA_MMC4_RX; + break; + case OMAP_MMC5_DEVID: + host->dma_line_tx = OMAP44XX_DMA_MMC5_TX; + host->dma_line_rx = OMAP44XX_DMA_MMC5_RX; + break; default: dev_err(mmc_dev(host->mmc), "Invalid MMC id\n"); goto err_irq; } /* Request IRQ for MMC operations */ - ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED, + ret = request_irq(host->irq, omap_hsmmc_irq, IRQF_DISABLED, mmc_hostname(mmc), host); if (ret) { dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n"); @@ -1112,7 +1784,8 @@ static int __init omap_mmc_probe(struct platform_device *pdev) /* initialize power supplies, gpios, etc */ if (pdata->init != NULL) { if (pdata->init(&pdev->dev) != 0) { - dev_dbg(mmc_dev(host->mmc), "late init error\n"); + dev_dbg(mmc_dev(host->mmc), + "Unable to configure MMC IRQs\n"); goto err_irq_cd_init; } } @@ -1121,7 +1794,7 @@ static int __init omap_mmc_probe(struct platform_device *pdev) /* Request IRQ for card detect */ if ((mmc_slot(host).card_detect_irq)) { ret = request_irq(mmc_slot(host).card_detect_irq, - omap_mmc_cd_handler, + omap_hsmmc_cd_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_DISABLED, mmc_hostname(mmc), host); @@ -1135,21 +1808,26 @@ static int __init omap_mmc_probe(struct platform_device *pdev) OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK); OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK); + mmc_host_lazy_disable(host->mmc); + + omap_hsmmc_protect_card(host); + mmc_add_host(mmc); - if (host->pdata->slots[host->slot_id].name != NULL) { + if (mmc_slot(host).name != NULL) { ret = device_create_file(&mmc->class_dev, &dev_attr_slot_name); if (ret < 0) goto err_slot_name; } - if (mmc_slot(host).card_detect_irq && - host->pdata->slots[host->slot_id].get_cover_state) { + if (mmc_slot(host).card_detect_irq && mmc_slot(host).get_cover_state) { ret = device_create_file(&mmc->class_dev, &dev_attr_cover_switch); if (ret < 0) goto err_cover_switch; } + omap_hsmmc_debugfs(mmc); + return 0; err_cover_switch: @@ -1161,11 +1839,11 @@ err_irq_cd: err_irq_cd_init: free_irq(host->irq, host); err_irq: - clk_disable(host->fclk); + mmc_host_disable(host->mmc); clk_disable(host->iclk); clk_put(host->fclk); clk_put(host->iclk); - if (host->dbclk_enabled) { + if (host->got_dbclk) { clk_disable(host->dbclk); clk_put(host->dbclk); } @@ -1180,12 +1858,13 @@ err: return ret; } -static int omap_mmc_remove(struct platform_device *pdev) +static int omap_hsmmc_remove(struct platform_device *pdev) { - struct mmc_omap_host *host = platform_get_drvdata(pdev); + struct omap_hsmmc_host *host = platform_get_drvdata(pdev); struct resource *res; if (host) { + mmc_host_enable(host->mmc); mmc_remove_host(host->mmc); if (host->pdata->cleanup) host->pdata->cleanup(&pdev->dev); @@ -1194,11 +1873,11 @@ static int omap_mmc_remove(struct platform_device *pdev) free_irq(mmc_slot(host).card_detect_irq, host); flush_scheduled_work(); - clk_disable(host->fclk); + mmc_host_disable(host->mmc); clk_disable(host->iclk); clk_put(host->fclk); clk_put(host->iclk); - if (host->dbclk_enabled) { + if (host->got_dbclk) { clk_disable(host->dbclk); clk_put(host->dbclk); } @@ -1216,36 +1895,51 @@ static int omap_mmc_remove(struct platform_device *pdev) } #ifdef CONFIG_PM -static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state) +static int omap_hsmmc_suspend(struct platform_device *pdev, pm_message_t state) { int ret = 0; - struct mmc_omap_host *host = platform_get_drvdata(pdev); + struct omap_hsmmc_host *host = platform_get_drvdata(pdev); if (host && host->suspended) return 0; if (host) { + host->suspended = 1; + if (host->pdata->suspend) { + ret = host->pdata->suspend(&pdev->dev, + host->slot_id); + if (ret) { + dev_dbg(mmc_dev(host->mmc), + "Unable to handle MMC board" + " level suspend\n"); + host->suspended = 0; + return ret; + } + } + cancel_work_sync(&host->mmc_carddetect_work); + mmc_host_enable(host->mmc); ret = mmc_suspend_host(host->mmc, state); if (ret == 0) { - host->suspended = 1; - OMAP_HSMMC_WRITE(host->base, ISE, 0); OMAP_HSMMC_WRITE(host->base, IE, 0); - if (host->pdata->suspend) { - ret = host->pdata->suspend(&pdev->dev, - host->slot_id); - if (ret) - dev_dbg(mmc_dev(host->mmc), - "Unable to handle MMC board" - " level suspend\n"); - } OMAP_HSMMC_WRITE(host->base, HCTL, - OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP); - clk_disable(host->fclk); + OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP); + mmc_host_disable(host->mmc); clk_disable(host->iclk); - clk_disable(host->dbclk); + if (host->got_dbclk) + clk_disable(host->dbclk); + } else { + host->suspended = 0; + if (host->pdata->resume) { + ret = host->pdata->resume(&pdev->dev, + host->slot_id); + if (ret) + dev_dbg(mmc_dev(host->mmc), + "Unmask interrupt failed\n"); + } + mmc_host_disable(host->mmc); } } @@ -1253,32 +1947,28 @@ static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state) } /* Routine to resume the MMC device */ -static int omap_mmc_resume(struct platform_device *pdev) +static int omap_hsmmc_resume(struct platform_device *pdev) { int ret = 0; - struct mmc_omap_host *host = platform_get_drvdata(pdev); + struct omap_hsmmc_host *host = platform_get_drvdata(pdev); if (host && !host->suspended) return 0; if (host) { - - ret = clk_enable(host->fclk); + ret = clk_enable(host->iclk); if (ret) goto clk_en_err; - ret = clk_enable(host->iclk); - if (ret) { - clk_disable(host->fclk); - clk_put(host->fclk); + if (mmc_host_enable(host->mmc) != 0) { + clk_disable(host->iclk); goto clk_en_err; } - if (clk_enable(host->dbclk) != 0) - dev_dbg(mmc_dev(host->mmc), - "Enabling debounce clk failed\n"); + if (host->got_dbclk) + clk_enable(host->dbclk); - omap_hsmmc_init(host); + omap_hsmmc_conf_bus_power(host); if (host->pdata->resume) { ret = host->pdata->resume(&pdev->dev, host->slot_id); @@ -1287,10 +1977,14 @@ static int omap_mmc_resume(struct platform_device *pdev) "Unmask interrupt failed\n"); } + omap_hsmmc_protect_card(host); + /* Notify the core to resume the host */ ret = mmc_resume_host(host->mmc); if (ret == 0) host->suspended = 0; + + mmc_host_lazy_disable(host->mmc); } return ret; @@ -1302,35 +1996,34 @@ clk_en_err: } #else -#define omap_mmc_suspend NULL -#define omap_mmc_resume NULL +#define omap_hsmmc_suspend NULL +#define omap_hsmmc_resume NULL #endif -static struct platform_driver omap_mmc_driver = { - .probe = omap_mmc_probe, - .remove = omap_mmc_remove, - .suspend = omap_mmc_suspend, - .resume = omap_mmc_resume, +static struct platform_driver omap_hsmmc_driver = { + .remove = omap_hsmmc_remove, + .suspend = omap_hsmmc_suspend, + .resume = omap_hsmmc_resume, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, }, }; -static int __init omap_mmc_init(void) +static int __init omap_hsmmc_init(void) { /* Register the MMC driver */ - return platform_driver_register(&omap_mmc_driver); + return platform_driver_register(&omap_hsmmc_driver); } -static void __exit omap_mmc_cleanup(void) +static void __exit omap_hsmmc_cleanup(void) { /* Unregister MMC driver */ - platform_driver_unregister(&omap_mmc_driver); + platform_driver_unregister(&omap_hsmmc_driver); } -module_init(omap_mmc_init); -module_exit(omap_mmc_cleanup); +module_init(omap_hsmmc_init); +module_exit(omap_hsmmc_cleanup); MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver"); MODULE_LICENSE("GPL"); |