diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-03-29 11:06:55 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-03-29 11:06:55 -0700 |
commit | 1d59c3b669faddb91737f4e59c09305878a971d8 (patch) | |
tree | dc00847bb3bd096d95349729d2175a5e8770da68 /drivers | |
parent | 5efabdadcf4a5b9a37847ecc85ba71cf2eff0fcf (diff) | |
parent | 3b65dd5be3c72b9d2013bfe6e9261e2b06222fa9 (diff) |
Merge tag 'pm-5.18-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
Pull more power management updates from Rafael Wysocki:
"These update ARM cpufreq drivers, the OPP (Operating Performance
Points) library and the power management documentation.
Specifics:
- Add per core DVFS support for QCom SoC (Bjorn Andersson), convert
to yaml binding (Manivannan Sadhasivam) and various other fixes to
the QCom drivers (Luca Weiss).
- Add OPP table for imx7s SoC (Denys Drozdov) and minor fixes (Stefan
Agner).
- Fix CPPC driver's freq/performance conversions (Pierre Gondois).
- Minor generic cleanups (Yury Norov).
- Introduce opp-microwatt property to the OPP core, bindings, etc
(Lukasz Luba).
- Convert DT bindings to schema format and various related fixes
(Yassine Oudjana).
- Expose OPP's OF node in debugfs (Viresh Kumar).
- Add Intel uncore frequency scaling documentation file to its
MAINTAINERS entry (Srinivas Pandruvada).
- Clean up the AMD P-state driver documentation (Jan Engelhardt)"
* tag 'pm-5.18-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (24 commits)
Documentation: amd-pstate: grammar and sentence structure updates
dt-bindings: cpufreq: cpufreq-qcom-hw: Convert to YAML bindings
dt-bindings: dvfs: Use MediaTek CPUFREQ HW as an example
Documentation: EM: Describe new registration method using DT
OPP: Add support of "opp-microwatt" for EM registration
PM: EM: add macro to set .active_power() callback conditionally
OPP: Add "opp-microwatt" supporting code
dt-bindings: opp: Add "opp-microwatt" entry in the OPP
MAINTAINERS: Add additional file to uncore frequency control
cpufreq: blocklist Qualcomm sc8280xp and sa8540p in cpufreq-dt-platdev
cpufreq: qcom-hw: Add support for per-core-dcvs
dt-bindings: power: avs: qcom,cpr: Convert to DT schema
arm64: dts: qcom: qcs404: Rename CPU and CPR OPP tables
arm64: dts: qcom: msm8996: Rename cluster OPP tables
dt-bindings: opp: Convert qcom-nvmem-cpufreq to DT schema
dt-bindings: opp: qcom-opp: Convert to DT schema
arm64: dts: qcom: msm8996-mtp: Add msm8996 compatible
dt-bindings: arm: qcom: Add msm8996 and apq8096 compatibles
opp: Expose of-node's name in debugfs
cpufreq: CPPC: Fix performance/frequency conversion
...
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/cpufreq/cppc_cpufreq.c | 43 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq-dt-platdev.c | 3 | ||||
-rw-r--r-- | drivers/cpufreq/qcom-cpufreq-hw.c | 20 | ||||
-rw-r--r-- | drivers/cpufreq/qcom-cpufreq-nvmem.c | 2 | ||||
-rw-r--r-- | drivers/cpufreq/scmi-cpufreq.c | 2 | ||||
-rw-r--r-- | drivers/opp/core.c | 25 | ||||
-rw-r--r-- | drivers/opp/debugfs.c | 8 | ||||
-rw-r--r-- | drivers/opp/of.c | 108 | ||||
-rw-r--r-- | drivers/opp/opp.h | 1 |
9 files changed, 184 insertions, 28 deletions
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index db17196266e4..82d370ae6a4a 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -303,52 +303,48 @@ static u64 cppc_get_dmi_max_khz(void) /* * If CPPC lowest_freq and nominal_freq registers are exposed then we can - * use them to convert perf to freq and vice versa - * - * If the perf/freq point lies between Nominal and Lowest, we can treat - * (Low perf, Low freq) and (Nom Perf, Nom freq) as 2D co-ordinates of a line - * and extrapolate the rest - * For perf/freq > Nominal, we use the ratio perf:freq at Nominal for conversion + * use them to convert perf to freq and vice versa. The conversion is + * extrapolated as an affine function passing by the 2 points: + * - (Low perf, Low freq) + * - (Nominal perf, Nominal perf) */ static unsigned int cppc_cpufreq_perf_to_khz(struct cppc_cpudata *cpu_data, unsigned int perf) { struct cppc_perf_caps *caps = &cpu_data->perf_caps; + s64 retval, offset = 0; static u64 max_khz; u64 mul, div; if (caps->lowest_freq && caps->nominal_freq) { - if (perf >= caps->nominal_perf) { - mul = caps->nominal_freq; - div = caps->nominal_perf; - } else { - mul = caps->nominal_freq - caps->lowest_freq; - div = caps->nominal_perf - caps->lowest_perf; - } + mul = caps->nominal_freq - caps->lowest_freq; + div = caps->nominal_perf - caps->lowest_perf; + offset = caps->nominal_freq - div64_u64(caps->nominal_perf * mul, div); } else { if (!max_khz) max_khz = cppc_get_dmi_max_khz(); mul = max_khz; div = caps->highest_perf; } - return (u64)perf * mul / div; + + retval = offset + div64_u64(perf * mul, div); + if (retval >= 0) + return retval; + return 0; } static unsigned int cppc_cpufreq_khz_to_perf(struct cppc_cpudata *cpu_data, unsigned int freq) { struct cppc_perf_caps *caps = &cpu_data->perf_caps; + s64 retval, offset = 0; static u64 max_khz; u64 mul, div; if (caps->lowest_freq && caps->nominal_freq) { - if (freq >= caps->nominal_freq) { - mul = caps->nominal_perf; - div = caps->nominal_freq; - } else { - mul = caps->lowest_perf; - div = caps->lowest_freq; - } + mul = caps->nominal_perf - caps->lowest_perf; + div = caps->nominal_freq - caps->lowest_freq; + offset = caps->nominal_perf - div64_u64(caps->nominal_freq * mul, div); } else { if (!max_khz) max_khz = cppc_get_dmi_max_khz(); @@ -356,7 +352,10 @@ static unsigned int cppc_cpufreq_khz_to_perf(struct cppc_cpudata *cpu_data, div = max_khz; } - return (u64)freq * mul / div; + retval = offset + div64_u64(freq * mul, div); + if (retval >= 0) + return retval; + return 0; } static int cppc_cpufreq_set_target(struct cpufreq_policy *policy, diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index ca1d103ec449..96de1536e1cb 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -110,6 +110,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "fsl,imx7ulp", }, { .compatible = "fsl,imx7d", }, + { .compatible = "fsl,imx7s", }, { .compatible = "fsl,imx8mq", }, { .compatible = "fsl,imx8mm", }, { .compatible = "fsl,imx8mn", }, @@ -138,9 +139,11 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "qcom,msm8996", }, { .compatible = "qcom,qcs404", }, { .compatible = "qcom,sa8155p" }, + { .compatible = "qcom,sa8540p" }, { .compatible = "qcom,sc7180", }, { .compatible = "qcom,sc7280", }, { .compatible = "qcom,sc8180x", }, + { .compatible = "qcom,sc8280xp", }, { .compatible = "qcom,sdm845", }, { .compatible = "qcom,sm6350", }, { .compatible = "qcom,sm8150", }, diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index effbb680b453..f9d593ff4718 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -28,6 +28,7 @@ struct qcom_cpufreq_soc_data { u32 reg_enable; + u32 reg_dcvs_ctrl; u32 reg_freq_lut; u32 reg_volt_lut; u32 reg_current_vote; @@ -50,6 +51,8 @@ struct qcom_cpufreq_data { bool cancel_throttle; struct delayed_work throttle_work; struct cpufreq_policy *policy; + + bool per_core_dcvs; }; static unsigned long cpu_hw_rate, xo_rate; @@ -102,9 +105,14 @@ static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, struct qcom_cpufreq_data *data = policy->driver_data; const struct qcom_cpufreq_soc_data *soc_data = data->soc_data; unsigned long freq = policy->freq_table[index].frequency; + unsigned int i; writel_relaxed(index, data->base + soc_data->reg_perf_state); + if (data->per_core_dcvs) + for (i = 1; i < cpumask_weight(policy->related_cpus); i++) + writel_relaxed(index, data->base + soc_data->reg_perf_state + i * 4); + if (icc_scaling_enabled) qcom_cpufreq_set_bw(policy, freq); @@ -137,10 +145,15 @@ static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, struct qcom_cpufreq_data *data = policy->driver_data; const struct qcom_cpufreq_soc_data *soc_data = data->soc_data; unsigned int index; + unsigned int i; index = policy->cached_resolved_idx; writel_relaxed(index, data->base + soc_data->reg_perf_state); + if (data->per_core_dcvs) + for (i = 1; i < cpumask_weight(policy->related_cpus); i++) + writel_relaxed(index, data->base + soc_data->reg_perf_state + i * 4); + return policy->freq_table[index].frequency; } @@ -342,6 +355,7 @@ static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data) static const struct qcom_cpufreq_soc_data qcom_soc_data = { .reg_enable = 0x0, + .reg_dcvs_ctrl = 0xbc, .reg_freq_lut = 0x110, .reg_volt_lut = 0x114, .reg_current_vote = 0x704, @@ -351,6 +365,7 @@ static const struct qcom_cpufreq_soc_data qcom_soc_data = { static const struct qcom_cpufreq_soc_data epss_soc_data = { .reg_enable = 0x0, + .reg_dcvs_ctrl = 0xb0, .reg_freq_lut = 0x100, .reg_volt_lut = 0x200, .reg_perf_state = 0x320, @@ -481,8 +496,11 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) goto error; } + if (readl_relaxed(base + data->soc_data->reg_dcvs_ctrl) & 0x1) + data->per_core_dcvs = true; + qcom_get_related_cpus(index, policy->cpus); - if (!cpumask_weight(policy->cpus)) { + if (cpumask_empty(policy->cpus)) { dev_err(dev, "Domain-%d failed to get related CPUs\n", index); ret = -ENOENT; goto error; diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c index d1744b5d9619..6dfa86971a75 100644 --- a/drivers/cpufreq/qcom-cpufreq-nvmem.c +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -130,7 +130,7 @@ static void get_krait_bin_format_b(struct device *cpu_dev, } /* Check PVS_BLOW_STATUS */ - pte_efuse = *(((u32 *)buf) + 4); + pte_efuse = *(((u32 *)buf) + 1); pte_efuse &= BIT(21); if (pte_efuse) { dev_dbg(cpu_dev, "PVS bin: %d\n", *pvs); diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c index 1e0cd4d165f0..919fa6e3f462 100644 --- a/drivers/cpufreq/scmi-cpufreq.c +++ b/drivers/cpufreq/scmi-cpufreq.c @@ -154,7 +154,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy) * table and opp-shared. */ ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, priv->opp_shared_cpus); - if (ret || !cpumask_weight(priv->opp_shared_cpus)) { + if (ret || cpumask_empty(priv->opp_shared_cpus)) { /* * Either opp-table is not set or no opp-shared was found. * Use the CPU mask from SCMI to designate CPUs sharing an OPP diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 3057beabd370..740407252298 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -114,6 +114,31 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp) EXPORT_SYMBOL_GPL(dev_pm_opp_get_voltage); /** + * dev_pm_opp_get_power() - Gets the power corresponding to an opp + * @opp: opp for which power has to be returned for + * + * Return: power in micro watt corresponding to the opp, else + * return 0 + * + * This is useful only for devices with single power supply. + */ +unsigned long dev_pm_opp_get_power(struct dev_pm_opp *opp) +{ + unsigned long opp_power = 0; + int i; + + if (IS_ERR_OR_NULL(opp)) { + pr_err("%s: Invalid parameters\n", __func__); + return 0; + } + for (i = 0; i < opp->opp_table->regulator_count; i++) + opp_power += opp->supplies[i].u_watt; + + return opp_power; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_get_power); + +/** * dev_pm_opp_get_freq() - Gets the frequency corresponding to an available opp * @opp: opp for which frequency has to be returned for * diff --git a/drivers/opp/debugfs.c b/drivers/opp/debugfs.c index 596c185b5dda..3fcc1f97f2d1 100644 --- a/drivers/opp/debugfs.c +++ b/drivers/opp/debugfs.c @@ -10,6 +10,7 @@ #include <linux/debugfs.h> #include <linux/device.h> #include <linux/err.h> +#include <linux/of.h> #include <linux/init.h> #include <linux/limits.h> #include <linux/slab.h> @@ -99,6 +100,9 @@ static void opp_debug_create_supplies(struct dev_pm_opp *opp, debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->supplies[i].u_amp); + + debugfs_create_ulong("u_watt", S_IRUGO, d, + &opp->supplies[i].u_watt); } } @@ -131,9 +135,13 @@ void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend); debugfs_create_u32("performance_state", S_IRUGO, d, &opp->pstate); debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate); + debugfs_create_u32("level", S_IRUGO, d, &opp->level); debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, &opp->clock_latency_ns); + opp->of_name = of_node_full_name(opp->np); + debugfs_create_str("of_name", S_IRUGO, d, (char **)&opp->of_name); + opp_debug_create_supplies(opp, opp_table, d); opp_debug_create_bw(opp, opp_table, d); diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 2f40afa4e65c..440ab5a03df9 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -575,8 +575,9 @@ static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table, static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev, struct opp_table *opp_table) { - u32 *microvolt, *microamp = NULL; - int supplies = opp_table->regulator_count, vcount, icount, ret, i, j; + u32 *microvolt, *microamp = NULL, *microwatt = NULL; + int supplies = opp_table->regulator_count; + int vcount, icount, pcount, ret, i, j; struct property *prop = NULL; char name[NAME_MAX]; @@ -688,6 +689,43 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev, } } + /* Search for "opp-microwatt" */ + sprintf(name, "opp-microwatt"); + prop = of_find_property(opp->np, name, NULL); + + if (prop) { + pcount = of_property_count_u32_elems(opp->np, name); + if (pcount < 0) { + dev_err(dev, "%s: Invalid %s property (%d)\n", __func__, + name, pcount); + ret = pcount; + goto free_microamp; + } + + if (pcount != supplies) { + dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n", + __func__, name, pcount, supplies); + ret = -EINVAL; + goto free_microamp; + } + + microwatt = kmalloc_array(pcount, sizeof(*microwatt), + GFP_KERNEL); + if (!microwatt) { + ret = -EINVAL; + goto free_microamp; + } + + ret = of_property_read_u32_array(opp->np, name, microwatt, + pcount); + if (ret) { + dev_err(dev, "%s: error parsing %s: %d\n", __func__, + name, ret); + ret = -EINVAL; + goto free_microwatt; + } + } + for (i = 0, j = 0; i < supplies; i++) { opp->supplies[i].u_volt = microvolt[j++]; @@ -701,8 +739,13 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev, if (microamp) opp->supplies[i].u_amp = microamp[i]; + + if (microwatt) + opp->supplies[i].u_watt = microwatt[i]; } +free_microwatt: + kfree(microwatt); free_microamp: kfree(microamp); free_microvolt: @@ -1397,6 +1440,38 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_of_node); /* * Callback function provided to the Energy Model framework upon registration. + * It provides the power used by @dev at @kHz if it is the frequency of an + * existing OPP, or at the frequency of the first OPP above @kHz otherwise + * (see dev_pm_opp_find_freq_ceil()). This function updates @kHz to the ceiled + * frequency and @mW to the associated power. + * + * Returns 0 on success or a proper -EINVAL value in case of error. + */ +static int __maybe_unused +_get_dt_power(unsigned long *mW, unsigned long *kHz, struct device *dev) +{ + struct dev_pm_opp *opp; + unsigned long opp_freq, opp_power; + + /* Find the right frequency and related OPP */ + opp_freq = *kHz * 1000; + opp = dev_pm_opp_find_freq_ceil(dev, &opp_freq); + if (IS_ERR(opp)) + return -EINVAL; + + opp_power = dev_pm_opp_get_power(opp); + dev_pm_opp_put(opp); + if (!opp_power) + return -EINVAL; + + *kHz = opp_freq / 1000; + *mW = opp_power / 1000; + + return 0; +} + +/* + * Callback function provided to the Energy Model framework upon registration. * This computes the power estimated by @dev at @kHz if it is the frequency * of an existing OPP, or at the frequency of the first OPP above @kHz otherwise * (see dev_pm_opp_find_freq_ceil()). This function updates @kHz to the ceiled @@ -1445,6 +1520,24 @@ static int __maybe_unused _get_power(unsigned long *mW, unsigned long *kHz, return 0; } +static bool _of_has_opp_microwatt_property(struct device *dev) +{ + unsigned long power, freq = 0; + struct dev_pm_opp *opp; + + /* Check if at least one OPP has needed property */ + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + if (IS_ERR(opp)) + return false; + + power = dev_pm_opp_get_power(opp); + dev_pm_opp_put(opp); + if (!power) + return false; + + return true; +} + /** * dev_pm_opp_of_register_em() - Attempt to register an Energy Model * @dev : Device for which an Energy Model has to be registered @@ -1458,7 +1551,7 @@ static int __maybe_unused _get_power(unsigned long *mW, unsigned long *kHz, */ int dev_pm_opp_of_register_em(struct device *dev, struct cpumask *cpus) { - struct em_data_callback em_cb = EM_DATA_CB(_get_power); + struct em_data_callback em_cb; struct device_node *np; int ret, nr_opp; u32 cap; @@ -1474,6 +1567,12 @@ int dev_pm_opp_of_register_em(struct device *dev, struct cpumask *cpus) goto failed; } + /* First, try to find more precised Energy Model in DT */ + if (_of_has_opp_microwatt_property(dev)) { + EM_SET_ACTIVE_POWER_CB(em_cb, _get_dt_power); + goto register_em; + } + np = of_node_get(dev->of_node); if (!np) { ret = -EINVAL; @@ -1495,6 +1594,9 @@ int dev_pm_opp_of_register_em(struct device *dev, struct cpumask *cpus) goto failed; } + EM_SET_ACTIVE_POWER_CB(em_cb, _get_power); + +register_em: ret = em_dev_register_perf_domain(dev, nr_opp, &em_cb, cpus, true); if (ret) goto failed; diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 407c3bfe51d9..45e3a55239a1 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -96,6 +96,7 @@ struct dev_pm_opp { #ifdef CONFIG_DEBUG_FS struct dentry *dentry; + const char *of_name; #endif }; |