summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/omap_hsmmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/omap_hsmmc.c')
-rw-r--r--drivers/mmc/host/omap_hsmmc.c347
1 files changed, 222 insertions, 125 deletions
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
index 4d1203236..7fb0753ab 100644
--- a/drivers/mmc/host/omap_hsmmc.c
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -181,18 +181,10 @@ struct omap_hsmmc_host {
struct mmc_data *data;
struct clk *fclk;
struct clk *dbclk;
- /*
- * vcc == configured supply
- * vcc_aux == optional
- * - MMC1, supply for DAT4..DAT7
- * - MMC2/MMC2, external level shifter voltage supply, for
- * chip (SDIO, eMMC, etc) or transceiver (MMC2 only)
- */
- struct regulator *vcc;
- struct regulator *vcc_aux;
struct regulator *pbias;
bool pbias_enabled;
void __iomem *base;
+ int vqmmc_enabled;
resource_size_t mapbase;
spinlock_t irq_lock; /* Prevent races with irq handler */
unsigned int dma_len;
@@ -213,7 +205,6 @@ struct omap_hsmmc_host {
int context_loss;
int protect_card;
int reqs_blocked;
- int use_reg;
int req_in_progress;
unsigned long clk_rate;
unsigned int flags;
@@ -254,32 +245,135 @@ static int omap_hsmmc_get_cover_state(struct device *dev)
return mmc_gpio_get_cd(host->mmc);
}
-#ifdef CONFIG_REGULATOR
+static int omap_hsmmc_enable_supply(struct mmc_host *mmc)
+{
+ int ret;
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+ struct mmc_ios *ios = &mmc->ios;
+
+ if (mmc->supply.vmmc) {
+ ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
+ if (ret)
+ return ret;
+ }
+
+ /* Enable interface voltage rail, if needed */
+ if (mmc->supply.vqmmc && !host->vqmmc_enabled) {
+ ret = regulator_enable(mmc->supply.vqmmc);
+ if (ret) {
+ dev_err(mmc_dev(mmc), "vmmc_aux reg enable failed\n");
+ goto err_vqmmc;
+ }
+ host->vqmmc_enabled = 1;
+ }
+
+ return 0;
+
+err_vqmmc:
+ if (mmc->supply.vmmc)
+ mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+
+ return ret;
+}
+
+static int omap_hsmmc_disable_supply(struct mmc_host *mmc)
+{
+ int ret;
+ int status;
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ if (mmc->supply.vqmmc && host->vqmmc_enabled) {
+ ret = regulator_disable(mmc->supply.vqmmc);
+ if (ret) {
+ dev_err(mmc_dev(mmc), "vmmc_aux reg disable failed\n");
+ return ret;
+ }
+ host->vqmmc_enabled = 0;
+ }
+
+ if (mmc->supply.vmmc) {
+ ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+ if (ret)
+ goto err_set_ocr;
+ }
+
+ return 0;
+
+err_set_ocr:
+ if (mmc->supply.vqmmc) {
+ status = regulator_enable(mmc->supply.vqmmc);
+ if (status)
+ dev_err(mmc_dev(mmc), "vmmc_aux re-enable failed\n");
+ }
+
+ return ret;
+}
+
+static int omap_hsmmc_set_pbias(struct omap_hsmmc_host *host, bool power_on,
+ int vdd)
+{
+ int ret;
+
+ if (!host->pbias)
+ return 0;
+
+ if (power_on) {
+ if (vdd <= VDD_165_195)
+ ret = regulator_set_voltage(host->pbias, VDD_1V8,
+ VDD_1V8);
+ else
+ ret = regulator_set_voltage(host->pbias, VDD_3V0,
+ VDD_3V0);
+ if (ret < 0) {
+ dev_err(host->dev, "pbias set voltage fail\n");
+ return ret;
+ }
+
+ if (host->pbias_enabled == 0) {
+ ret = regulator_enable(host->pbias);
+ if (ret) {
+ dev_err(host->dev, "pbias reg enable fail\n");
+ return ret;
+ }
+ host->pbias_enabled = 1;
+ }
+ } else {
+ if (host->pbias_enabled == 1) {
+ ret = regulator_disable(host->pbias);
+ if (ret) {
+ dev_err(host->dev, "pbias reg disable fail\n");
+ return ret;
+ }
+ host->pbias_enabled = 0;
+ }
+ }
+
+ return 0;
+}
static int omap_hsmmc_set_power(struct device *dev, int power_on, int vdd)
{
struct omap_hsmmc_host *host =
platform_get_drvdata(to_platform_device(dev));
+ struct mmc_host *mmc = host->mmc;
int ret = 0;
+ if (mmc_pdata(host)->set_power)
+ return mmc_pdata(host)->set_power(dev, power_on, vdd);
+
/*
* If we don't see a Vcc regulator, assume it's a fixed
* voltage always-on regulator.
*/
- if (!host->vcc)
+ if (!mmc->supply.vmmc)
return 0;
if (mmc_pdata(host)->before_set_reg)
mmc_pdata(host)->before_set_reg(dev, power_on, vdd);
- if (host->pbias) {
- if (host->pbias_enabled == 1) {
- ret = regulator_disable(host->pbias);
- if (!ret)
- host->pbias_enabled = 0;
- }
- regulator_set_voltage(host->pbias, VDD_3V0, VDD_3V0);
- }
+ ret = omap_hsmmc_set_pbias(host, false, 0);
+ if (ret)
+ return ret;
/*
* Assume Vcc regulator is used only to power the card ... OMAP
@@ -295,129 +389,138 @@ static int omap_hsmmc_set_power(struct device *dev, int power_on, int vdd)
* chips/cards need an interface voltage rail too.
*/
if (power_on) {
- if (host->vcc)
- ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
- /* Enable interface voltage rail, if needed */
- if (ret == 0 && host->vcc_aux) {
- ret = regulator_enable(host->vcc_aux);
- if (ret < 0 && host->vcc)
- ret = mmc_regulator_set_ocr(host->mmc,
- host->vcc, 0);
- }
- } else {
- /* Shut down the rail */
- if (host->vcc_aux)
- ret = regulator_disable(host->vcc_aux);
- if (host->vcc) {
- /* Then proceed to shut down the local regulator */
- ret = mmc_regulator_set_ocr(host->mmc,
- host->vcc, 0);
- }
- }
-
- if (host->pbias) {
- if (vdd <= VDD_165_195)
- ret = regulator_set_voltage(host->pbias, VDD_1V8,
- VDD_1V8);
- else
- ret = regulator_set_voltage(host->pbias, VDD_3V0,
- VDD_3V0);
- if (ret < 0)
- goto error_set_power;
+ ret = omap_hsmmc_enable_supply(mmc);
+ if (ret)
+ return ret;
- if (host->pbias_enabled == 0) {
- ret = regulator_enable(host->pbias);
- if (!ret)
- host->pbias_enabled = 1;
- }
+ ret = omap_hsmmc_set_pbias(host, true, vdd);
+ if (ret)
+ goto err_set_voltage;
+ } else {
+ ret = omap_hsmmc_disable_supply(mmc);
+ if (ret)
+ return ret;
}
if (mmc_pdata(host)->after_set_reg)
mmc_pdata(host)->after_set_reg(dev, power_on, vdd);
-error_set_power:
+ return 0;
+
+err_set_voltage:
+ omap_hsmmc_disable_supply(mmc);
+
return ret;
}
-static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
+static int omap_hsmmc_disable_boot_regulator(struct regulator *reg)
{
- struct regulator *reg;
- int ocr_value = 0;
+ int ret;
- reg = devm_regulator_get(host->dev, "vmmc");
- if (IS_ERR(reg)) {
- dev_err(host->dev, "unable to get vmmc regulator %ld\n",
- PTR_ERR(reg));
- return PTR_ERR(reg);
- } else {
- host->vcc = reg;
- ocr_value = mmc_regulator_get_ocrmask(reg);
- if (!mmc_pdata(host)->ocr_mask) {
- mmc_pdata(host)->ocr_mask = ocr_value;
- } else {
- if (!(mmc_pdata(host)->ocr_mask & ocr_value)) {
- dev_err(host->dev, "ocrmask %x is not supported\n",
- mmc_pdata(host)->ocr_mask);
- mmc_pdata(host)->ocr_mask = 0;
- return -EINVAL;
- }
- }
+ if (!reg)
+ return 0;
+
+ if (regulator_is_enabled(reg)) {
+ ret = regulator_enable(reg);
+ if (ret)
+ return ret;
+
+ ret = regulator_disable(reg);
+ if (ret)
+ return ret;
}
- mmc_pdata(host)->set_power = omap_hsmmc_set_power;
- /* Allow an aux regulator */
- reg = devm_regulator_get_optional(host->dev, "vmmc_aux");
- host->vcc_aux = IS_ERR(reg) ? NULL : reg;
+ return 0;
+}
- reg = devm_regulator_get_optional(host->dev, "pbias");
- host->pbias = IS_ERR(reg) ? NULL : reg;
+static int omap_hsmmc_disable_boot_regulators(struct omap_hsmmc_host *host)
+{
+ struct mmc_host *mmc = host->mmc;
+ int ret;
- /* For eMMC do not power off when not in sleep state */
- if (mmc_pdata(host)->no_regulator_off_init)
- return 0;
/*
- * To disable boot_on regulator, enable regulator
- * to increase usecount and then disable it.
+ * disable regulators enabled during boot and get the usecount
+ * right so that regulators can be enabled/disabled by checking
+ * the return value of regulator_is_enabled
*/
- if ((host->vcc && regulator_is_enabled(host->vcc) > 0) ||
- (host->vcc_aux && regulator_is_enabled(host->vcc_aux))) {
- int vdd = ffs(mmc_pdata(host)->ocr_mask) - 1;
+ ret = omap_hsmmc_disable_boot_regulator(mmc->supply.vmmc);
+ if (ret) {
+ dev_err(host->dev, "fail to disable boot enabled vmmc reg\n");
+ return ret;
+ }
+
+ ret = omap_hsmmc_disable_boot_regulator(mmc->supply.vqmmc);
+ if (ret) {
+ dev_err(host->dev,
+ "fail to disable boot enabled vmmc_aux reg\n");
+ return ret;
+ }
- mmc_pdata(host)->set_power(host->dev, 1, vdd);
- mmc_pdata(host)->set_power(host->dev, 0, 0);
+ ret = omap_hsmmc_disable_boot_regulator(host->pbias);
+ if (ret) {
+ dev_err(host->dev,
+ "failed to disable boot enabled pbias reg\n");
+ return ret;
}
return 0;
}
-static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host)
+static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
{
- mmc_pdata(host)->set_power = NULL;
-}
+ int ocr_value = 0;
+ int ret;
+ struct mmc_host *mmc = host->mmc;
-static inline int omap_hsmmc_have_reg(void)
-{
- return 1;
-}
+ if (mmc_pdata(host)->set_power)
+ return 0;
-#else
+ mmc->supply.vmmc = devm_regulator_get_optional(host->dev, "vmmc");
+ if (IS_ERR(mmc->supply.vmmc)) {
+ ret = PTR_ERR(mmc->supply.vmmc);
+ if ((ret != -ENODEV) && host->dev->of_node)
+ return ret;
+ dev_dbg(host->dev, "unable to get vmmc regulator %ld\n",
+ PTR_ERR(mmc->supply.vmmc));
+ mmc->supply.vmmc = NULL;
+ } else {
+ ocr_value = mmc_regulator_get_ocrmask(mmc->supply.vmmc);
+ if (ocr_value > 0)
+ mmc_pdata(host)->ocr_mask = ocr_value;
+ }
-static inline int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
-{
- return -EINVAL;
-}
+ /* Allow an aux regulator */
+ mmc->supply.vqmmc = devm_regulator_get_optional(host->dev, "vmmc_aux");
+ if (IS_ERR(mmc->supply.vqmmc)) {
+ ret = PTR_ERR(mmc->supply.vqmmc);
+ if ((ret != -ENODEV) && host->dev->of_node)
+ return ret;
+ dev_dbg(host->dev, "unable to get vmmc_aux regulator %ld\n",
+ PTR_ERR(mmc->supply.vqmmc));
+ mmc->supply.vqmmc = NULL;
+ }
-static inline void omap_hsmmc_reg_put(struct omap_hsmmc_host *host)
-{
-}
+ host->pbias = devm_regulator_get_optional(host->dev, "pbias");
+ if (IS_ERR(host->pbias)) {
+ ret = PTR_ERR(host->pbias);
+ if ((ret != -ENODEV) && host->dev->of_node)
+ return ret;
+ dev_dbg(host->dev, "unable to get pbias regulator %ld\n",
+ PTR_ERR(host->pbias));
+ host->pbias = NULL;
+ }
+
+ /* For eMMC do not power off when not in sleep state */
+ if (mmc_pdata(host)->no_regulator_off_init)
+ return 0;
+
+ ret = omap_hsmmc_disable_boot_regulators(host);
+ if (ret)
+ return ret;
-static inline int omap_hsmmc_have_reg(void)
-{
return 0;
}
-#endif
-
static irqreturn_t omap_hsmmc_cover_irq(int irq, void *dev_id);
static int omap_hsmmc_gpio_init(struct mmc_host *mmc,
@@ -1149,11 +1252,11 @@ static int omap_hsmmc_switch_opcond(struct omap_hsmmc_host *host, int vdd)
clk_disable_unprepare(host->dbclk);
/* Turn the power off */
- ret = mmc_pdata(host)->set_power(host->dev, 0, 0);
+ ret = omap_hsmmc_set_power(host->dev, 0, 0);
/* Turn the power ON with given VDD 1.8 or 3.0v */
if (!ret)
- ret = mmc_pdata(host)->set_power(host->dev, 1, vdd);
+ ret = omap_hsmmc_set_power(host->dev, 1, vdd);
pm_runtime_get_sync(host->dev);
if (host->dbclk)
clk_prepare_enable(host->dbclk);
@@ -1552,10 +1655,10 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
if (ios->power_mode != host->power_mode) {
switch (ios->power_mode) {
case MMC_POWER_OFF:
- mmc_pdata(host)->set_power(host->dev, 0, 0);
+ omap_hsmmc_set_power(host->dev, 0, 0);
break;
case MMC_POWER_UP:
- mmc_pdata(host)->set_power(host->dev, 1, ios->vdd);
+ omap_hsmmc_set_power(host->dev, 1, ios->vdd);
break;
case MMC_POWER_ON:
do_send_init_stream = 1;
@@ -1954,6 +2057,7 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
host->power_mode = MMC_POWER_OFF;
host->next_data.cookie = 1;
host->pbias_enabled = 0;
+ host->vqmmc_enabled = 0;
ret = omap_hsmmc_gpio_init(mmc, host, pdata);
if (ret)
@@ -2078,12 +2182,9 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
goto err_irq;
}
- if (omap_hsmmc_have_reg() && !mmc_pdata(host)->set_power) {
- ret = omap_hsmmc_reg_get(host);
- if (ret)
- goto err_irq;
- host->use_reg = 1;
- }
+ ret = omap_hsmmc_reg_get(host);
+ if (ret)
+ goto err_irq;
mmc->ocr_avail = mmc_pdata(host)->ocr_mask;
@@ -2125,8 +2226,6 @@ static int omap_hsmmc_probe(struct platform_device *pdev)
err_slot_name:
mmc_remove_host(mmc);
- if (host->use_reg)
- omap_hsmmc_reg_put(host);
err_irq:
device_init_wakeup(&pdev->dev, false);
if (host->tx_chan)
@@ -2150,8 +2249,6 @@ static int omap_hsmmc_remove(struct platform_device *pdev)
pm_runtime_get_sync(host->dev);
mmc_remove_host(host->mmc);
- if (host->use_reg)
- omap_hsmmc_reg_put(host);
if (host->tx_chan)
dma_release_channel(host->tx_chan);