arm/dt: msm8974: Add regulator support for SDC controllers
SDC controllers need two types of power supply:
- vdd (SD/eMMC flash core power supply)
- vdd_io (SD/eMMC I/O pad power supply)
Add support for enable, disable, low power mode setting
for these regulators.
Change-Id: Ifd0ef0d4dd9732893f49700fe86b1dad24497f71
Signed-off-by: Sujit Reddy Thumma <sthumma@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/mmc/msm_sdcc.txt b/Documentation/devicetree/bindings/mmc/msm_sdcc.txt
index 0c1762d..5712aa2 100644
--- a/Documentation/devicetree/bindings/mmc/msm_sdcc.txt
+++ b/Documentation/devicetree/bindings/mmc/msm_sdcc.txt
@@ -10,6 +10,8 @@
- qcom,sdcc-clk-rates : specifies supported SDCC clock frequencies, Units - Hz.
- qcom,sdcc-sup-voltages: specifies supported voltage ranges for card. Should always be
specified in pairs (min, max), Units - mV.
+ - <supply-name>-supply: phandle to the regulator device tree node
+ "supply-name" examples are "vdd", "vdd-io".
Optional Properties:
- cell-index - defines slot ID.
@@ -23,6 +25,14 @@
- qcom,sdcc-disable_cmd23 - disable sending CMD23 to card when controller can't support it.
- qcom,sdcc-hs200 - enable eMMC4.5 HS200 bus speed mode
+In the following, <supply> can be vdd (flash core voltage) or vdd-io (I/O voltage).
+ - qcom,sdcc-<supply>-always_on - specifies whether supply should be kept "on" always.
+ - qcom,sdcc-<supply>-lpm_sup - specifies whether supply can be kept in low power mode (lpm).
+ - qcom,sdcc-<supply>-voltage_level - specifies voltage levels for supply. Should be
+ specified in pairs (min, max), units uV.
+ - qcom,sdcc-<supply>-current_level - specifies load levels for supply in lpm or
+ high power mode (hpm). Should be specified in pairs (lpm, hpm), units uA.
+
Example:
qcom,sdcc@f9600000 {
diff --git a/arch/arm/boot/dts/msm8974.dtsi b/arch/arm/boot/dts/msm8974.dtsi
index 6bcc3c2..68f96db 100644
--- a/arch/arm/boot/dts/msm8974.dtsi
+++ b/arch/arm/boot/dts/msm8974.dtsi
@@ -83,6 +83,15 @@
compatible = "qcom,msm-sdcc";
reg = <0xf9824000 0x1000>;
interrupts = <0 123 0>;
+ vdd-supply = <&pm8941_l20>;
+ vdd-io-supply = <&pm8941_s3>;
+
+ qcom,sdcc-vdd-voltage_level = <2950000 2950000>;
+ qcom,sdcc-vdd-current_level = <800 500000>;
+
+ qcom,sdcc-vdd-io-always_on;
+ qcom,sdcc-vdd-io-voltage_level = <1800000 1800000>;
+ qcom,sdcc-vdd-io-current_level = <250 154000>;
qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000 200000000>;
qcom,sdcc-sup-voltages = <2950 2950>;
@@ -96,6 +105,16 @@
compatible = "qcom,msm-sdcc";
reg = <0xf98a4000 0x1000>;
interrupts = <0 125 0>;
+ vdd-supply = <&pm8941_l21>;
+ vdd-io-supply = <&pm8941_l13>;
+
+ qcom,sdcc-vdd-voltage_level = <2950000 2950000>;
+ qcom,sdcc-vdd-current_level = <9000 800000>;
+
+ qcom,sdcc-vdd-io-always_on;
+ qcom,sdcc-vdd-io-lpm_sup;
+ qcom,sdcc-vdd-io-voltage_level = <1800000 2950000>;
+ qcom,sdcc-vdd-io-current_level = <6 22000>;
qcom,sdcc-clk-rates = <400000 25000000 50000000 100000000 200000000>;
qcom,sdcc-sup-voltages = <2950 2950>;
diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c
index 03bcc7c..d6daeae 100644
--- a/drivers/mmc/host/msm_sdcc.c
+++ b/drivers/mmc/host/msm_sdcc.c
@@ -2112,8 +2112,15 @@
goto out;
}
- if (regulator_count_voltages(vreg->reg) > 0)
+ if (regulator_count_voltages(vreg->reg) > 0) {
vreg->set_voltage_sup = 1;
+ /* sanity check */
+ if (!vreg->high_vol_level || !vreg->hpm_uA) {
+ pr_err("%s: %s invalid constraints specified\n",
+ __func__, vreg->name);
+ rc = -EINVAL;
+ }
+ }
out:
return rc;
@@ -4594,16 +4601,78 @@
spin_unlock_irqrestore(&host->lock, flags);
}
+#define MAX_PROP_SIZE 32
+static int msmsdcc_dt_parse_vreg_info(struct device *dev,
+ struct msm_mmc_reg_data **vreg_data, const char *vreg_name)
+{
+ int len, ret = 0;
+ const __be32 *prop;
+ char prop_name[MAX_PROP_SIZE];
+ struct msm_mmc_reg_data *vreg;
+ struct device_node *np = dev->of_node;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name);
+ if (of_parse_phandle(np, prop_name, 0)) {
+ vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
+ if (!vreg) {
+ dev_err(dev, "No memory for vreg: %s\n", vreg_name);
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ vreg->name = vreg_name;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,sdcc-%s-always_on", vreg_name);
+ if (of_get_property(np, prop_name, NULL))
+ vreg->always_on = true;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,sdcc-%s-lpm_sup", vreg_name);
+ if (of_get_property(np, prop_name, NULL))
+ vreg->lpm_sup = true;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,sdcc-%s-voltage_level", vreg_name);
+ prop = of_get_property(np, prop_name, &len);
+ if (!prop || (len != (2 * sizeof(__be32)))) {
+ dev_warn(dev, "%s %s property\n",
+ prop ? "invalid format" : "no", prop_name);
+ } else {
+ vreg->low_vol_level = be32_to_cpup(&prop[0]);
+ vreg->high_vol_level = be32_to_cpup(&prop[1]);
+ }
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,sdcc-%s-current_level", vreg_name);
+ prop = of_get_property(np, prop_name, &len);
+ if (!prop || (len != (2 * sizeof(__be32)))) {
+ dev_warn(dev, "%s %s property\n",
+ prop ? "invalid format" : "no", prop_name);
+ } else {
+ vreg->lpm_uA = be32_to_cpup(&prop[0]);
+ vreg->hpm_uA = be32_to_cpup(&prop[1]);
+ }
+
+ *vreg_data = vreg;
+ dev_dbg(dev, "%s: %s %s vol=[%d %d]uV, curr=[%d %d]uA\n",
+ vreg->name, vreg->always_on ? "always_on," : "",
+ vreg->lpm_sup ? "lpm_sup," : "", vreg->low_vol_level,
+ vreg->high_vol_level, vreg->lpm_uA, vreg->hpm_uA);
+ }
+
+err:
+ return ret;
+}
+
static struct mmc_platform_data *msmsdcc_populate_pdata(struct device *dev)
{
int i, ret;
struct mmc_platform_data *pdata;
struct device_node *np = dev->of_node;
u32 bus_width = 0;
- u32 *clk_table;
- int clk_table_len;
- u32 *sup_voltages;
- int sup_volt_len;
+ u32 *clk_table, *sup_voltages;
+ int clk_table_len, sup_volt_len;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
@@ -4686,6 +4755,21 @@
dev_err(dev, "Supported clock rates not specified\n");
}
+ pdata->vreg_data = devm_kzalloc(dev,
+ sizeof(struct msm_mmc_slot_reg_data), GFP_KERNEL);
+ if (!pdata->vreg_data) {
+ dev_err(dev, "could not allocate memory for vreg_data\n");
+ goto err;
+ }
+
+ if (msmsdcc_dt_parse_vreg_info(dev,
+ &pdata->vreg_data->vdd_data, "vdd"))
+ goto err;
+
+ if (msmsdcc_dt_parse_vreg_info(dev,
+ &pdata->vreg_data->vdd_io_data, "vdd-io"))
+ goto err;
+
if (of_get_property(np, "qcom,sdcc-nonremovable", NULL))
pdata->nonremovable = true;
if (of_get_property(np, "qcom,sdcc-disable_cmd23", NULL))