#define OMAP_HSMMC_ISE 0x0138
#define OMAP_HSMMC_CAPA 0x0140
-#define VS18 (1<<26)
-#define VS30 (1<<25)
-#define SDVS18 (0x5<<9)
-#define SDVS30 (0x6<<9)
+#define VS18 (1 << 26)
+#define VS30 (1 << 25)
+#define SDVS18 (0x5 << 9)
+#define SDVS30 (0x6 << 9)
#define SDVSCLR 0xFFFFF1FF
#define SDVSDET 0x00000400
#define AUTOIDLE 0x1
-#define SDBP (1<<8)
+#define SDBP (1 << 8)
#define DTO 0xe
#define ICE 0x1
#define ICS 0x2
-#define CEN (1<<2)
+#define CEN (1 << 2)
#define CLKD_MASK 0x0000FFC0
+#define CLKD_SHIFT 6
+#define DTO_MASK 0x000F0000
+#define DTO_SHIFT 16
#define INT_EN_MASK 0x307F0033
-#define INIT_STREAM (1<<1)
-#define DP_SELECT (1<<21)
-#define DDIR (1<<4)
+#define INIT_STREAM (1 << 1)
+#define DP_SELECT (1 << 21)
+#define DDIR (1 << 4)
#define DMA_EN 0x1
-#define MSBS 1<<5
-#define BCE 1<<1
-#define FOUR_BIT 1 << 1
+#define MSBS (1 << 5)
+#define BCE (1 << 1)
+#define FOUR_BIT (1 << 1)
#define CC 0x1
#define TC 0x02
#define OD 0x1
#define SRC (1 << 25)
#define SRD (1 << 26)
-#define OMAP_MMC1_DEVID 1
-#define OMAP_MMC2_DEVID 2
+/*
+ * FIXME: Most likely all the data using these _DEVID defines should come
+ * from the platform_data, or implemented in controller and slot specific
+ * functions.
+ */
+#define OMAP_MMC1_DEVID 0
+#define OMAP_MMC2_DEVID 1
+
#define OMAP_MMC_DATADIR_NONE 0
#define OMAP_MMC_DATADIR_READ 1
#define OMAP_MMC_DATADIR_WRITE 2
#define MMC_TIMEOUT_MS 20
#define OMAP_MMC_MASTER_CLOCK 96000000
-#define DRIVER_NAME "mmci-omap"
+#define DRIVER_NAME "mmci-omap-hs"
+
/*
- * slot_id is device id - 1, device id is a static value
- * of 1 to represent device 1 etc..
+ * 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
+ * omap_hsmmc.c device.
*/
#define mmc_slot(host) (host->pdata->slots[host->slot_id])
#define OMAP_HSMMC_WRITE(base, reg, val) \
__raw_writel((val), (base) + OMAP_HSMMC_##reg)
+enum {OFF = 0, ON};
+#define IDLE_TIMEOUT (jiffies_to_msecs(10))
+
struct mmc_omap_host {
struct device *dev;
struct mmc_host *mmc;
int initstr;
int slot_id;
int dbclk_enabled;
+
+ struct timer_list idle_timer;
+ spinlock_t clk_lock; /* for changing enabled state */
+ unsigned int fclk_enabled:1;
+
struct omap_mmc_platform_data *pdata;
};
+static int mmc_omap_fclk_state(struct mmc_omap_host *host, unsigned int state)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&host->clk_lock, flags);
+ del_timer(&host->idle_timer);
+ if (host->fclk_enabled != state) {
+ if (state == ON) {
+ ret = clk_enable(host->fclk);
+ if (ret != 0)
+ goto err_out;
+
+ dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
+ } else {
+ clk_disable(host->fclk);
+ dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
+ }
+ host->fclk_enabled = state;
+ }
+
+err_out:
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ return ret;
+}
+
+static void mmc_omap_idle_timer(unsigned long data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+ mmc_omap_fclk_state(host, OFF);
+}
+
+static void mmc_omap_fclk_lazy_disable(struct mmc_omap_host *host)
+{
+ mod_timer(&host->idle_timer, jiffies + IDLE_TIMEOUT);
+}
+
/*
* Stop clock to the card
*/
enable_irq(host->irq);
}
+static inline
+int mmc_omap_cover_is_closed(struct mmc_omap_host *host)
+{
+ if (host->pdata->slots[host->slot_id].get_cover_state)
+ return host->pdata->slots[host->slot_id].get_cover_state(host->dev, host->slot_id);
+ return 1;
+}
+
+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_host *host = mmc_priv(mmc);
+
+ return sprintf(buf, "%s\n", mmc_omap_cover_is_closed(host) ? "closed" :
+ "open");
+}
+
+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_host *host = mmc_priv(mmc);
+ struct omap_mmc_slot_data slot = host->pdata->slots[host->slot_id];
+
+ return sprintf(buf, "slot:%s\n", slot.name);
+}
+
+static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL);
+
/*
* Configure the response type and send the cmd.
*/
/*
* Unlike OMAP1 controller, the cmdtype does not seem to be based on
- * ac, bc, adtc, bcr. Only CMD12 needs a val of 0x3, rest 0x0.
+ * ac, bc, adtc, bcr. Only commands ending an open ended transfer need
+ * a val of 0x3, rest 0x0.
*/
- if (cmd->opcode == 12)
+ if (cmd == host->mrq->stop)
cmdtype = 0x3;
cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
if (!data->stop) {
host->mrq = NULL;
+ mmc_omap_fclk_lazy_disable(host);
mmc_request_done(host->mmc, data->mrq);
return;
}
}
if (host->data == NULL || cmd->error) {
host->mrq = NULL;
+ mmc_omap_fclk_lazy_disable(host);
mmc_request_done(host->mmc, cmd->mrq);
}
}
mmc_dma_cleanup(host);
else
host->data->error = -EILSEQ;
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base,
+ SYSCTL) | SRD);
+ while (OMAP_HSMMC_READ(host->base,
+ SYSCTL) & SRD) ;
end_trans = 1;
}
}
int ret;
/* Disable the clocks */
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_disable(host->dbclk);
if (ret != 0)
goto err;
- clk_enable(host->fclk);
+ mmc_omap_fclk_state(host, ON);
clk_enable(host->iclk);
clk_enable(host->dbclk);
/*
* 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, mmc_omap_detect fn sets the VDD back to 3V.
+ * slot, omap_mmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF.
*
* Only MMC1 supports 3.0V. MMC2 will not function if SDVS30 is
* set in HCTL.
*/
- if (host->id == OMAP_MMC1_DEVID && (((1 << vdd) == MMC_VDD_32_33) ||
- ((1 << vdd) == MMC_VDD_33_34)))
- reg_val |= SDVS30;
- if ((1 << vdd) == MMC_VDD_165_195)
+ if (host->id == OMAP_MMC1_DEVID) {
+ if (((1 << vdd) == MMC_VDD_32_33) ||
+ ((1 << vdd) == MMC_VDD_33_34))
+ reg_val |= SDVS30;
+ else if ((1 << vdd) == MMC_VDD_165_195)
+ reg_val |= SDVS18;
+ } else
reg_val |= SDVS18;
OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
mmc_carddetect_work);
+ sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
+ mmc_omap_fclk_state(host, ON);
if (host->carddetect) {
- if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
- /*
- * Set the VDD back to 3V when the card is removed
- * before the set_ios fn turns off the power.
- */
- vdd = fls(host->mmc->ocr_avail) - 1;
- if (omap_mmc_switch_opcond(host, vdd) != 0)
- host->mmc->ios.vdd = vdd;
- }
mmc_detect_change(host->mmc, (HZ * 200) / 1000);
} else {
OMAP_HSMMC_WRITE(host->base, SYSCTL,
while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ;
mmc_detect_change(host->mmc, (HZ * 50) / 1000);
}
+ mmc_omap_fclk_lazy_disable(host);
}
/*
return 0;
}
+static void set_data_timeout(struct mmc_omap_host *host,
+ struct mmc_request *req)
+{
+ unsigned int timeout, cycle_ns;
+ uint32_t reg, clkd, dto = 0;
+
+ reg = OMAP_HSMMC_READ(host->base, SYSCTL);
+ clkd = (reg & CLKD_MASK) >> CLKD_SHIFT;
+ if (clkd == 0)
+ clkd = 1;
+
+ cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd);
+ timeout = req->data->timeout_ns / cycle_ns;
+ timeout += req->data->timeout_clks;
+ if (timeout) {
+ while ((timeout & 0x80000000) == 0) {
+ dto += 1;
+ timeout <<= 1;
+ }
+ dto = 31 - dto;
+ timeout <<= 1;
+ if (timeout && dto)
+ dto += 1;
+ if (dto >= 13)
+ dto -= 13;
+ else
+ dto = 0;
+ if (dto > 14)
+ dto = 14;
+ }
+
+ reg &= ~DTO_MASK;
+ reg |= dto << DTO_SHIFT;
+ OMAP_HSMMC_WRITE(host->base, SYSCTL, reg);
+}
+
/*
* Configure block length for MMC/SD cards and initiate the transfer.
*/
OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
| (req->data->blocks << 16));
+ set_data_timeout(host, req);
host->datadir = (req->data->flags & MMC_DATA_WRITE) ?
OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
WARN_ON(host->mrq != NULL);
host->mrq = req;
+ mmc_omap_fclk_state(host, ON);
mmc_omap_prepare_data(host, req);
mmc_omap_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)
{
unsigned long regval;
unsigned long timeout;
+ mmc_omap_fclk_state(host, ON);
+
switch (ios->power_mode) {
case MMC_POWER_OFF:
mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+
+ /*
+ * Reset bus voltage to 3V if it got set to 1.8V earlier.
+ * REVISIT: If we are able to detect cards after unplugging
+ * a 1.8V card, this code should not be needed.
+ */
+ regval = OMAP_HSMMC_READ(host->base, HCTL);
+ if (regval & SDVSDET) {
+ regval &= ~SDVSDET;
+ OMAP_HSMMC_WRITE(host->base, HCTL, regval);
+ }
break;
case MMC_POWER_UP:
mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd);
if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
OMAP_HSMMC_WRITE(host->base, CON,
OMAP_HSMMC_READ(host->base, CON) | OD);
+
+ mmc_omap_fclk_lazy_disable(host);
}
-/* NOTE: Read only switch not supported yet */
+
+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;
+
+ if (!pdata->slots[0].card_detect)
+ return -ENOSYS;
+ return pdata->slots[0].card_detect(pdata->slots[0].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;
+
+ if (!pdata->slots[0].get_ro)
+ return -ENOSYS;
+ return pdata->slots[0].get_ro(host->dev, 0);
+}
+
static struct mmc_host_ops mmc_omap_ops = {
.request = omap_mmc_request,
.set_ios = omap_mmc_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)
struct mmc_host *mmc;
struct mmc_omap_host *host = NULL;
struct resource *res;
- int ret = 0, irq;
+ int ret = 0, irq, reg;
u32 hctl, capa;
if (pdata == NULL) {
host = mmc_priv(mmc);
host->mmc = mmc;
host->pdata = pdata;
+ host->dev = &pdev->dev;
host->use_dma = 1;
+ host->dev->dma_mask = &pdata->dma_mask;
host->dma_ch = -1;
host->irq = irq;
host->id = pdev->id;
host->slot_id = 0;
host->mapbase = res->start;
host->base = ioremap(host->mapbase, SZ_4K);
+
+ platform_set_drvdata(pdev, host);
+ INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
+
mmc->ops = &mmc_omap_ops;
mmc->f_min = 400000;
mmc->f_max = 52000000;
goto err1;
}
- if (clk_enable(host->fclk) != 0) {
+ spin_lock_init(&host->clk_lock);
+ setup_timer(&host->idle_timer, mmc_omap_idle_timer,
+ (unsigned long) host);
+
+ if (mmc_omap_fclk_state(host, ON) != 0) {
clk_put(host->iclk);
clk_put(host->fclk);
goto err1;
}
-
if (clk_enable(host->iclk) != 0) {
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_put(host->iclk);
clk_put(host->fclk);
goto err1;
* MMC can still work without debounce clock.
*/
if (IS_ERR(host->dbclk))
- dev_dbg(mmc_dev(host->mmc), "Failed to get debounce clock\n");
+ 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"
mmc->ocr_avail = mmc_slot(host).ocr_mask;
mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
- if (pdata->conf.wire4)
+ if (pdata->slots[host->slot_id].wires >= 4)
mmc->caps |= MMC_CAP_4_BIT_DATA;
/* Only MMC1 supports 3.0V */
OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
/* Request IRQ for MMC operations */
- ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED, pdev->name,
- host);
+ ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED,
+ mmc_hostname(mmc), host);
if (ret) {
dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n");
- goto irq_err;
+ goto err_irq;
+ }
+
+ if (pdata->init != NULL) {
+ if (pdata->init(&pdev->dev) != 0) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Unable to configure MMC IRQs\n");
+ goto err_irq_cd_init;
+ }
}
/* Request IRQ for card detect */
if ((mmc_slot(host).card_detect_irq) && (mmc_slot(host).card_detect)) {
ret = request_irq(mmc_slot(host).card_detect_irq,
- omap_mmc_cd_handler, IRQF_DISABLED, "MMC CD",
- host);
+ omap_mmc_cd_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+ | IRQF_DISABLED,
+ mmc_hostname(mmc), host);
if (ret) {
dev_dbg(mmc_dev(host->mmc),
- "Unable to grab MMC CD IRQ");
- free_irq(host->irq, host);
- goto irq_err;
- }
- }
-
- INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
- if (pdata->init != NULL) {
- if (pdata->init(&pdev->dev) != 0) {
- free_irq(mmc_slot(host).card_detect_irq, host);
- free_irq(host->irq, host);
- goto irq_err;
+ "Unable to grab MMC CD IRQ\n");
+ goto err_irq_cd;
}
}
OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
- platform_set_drvdata(pdev, host);
mmc_add_host(mmc);
+ if (host->pdata->slots[host->slot_id].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 && mmc_slot(host).card_detect &&
+ host->pdata->slots[host->slot_id].get_cover_state) {
+ ret = device_create_file(&mmc->class_dev, &dev_attr_cover_switch);
+ if (ret < 0)
+ goto err_cover_switch;
+ }
+ mmc_omap_fclk_lazy_disable(host);
+
return 0;
-irq_err:
- dev_dbg(mmc_dev(host->mmc), "Unable to configure MMC IRQs\n");
- clk_disable(host->fclk);
+err_cover_switch:
+ device_remove_file(&mmc->class_dev, &dev_attr_cover_switch);
+err_slot_name:
+ mmc_remove_host(mmc);
+err_irq_cd:
+ free_irq(mmc_slot(host).card_detect_irq, host);
+err_irq_cd_init:
+ free_irq(host->irq, host);
+err_irq:
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_put(host->fclk);
clk_put(host->iclk);
{
struct mmc_omap_host *host = platform_get_drvdata(pdev);
struct resource *res;
- u16 vdd = 0;
- if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
- /*
- * Set the vdd back to 3V,
- * applicable for dual volt support.
- */
- vdd = fls(host->mmc->ocr_avail) - 1;
- if (omap_mmc_switch_opcond(host, vdd) != 0)
- host->mmc->ios.vdd = vdd;
- }
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res)
- release_mem_region(res->start, res->end - res->start + 1);
-
- platform_set_drvdata(pdev, NULL);
if (host) {
mmc_remove_host(host->mmc);
if (host->pdata->cleanup)
free_irq(mmc_slot(host).card_detect_irq, host);
flush_scheduled_work();
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_put(host->fclk);
clk_put(host->iclk);
iounmap(host->base);
}
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, res->end - res->start + 1);
+ platform_set_drvdata(pdev, NULL);
+
return 0;
}
if (ret == 0) {
host->suspended = 1;
+ mmc_omap_fclk_state(host, ON);
OMAP_HSMMC_WRITE(host->base, ISE, 0);
OMAP_HSMMC_WRITE(host->base, IE, 0);
" level suspend\n");
}
- if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
- OMAP_HSMMC_WRITE(host->base, HCTL,
- OMAP_HSMMC_READ(host->base, HCTL)
- & SDVSCLR);
- OMAP_HSMMC_WRITE(host->base, HCTL,
- OMAP_HSMMC_READ(host->base, HCTL)
- | SDVS30);
- OMAP_HSMMC_WRITE(host->base, HCTL,
- OMAP_HSMMC_READ(host->base, HCTL)
- | SDBP);
- }
-
- clk_disable(host->fclk);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
+ mmc_omap_fclk_state(host, OFF);
clk_disable(host->iclk);
clk_disable(host->dbclk);
}
return 0;
if (host) {
-
- ret = clk_enable(host->fclk);
- if (ret)
+ int i;
+ if (mmc_omap_fclk_state(host, ON) != 0)
goto clk_en_err;
ret = clk_enable(host->iclk);
if (ret) {
- clk_disable(host->fclk);
+ mmc_omap_fclk_state(host, OFF);
clk_put(host->fclk);
goto clk_en_err;
}
dev_dbg(mmc_dev(host->mmc),
"Enabling debounce clk failed\n");
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+ for (i = 0; i < 100; i++)
+ if (OMAP_HSMMC_READ(host->base, HCTL) & SDBP)
+ break;
+
if (host->pdata->resume) {
ret = host->pdata->resume(&pdev->dev, host->slot_id);
if (ret)
ret = mmc_resume_host(host->mmc);
if (ret == 0)
host->suspended = 0;
+
+ mmc_omap_fclk_lazy_disable(host);
}
return ret;