/* * Work Item to notify the core about card insertion/removal */ static void mmc_omap_detect(struct work_struct *work) { u16 vdd = 0; struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, mmc_carddetect_work); mmc_clk_try_enable(host); sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch"); 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; } /* 1,5 sec is a lot, but 1,5 sec is safe... */ mmc_detect_change(host->mmc, (3 * HZ)/2); } else { OMAP_HSMMC_WRITE(host->base, SYSCTL, OMAP_HSMMC_READ(host->base, SYSCTL) | SRD); while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ; mmc_detect_change(host->mmc, (HZ * 50) / 1000); } }
static int omap_mmc_remove(struct platform_device *pdev) { struct mmc_omap_host *host = platform_get_drvdata(pdev); struct resource *res; u16 vdd = 0; /* * TODO: * The timer could kick in and turn off the clocks. * if mmc_remove_host touches the mmc module regs, this can * crash. So need to verify if mmc_remove_host indeed touches * the module regs. */ mmc_clk_try_enable(host); 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) host->pdata->cleanup(&pdev->dev); free_irq(host->irq, host); if (mmc_slot(host).card_detect_irq) free_irq(mmc_slot(host).card_detect_irq, host); flush_scheduled_work(); mmc_clk_try_disable(host); clk_put(host->fclk); clk_put(host->iclk); if (host->dbclk_enabled) { clk_disable(host->dbclk); clk_put(host->dbclk); } mmc_free_host(host->mmc); iounmap(host->base); } return 0; }
/* * Work Item to notify the core about card insertion/removal */ static void mmc_omap_detect(struct work_struct *work) { u16 vdd = 0; struct mmc_omap_host *host = container_of(work, struct mmc_omap_host, mmc_carddetect_work); 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 mmc_detect_change(host->mmc, (HZ * 50) / 1000); }
/* Routine to configure clock values. Exposed API to core */ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mmc_omap_host *host = mmc_priv(mmc); u16 dsor = 0; unsigned long regval; unsigned long timeout; 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; } switch (mmc->ios.bus_width) { case MMC_BUS_WIDTH_4: 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, HCTL, OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT); break; } if (host->id == OMAP_MMC1_DEVID) { /* Only MMC1 can operate at 3V/1.8V */ if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) && (ios->vdd == DUAL_VOLT_OCR_BIT)) { /* * The mmc_select_voltage fn of the core does * not seem to set the power_mode to * 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), "Switch operation failed\n"); } } 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_mmc_stop_clock(host); regval = OMAP_HSMMC_READ(host->base, SYSCTL); regval = regval & ~(CLKD_MASK); regval = regval | (dsor << 6) | (DTO << 16); OMAP_HSMMC_WRITE(host->base, SYSCTL, regval); OMAP_HSMMC_WRITE(host->base, SYSCTL, OMAP_HSMMC_READ(host->base, SYSCTL) | ICE); /* Wait till the ICS bit is set */ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2 && 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) send_init_stream(host); if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) | OD); }
/* Routine to configure clock values. Exposed API to core */ static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mmc_omap_host *host = mmc_priv(mmc); u16 dsor = 0; unsigned long regval; unsigned long timeout; u32 con; del_timer_sync(&host->inact_timer); omap_hsmmc_enable_clks(host); switch (ios->power_mode) { case MMC_POWER_OFF: mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); /* * Reset interface voltage to 3V if it's 1.8V now; * only relevant on MMC-1, the others always use 1.8V. * * REVISIT: If we are able to detect cards after unplugging * a 1.8V card, this code should not be needed. */ if (host->id != OMAP_MMC1_DEVID) break; if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) { int vdd = fls(host->mmc->ocr_avail) - 1; if (omap_mmc_switch_opcond(host, vdd) != 0) host->mmc->ios.vdd = vdd; } break; case MMC_POWER_UP: mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd); break; } switch (mmc->ios.bus_width) { case MMC_BUS_WIDTH_8: OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, CON) | DW8); break; case MMC_BUS_WIDTH_4: OMAP_HSMMC_WRITE(host->base, CON, OMAP_HSMMC_READ(host->base, 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, OMAP_HSMMC_READ(host->base, CON) & ~DW8); OMAP_HSMMC_WRITE(host->base, HCTL, OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT); break; } if (host->id == OMAP_MMC1_DEVID) { /* Only MMC1 can interface at 3V without some flavor * of external transceiver; but they all handle 1.8V. */ if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) && (ios->vdd == DUAL_VOLT_OCR_BIT)) { /* * The mmc_select_voltage fn of the core does * not seem to set the power_mode to * 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), "Switch operation failed\n"); } } 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_mmc_stop_clock(host); regval = OMAP_HSMMC_READ(host->base, SYSCTL); regval = regval & ~(CLKD_MASK); regval = regval | (dsor << 6) | (DTO << 16); OMAP_HSMMC_WRITE(host->base, SYSCTL, regval); OMAP_HSMMC_WRITE(host->base, SYSCTL, OMAP_HSMMC_READ(host->base, SYSCTL) | ICE); /* Wait till the ICS bit is set */ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2 && 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) send_init_stream(host); 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); omap_hsmmc_disable_clks(host); }