diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 25f9ad7b89d9..6c8c13f335df 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -45,6 +45,26 @@ Optional Properties: the driver will construct one based on the card supported max and min frequencies. The frequencies must be ordered from lowest to highest. + - qcom,pm-qos-irq-type - the PM QoS request type to be used for IRQ voting. + Can be either "affine_cores" or "affine_irq". If not specified, will default + to "affine_cores". Use "affine_irq" setting in case an IRQ balancer is active, + and IRQ affinity changes during runtime. + - qcom,pm-qos-irq-cpu - specifies the CPU for which IRQ voting shall be done. + If "affine_cores" was specified for property 'qcom,pm-qos-irq-type' + then this property must be defined, and is not relevant otherwise. + - qcom,pm-qos-irq-latency - a tuple defining two latency values with which + PM QoS IRQ voting shall be done. The first value is the latecy to be used + when load is high (performance mode) and the second is for low loads + (power saving mode). + - qcom,pm-qos-cpu-groups - defines cpu groups mapping. + Each cell represnets a group, which is a cpu bitmask defining which cpus belong + to that group. + - qcom,pm-qos--latency-us - where is either "cmdq" or "legacy". + An array of latency value tuples, each tuple corresponding to a cpu group in the order + defined in property 'qcom,pm-qos-cpu-groups'. The first value is the latecy to be used + when load is high (performance mode) and the second is for low loads + (power saving mode). These values will be used for cpu group voting for + command-queueing mode or legacy respectively. In the following, can be vdd (flash core voltage) or vdd-io (I/O voltage). - qcom,-always-on - specifies whether supply should be kept "on" always. @@ -122,6 +142,13 @@ Example: <&msmgpio 36 0>, /* DATA2 */ <&msmgpio 35 0>; /* DATA3 */ qcom,gpio-names = "CLK", "CMD", "DAT0", "DAT1", "DAT2", "DAT3"; + + qcom,pm-qos-irq-type = "affine_cores"; + qcom,pm-qos-irq-cpu = <0>; + qcom,pm-qos-irq-latency = <500 100>; + qcom,pm-qos-cpu-groups = <0x03 0x0c>; + qcom,pm-qos-cmdq-latency-us = <50 100>, <50 100>; + qcom,pm-qos-legacy-latency-us = <50 100>, <50 100>; }; sdhc_2: qcom,sdhc@f98a4900 { @@ -145,4 +172,7 @@ Example: qcom,pad-pull-off = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */ qcom,pad-drv-on = <0x7 0x4 0x4>; /* 16mA, 10mA, 10mA */ qcom,pad-drv-off = <0x0 0x0 0x0>; /* 2mA, 2mA, 2mA */ + + qcom,pm-qos-irq-type = "affine_irq"; + qcom,pm-qos-irq-latency = <120 200>; }; diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 415131ff361a..14726cae365a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1424,6 +1424,169 @@ out: return ret; } +static int sdhci_msm_pm_qos_parse_irq(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + struct device_node *np = dev->of_node; + const char *str; + u32 cpu; + int ret = 0; + int i; + + pdata->pm_qos_data.irq_valid = false; + pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_CORES; + if (!of_property_read_string(np, "qcom,pm-qos-irq-type", &str) && + !strcmp(str, "affine_irq")) { + pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_IRQ; + } + + /* must specify cpu for "affine_cores" type */ + if (pdata->pm_qos_data.irq_req_type == PM_QOS_REQ_AFFINE_CORES) { + pdata->pm_qos_data.irq_cpu = -1; + ret = of_property_read_u32(np, "qcom,pm-qos-irq-cpu", &cpu); + if (ret) { + dev_err(dev, "%s: error %d reading irq cpu\n", __func__, + ret); + goto out; + } + if (cpu < 0 || cpu >= num_possible_cpus()) { + dev_err(dev, "%s: invalid irq cpu %d (NR_CPUS=%d)\n", + __func__, cpu, num_possible_cpus()); + ret = -EINVAL; + goto out; + } + pdata->pm_qos_data.irq_cpu = cpu; + } + + if (of_property_count_u32_elems(np, "qcom,pm-qos-irq-latency") != + SDHCI_POWER_POLICY_NUM) { + dev_err(dev, "%s: could not read %d values for 'qcom,pm-qos-irq-latency'\n", + __func__, SDHCI_POWER_POLICY_NUM); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < SDHCI_POWER_POLICY_NUM; i++) + of_property_read_u32_index(np, "qcom,pm-qos-irq-latency", i, + &pdata->pm_qos_data.irq_latency.latency[i]); + + pdata->pm_qos_data.irq_valid = true; +out: + return ret; +} + +static int sdhci_msm_pm_qos_parse_cpu_groups(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + struct device_node *np = dev->of_node; + u32 mask; + int nr_groups; + int ret; + int i; + + /* Read cpu group mapping */ + nr_groups = of_property_count_u32_elems(np, "qcom,pm-qos-cpu-groups"); + if (nr_groups <= 0) { + ret = -EINVAL; + goto out; + } + pdata->pm_qos_data.cpu_group_map.nr_groups = nr_groups; + pdata->pm_qos_data.cpu_group_map.mask = + kcalloc(nr_groups, sizeof(cpumask_t), GFP_KERNEL); + if (!pdata->pm_qos_data.cpu_group_map.mask) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < nr_groups; i++) { + of_property_read_u32_index(np, "qcom,pm-qos-cpu-groups", + i, &mask); + + pdata->pm_qos_data.cpu_group_map.mask[i].bits[0] = mask; + if (!cpumask_subset(&pdata->pm_qos_data.cpu_group_map.mask[i], + cpu_possible_mask)) { + dev_err(dev, "%s: invalid mask 0x%x of cpu group #%d\n", + __func__, mask, i); + ret = -EINVAL; + goto free_res; + } + } + return 0; + +free_res: + kfree(pdata->pm_qos_data.cpu_group_map.mask); +out: + return ret; +} + +static int sdhci_msm_pm_qos_parse_latency(struct device *dev, const char *name, + int nr_groups, struct sdhci_msm_pm_qos_latency **latency) +{ + struct device_node *np = dev->of_node; + struct sdhci_msm_pm_qos_latency *values; + int ret; + int i; + int group; + int cfg; + + ret = of_property_count_u32_elems(np, name); + if (ret > 0 && ret != SDHCI_POWER_POLICY_NUM * nr_groups) { + dev_err(dev, "%s: invalid number of values for property %s: expected=%d actual=%d\n", + __func__, name, SDHCI_POWER_POLICY_NUM * nr_groups, + ret); + return -EINVAL; + } else if (ret < 0) { + return ret; + } + + values = kcalloc(nr_groups, sizeof(struct sdhci_msm_pm_qos_latency), + GFP_KERNEL); + if (!values) + return -ENOMEM; + + for (i = 0; i < SDHCI_POWER_POLICY_NUM * nr_groups; i++) { + group = i / SDHCI_POWER_POLICY_NUM; + cfg = i % SDHCI_POWER_POLICY_NUM; + of_property_read_u32_index(np, name, i, + &(values[group].latency[cfg])); + } + + *latency = values; + return 0; +} + +static void sdhci_msm_pm_qos_parse(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + if (sdhci_msm_pm_qos_parse_irq(dev, pdata)) + dev_notice(dev, "%s: PM QoS voting for IRQ will be disabled\n", + __func__); + + if (!sdhci_msm_pm_qos_parse_cpu_groups(dev, pdata)) { + pdata->pm_qos_data.cmdq_valid = + !sdhci_msm_pm_qos_parse_latency(dev, + "qcom,pm-qos-cmdq-latency-us", + pdata->pm_qos_data.cpu_group_map.nr_groups, + &pdata->pm_qos_data.cmdq_latency); + pdata->pm_qos_data.legacy_valid = + !sdhci_msm_pm_qos_parse_latency(dev, + "qcom,pm-qos-legacy-latency-us", + pdata->pm_qos_data.cpu_group_map.nr_groups, + &pdata->pm_qos_data.latency); + if (!pdata->pm_qos_data.cmdq_valid && + !pdata->pm_qos_data.legacy_valid) { + /* clean-up previously allocated arrays */ + kfree(pdata->pm_qos_data.latency); + kfree(pdata->pm_qos_data.cmdq_latency); + dev_err(dev, "%s: invalid PM QoS latency values. Voting for cpu group will be disabled\n", + __func__); + } + } else { + dev_notice(dev, "%s: PM QoS voting for cpu group will be disabled\n", + __func__); + } +} + /* Parse platform data */ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, @@ -1565,6 +1728,8 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, if (of_property_read_bool(np, "qcom,wakeup-on-idle")) msm_host->mmc->wakeup_on_idle = true; + sdhci_msm_pm_qos_parse(dev, pdata); + return pdata; out: return NULL; diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 4f724b181c51..1a7fd827b96e 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -16,6 +16,7 @@ #define __SDHCI_MSM_H__ #include +#include #include "sdhci-pltfm.h" /* This structure keeps information per regulator */ @@ -83,6 +84,27 @@ struct sdhci_msm_bus_voting_data { unsigned int bw_vecs_size; }; +struct sdhci_msm_cpu_group_map { + int nr_groups; + cpumask_t *mask; +}; + +struct sdhci_msm_pm_qos_latency { + s32 latency[SDHCI_POWER_POLICY_NUM]; +}; + +struct sdhci_msm_pm_qos_data { + struct sdhci_msm_cpu_group_map cpu_group_map; + enum pm_qos_req_type irq_req_type; + int irq_cpu; + struct sdhci_msm_pm_qos_latency irq_latency; + struct sdhci_msm_pm_qos_latency *cmdq_latency; + struct sdhci_msm_pm_qos_latency *latency; + bool irq_valid; + bool cmdq_valid; + bool legacy_valid; +}; + struct sdhci_msm_pltfm_data { /* Supported UHS-I Modes */ u32 caps; @@ -106,6 +128,7 @@ struct sdhci_msm_pltfm_data { unsigned char sup_ice_clk_cnt; u32 ice_clk_max; u32 ice_clk_min; + struct sdhci_msm_pm_qos_data pm_qos_data; }; struct sdhci_msm_bus_vote { diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 36e178f52d90..5468abe9698b 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -328,6 +328,7 @@ enum sdhci_cookie { enum sdhci_power_policy { SDHCI_PERFORMANCE_MODE, SDHCI_POWER_SAVE_MODE, + SDHCI_POWER_POLICY_NUM /* Always keep this one last */ }; struct sdhci_host {