diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2019-02-13 12:27:23 +0100 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2019-02-13 12:27:23 +0100 |
commit | b53c7348caf4de8ee447f9e6c2a1eb480199c042 (patch) | |
tree | 7bef5b2c91add03583fe84ba812a17eef2efb078 /drivers | |
parent | 263abfe74b5f1058887b14e7668b0d737e040e42 (diff) | |
parent | af816ddfbdf049dfce251513af4b5dc903c9764c (diff) | |
download | linux-b53c7348caf4de8ee447f9e6c2a1eb480199c042.tar.bz2 |
Merge branch 'cpufreq/arm/linux-next' of git://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm into pm-cpufreq
Pull ARM cpufreq driver updates for v5.1 from Viresh Kumar:
"This pull request contains following changes:
- New Armada 8k cpufreq driver (Gregory CLEMENT).
- qcom driver cleanups (Amit Kucheria, Taniya Das, Yangtao Li).
- s5pv210 driver cleanup (Paweł Chmiel).
- tegra driver cleanup (Yangtao Li).
- Minor update to MAINTAINERS file (Baruch Siach)."
* 'cpufreq/arm/linux-next' of git://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm:
cpufreq: qcom-hw: Register an Energy Model
cpufreq: qcom: Read voltage LUT and populate OPP
cpufreq: qcom-hw: Move to device_initcall
cpufreq: tegra124: add missing of_node_put()
cpufreq: qcom-kryo: make some variables static
MAINTAINERS: Update the active pm tree for ARM
cpufreq: ap806: add cpufreq driver for Armada 8K
MAINTAINERS: add new entries for Armada 8K cpufreq driver
cpufreq: s5pv210: Defer probe if getting regulators fail
MAINTAINERS: use common indentation
PM / OPP: Introduce a power estimation helper
PM / OPP: Remove unused parameter of _generic_set_opp_clk_only()
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/cpufreq/Kconfig.arm | 11 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 1 | ||||
-rw-r--r-- | drivers/cpufreq/armada-8k-cpufreq.c | 204 | ||||
-rw-r--r-- | drivers/cpufreq/qcom-cpufreq-hw.c | 50 | ||||
-rw-r--r-- | drivers/cpufreq/qcom-cpufreq-kryo.c | 2 | ||||
-rw-r--r-- | drivers/cpufreq/s5pv210-cpufreq.c | 67 | ||||
-rw-r--r-- | drivers/cpufreq/tegra124-cpufreq.c | 2 | ||||
-rw-r--r-- | drivers/opp/core.c | 11 | ||||
-rw-r--r-- | drivers/opp/of.c | 99 |
9 files changed, 411 insertions, 36 deletions
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index ca8567c3152c..f08bb0075316 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -25,6 +25,17 @@ config ARM_ARMADA_37XX_CPUFREQ This adds the CPUFreq driver support for Marvell Armada 37xx SoCs. The Armada 37xx PMU supports 4 frequency and VDD levels. +config ARM_ARMADA_8K_CPUFREQ + tristate "Armada 8K CPUFreq driver" + depends on ARCH_MVEBU && CPUFREQ_DT + help + This enables the CPUFreq driver support for Marvell + Armada8k SOCs. + Armada8K device has the AP806 which supports scaling + to any full integer divider. + + If in doubt, say N. + # big LITTLE core layer and glue drivers config ARM_BIG_LITTLE_CPUFREQ tristate "Generic ARM big LITTLE CPUfreq driver" diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 08c071be2491..689b26c6f949 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_X86_SFI_CPUFREQ) += sfi-cpufreq.o obj-$(CONFIG_ARM_BIG_LITTLE_CPUFREQ) += arm_big_little.o obj-$(CONFIG_ARM_ARMADA_37XX_CPUFREQ) += armada-37xx-cpufreq.o +obj-$(CONFIG_ARM_ARMADA_8K_CPUFREQ) += armada-8k-cpufreq.o obj-$(CONFIG_ARM_BRCMSTB_AVS_CPUFREQ) += brcmstb-avs-cpufreq.o obj-$(CONFIG_ACPI_CPPC_CPUFREQ) += cppc_cpufreq.o obj-$(CONFIG_ARCH_DAVINCI) += davinci-cpufreq.o diff --git a/drivers/cpufreq/armada-8k-cpufreq.c b/drivers/cpufreq/armada-8k-cpufreq.c new file mode 100644 index 000000000000..8a5ddb93fc58 --- /dev/null +++ b/drivers/cpufreq/armada-8k-cpufreq.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * CPUFreq support for Armada 8K + * + * Copyright (C) 2018 Marvell + * + * Omri Itach <omrii@marvell.com> + * Gregory Clement <gregory.clement@bootlin.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/clk.h> +#include <linux/cpu.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/slab.h> + +/* + * Setup the opps list with the divider for the max frequency, that + * will be filled at runtime. + */ +static const int opps_div[] __initconst = {1, 2, 3, 4}; + +static struct platform_device *armada_8k_pdev; + +struct freq_table { + struct device *cpu_dev; + unsigned int freq[ARRAY_SIZE(opps_div)]; +}; + +/* If the CPUs share the same clock, then they are in the same cluster. */ +static void __init armada_8k_get_sharing_cpus(struct clk *cur_clk, + struct cpumask *cpumask) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct device *cpu_dev; + struct clk *clk; + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_warn("Failed to get cpu%d device\n", cpu); + continue; + } + + clk = clk_get(cpu_dev, 0); + if (IS_ERR(clk)) { + pr_warn("Cannot get clock for CPU %d\n", cpu); + } else { + if (clk_is_match(clk, cur_clk)) + cpumask_set_cpu(cpu, cpumask); + + clk_put(clk); + } + } +} + +static int __init armada_8k_add_opp(struct clk *clk, struct device *cpu_dev, + struct freq_table *freq_tables, + int opps_index) +{ + unsigned int cur_frequency; + unsigned int freq; + int i, ret; + + /* Get nominal (current) CPU frequency. */ + cur_frequency = clk_get_rate(clk); + if (!cur_frequency) { + dev_err(cpu_dev, "Failed to get clock rate for this CPU\n"); + return -EINVAL; + } + + freq_tables[opps_index].cpu_dev = cpu_dev; + + for (i = 0; i < ARRAY_SIZE(opps_div); i++) { + freq = cur_frequency / opps_div[i]; + + ret = dev_pm_opp_add(cpu_dev, freq, 0); + if (ret) + return ret; + + freq_tables[opps_index].freq[i] = freq; + } + + return 0; +} + +static void armada_8k_cpufreq_free_table(struct freq_table *freq_tables) +{ + int opps_index, nb_cpus = num_possible_cpus(); + + for (opps_index = 0 ; opps_index <= nb_cpus; opps_index++) { + int i; + + /* If cpu_dev is NULL then we reached the end of the array */ + if (!freq_tables[opps_index].cpu_dev) + break; + + for (i = 0; i < ARRAY_SIZE(opps_div); i++) { + /* + * A 0Hz frequency is not valid, this meant + * that it was not yet initialized so there is + * no more opp to free + */ + if (freq_tables[opps_index].freq[i] == 0) + break; + + dev_pm_opp_remove(freq_tables[opps_index].cpu_dev, + freq_tables[opps_index].freq[i]); + } + } + + kfree(freq_tables); +} + +static int __init armada_8k_cpufreq_init(void) +{ + int ret = 0, opps_index = 0, cpu, nb_cpus; + struct freq_table *freq_tables; + struct device_node *node; + struct cpumask cpus; + + node = of_find_compatible_node(NULL, NULL, "marvell,ap806-cpu-clock"); + if (!node || !of_device_is_available(node)) + return -ENODEV; + + nb_cpus = num_possible_cpus(); + freq_tables = kcalloc(nb_cpus, sizeof(*freq_tables), GFP_KERNEL); + cpumask_copy(&cpus, cpu_possible_mask); + + /* + * For each CPU, this loop registers the operating points + * supported (which are the nominal CPU frequency and full integer + * divisions of it). + */ + for_each_cpu(cpu, &cpus) { + struct cpumask shared_cpus; + struct device *cpu_dev; + struct clk *clk; + + cpu_dev = get_cpu_device(cpu); + + if (!cpu_dev) { + pr_err("Cannot get CPU %d\n", cpu); + continue; + } + + clk = clk_get(cpu_dev, 0); + + if (IS_ERR(clk)) { + pr_err("Cannot get clock for CPU %d\n", cpu); + ret = PTR_ERR(clk); + goto remove_opp; + } + + ret = armada_8k_add_opp(clk, cpu_dev, freq_tables, opps_index); + if (ret) { + clk_put(clk); + goto remove_opp; + } + + opps_index++; + cpumask_clear(&shared_cpus); + armada_8k_get_sharing_cpus(clk, &shared_cpus); + dev_pm_opp_set_sharing_cpus(cpu_dev, &shared_cpus); + cpumask_andnot(&cpus, &cpus, &shared_cpus); + clk_put(clk); + } + + armada_8k_pdev = platform_device_register_simple("cpufreq-dt", -1, + NULL, 0); + ret = PTR_ERR_OR_ZERO(armada_8k_pdev); + if (ret) + goto remove_opp; + + platform_set_drvdata(armada_8k_pdev, freq_tables); + + return 0; + +remove_opp: + armada_8k_cpufreq_free_table(freq_tables); + return ret; +} +module_init(armada_8k_cpufreq_init); + +static void __exit armada_8k_cpufreq_exit(void) +{ + struct freq_table *freq_tables = platform_get_drvdata(armada_8k_pdev); + + platform_device_unregister(armada_8k_pdev); + armada_8k_cpufreq_free_table(freq_tables); +} +module_exit(armada_8k_cpufreq_exit); + +MODULE_AUTHOR("Gregory Clement <gregory.clement@bootlin.com>"); +MODULE_DESCRIPTION("Armada 8K cpufreq driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index c88b51304d89..4b0b50403901 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -10,18 +10,21 @@ #include <linux/module.h> #include <linux/of_address.h> #include <linux/of_platform.h> +#include <linux/pm_opp.h> #include <linux/slab.h> #define LUT_MAX_ENTRIES 40U #define LUT_SRC GENMASK(31, 30) #define LUT_L_VAL GENMASK(7, 0) #define LUT_CORE_COUNT GENMASK(18, 16) +#define LUT_VOLT GENMASK(11, 0) #define LUT_ROW_SIZE 32 #define CLK_HW_DIV 2 /* Register offsets */ #define REG_ENABLE 0x0 -#define REG_LUT_TABLE 0x110 +#define REG_FREQ_LUT 0x110 +#define REG_VOLT_LUT 0x114 #define REG_PERF_STATE 0x920 static unsigned long cpu_hw_rate, xo_rate; @@ -70,11 +73,12 @@ static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, return policy->freq_table[index].frequency; } -static int qcom_cpufreq_hw_read_lut(struct device *dev, +static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, struct cpufreq_policy *policy, void __iomem *base) { u32 data, src, lval, i, core_count, prev_cc = 0, prev_freq = 0, freq; + u32 volt; unsigned int max_cores = cpumask_weight(policy->cpus); struct cpufreq_frequency_table *table; @@ -83,23 +87,28 @@ static int qcom_cpufreq_hw_read_lut(struct device *dev, return -ENOMEM; for (i = 0; i < LUT_MAX_ENTRIES; i++) { - data = readl_relaxed(base + REG_LUT_TABLE + i * LUT_ROW_SIZE); + data = readl_relaxed(base + REG_FREQ_LUT + + i * LUT_ROW_SIZE); src = FIELD_GET(LUT_SRC, data); lval = FIELD_GET(LUT_L_VAL, data); core_count = FIELD_GET(LUT_CORE_COUNT, data); + data = readl_relaxed(base + REG_VOLT_LUT + + i * LUT_ROW_SIZE); + volt = FIELD_GET(LUT_VOLT, data) * 1000; + if (src) freq = xo_rate * lval / 1000; else freq = cpu_hw_rate / 1000; - /* Ignore boosts in the middle of the table */ - if (core_count != max_cores) { - table[i].frequency = CPUFREQ_ENTRY_INVALID; - } else { + if (freq != prev_freq && core_count == max_cores) { table[i].frequency = freq; - dev_dbg(dev, "index=%d freq=%d, core_count %d\n", i, + dev_pm_opp_add(cpu_dev, freq * 1000, volt); + dev_dbg(cpu_dev, "index=%d freq=%d, core_count %d\n", i, freq, core_count); + } else { + table[i].frequency = CPUFREQ_ENTRY_INVALID; } /* @@ -116,6 +125,7 @@ static int qcom_cpufreq_hw_read_lut(struct device *dev, if (prev_cc != max_cores) { prev->frequency = prev_freq; prev->flags = CPUFREQ_BOOST_FREQ; + dev_pm_opp_add(cpu_dev, prev_freq * 1000, volt); } break; @@ -127,6 +137,7 @@ static int qcom_cpufreq_hw_read_lut(struct device *dev, table[i].frequency = CPUFREQ_TABLE_END; policy->freq_table = table; + dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); return 0; } @@ -159,10 +170,18 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) struct device *dev = &global_pdev->dev; struct of_phandle_args args; struct device_node *cpu_np; + struct device *cpu_dev; struct resource *res; void __iomem *base; int ret, index; + cpu_dev = get_cpu_device(policy->cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + policy->cpu); + return -ENODEV; + } + cpu_np = of_cpu_device_node_get(policy->cpu); if (!cpu_np) return -EINVAL; @@ -199,12 +218,21 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) policy->driver_data = base + REG_PERF_STATE; - ret = qcom_cpufreq_hw_read_lut(dev, policy, base); + ret = qcom_cpufreq_hw_read_lut(cpu_dev, policy, base); if (ret) { dev_err(dev, "Domain-%d failed to read LUT\n", index); goto error; } + ret = dev_pm_opp_get_opp_count(cpu_dev); + if (ret <= 0) { + dev_err(cpu_dev, "Failed to add OPPs\n"); + ret = -ENODEV; + goto error; + } + + dev_pm_opp_of_register_em(policy->cpus); + policy->fast_switch_possible = true; return 0; @@ -215,8 +243,10 @@ error: static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) { + struct device *cpu_dev = get_cpu_device(policy->cpu); void __iomem *base = policy->driver_data - REG_PERF_STATE; + dev_pm_opp_remove_all_dynamic(cpu_dev); kfree(policy->freq_table); devm_iounmap(&global_pdev->dev, base); @@ -297,7 +327,7 @@ static int __init qcom_cpufreq_hw_init(void) { return platform_driver_register(&qcom_cpufreq_hw_driver); } -subsys_initcall(qcom_cpufreq_hw_init); +device_initcall(qcom_cpufreq_hw_init); static void __exit qcom_cpufreq_hw_exit(void) { diff --git a/drivers/cpufreq/qcom-cpufreq-kryo.c b/drivers/cpufreq/qcom-cpufreq-kryo.c index 2a3675c24032..1c8583cc06a2 100644 --- a/drivers/cpufreq/qcom-cpufreq-kryo.c +++ b/drivers/cpufreq/qcom-cpufreq-kryo.c @@ -42,7 +42,7 @@ enum _msm8996_version { NUM_OF_MSM8996_VERSIONS, }; -struct platform_device *cpufreq_dt_pdev, *kryo_cpufreq_pdev; +static struct platform_device *cpufreq_dt_pdev, *kryo_cpufreq_pdev; static enum _msm8996_version qcom_cpufreq_kryo_get_msm_id(void) { diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index dbecd7667db2..5b4289460bc9 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -584,7 +584,7 @@ static struct notifier_block s5pv210_cpufreq_reboot_notifier = { static int s5pv210_cpufreq_probe(struct platform_device *pdev) { struct device_node *np; - int id; + int id, result = 0; /* * HACK: This is a temporary workaround to get access to clock @@ -594,18 +594,39 @@ static int s5pv210_cpufreq_probe(struct platform_device *pdev) * this whole driver as soon as S5PV210 gets migrated to use * cpufreq-dt driver. */ + arm_regulator = regulator_get(NULL, "vddarm"); + if (IS_ERR(arm_regulator)) { + if (PTR_ERR(arm_regulator) == -EPROBE_DEFER) + pr_debug("vddarm regulator not ready, defer\n"); + else + pr_err("failed to get regulator vddarm\n"); + return PTR_ERR(arm_regulator); + } + + int_regulator = regulator_get(NULL, "vddint"); + if (IS_ERR(int_regulator)) { + if (PTR_ERR(int_regulator) == -EPROBE_DEFER) + pr_debug("vddint regulator not ready, defer\n"); + else + pr_err("failed to get regulator vddint\n"); + result = PTR_ERR(int_regulator); + goto err_int_regulator; + } + np = of_find_compatible_node(NULL, NULL, "samsung,s5pv210-clock"); if (!np) { pr_err("%s: failed to find clock controller DT node\n", __func__); - return -ENODEV; + result = -ENODEV; + goto err_clock; } clk_base = of_iomap(np, 0); of_node_put(np); if (!clk_base) { pr_err("%s: failed to map clock registers\n", __func__); - return -EFAULT; + result = -EFAULT; + goto err_clock; } for_each_compatible_node(np, NULL, "samsung,s5pv210-dmc") { @@ -614,7 +635,8 @@ static int s5pv210_cpufreq_probe(struct platform_device *pdev) pr_err("%s: failed to get alias of dmc node '%pOFn'\n", __func__, np); of_node_put(np); - return id; + result = id; + goto err_clk_base; } dmc_base[id] = of_iomap(np, 0); @@ -622,33 +644,40 @@ static int s5pv210_cpufreq_probe(struct platform_device *pdev) pr_err("%s: failed to map dmc%d registers\n", __func__, id); of_node_put(np); - return -EFAULT; + result = -EFAULT; + goto err_dmc; } } for (id = 0; id < ARRAY_SIZE(dmc_base); ++id) { if (!dmc_base[id]) { pr_err("%s: failed to find dmc%d node\n", __func__, id); - return -ENODEV; + result = -ENODEV; + goto err_dmc; } } - arm_regulator = regulator_get(NULL, "vddarm"); - if (IS_ERR(arm_regulator)) { - pr_err("failed to get regulator vddarm\n"); - return PTR_ERR(arm_regulator); - } - - int_regulator = regulator_get(NULL, "vddint"); - if (IS_ERR(int_regulator)) { - pr_err("failed to get regulator vddint\n"); - regulator_put(arm_regulator); - return PTR_ERR(int_regulator); - } - register_reboot_notifier(&s5pv210_cpufreq_reboot_notifier); return cpufreq_register_driver(&s5pv210_driver); + +err_dmc: + for (id = 0; id < ARRAY_SIZE(dmc_base); ++id) + if (dmc_base[id]) { + iounmap(dmc_base[id]); + dmc_base[id] = NULL; + } + +err_clk_base: + iounmap(clk_base); + +err_clock: + regulator_put(int_regulator); + +err_int_regulator: + regulator_put(arm_regulator); + + return result; } static struct platform_driver s5pv210_cpufreq_platdrv = { diff --git a/drivers/cpufreq/tegra124-cpufreq.c b/drivers/cpufreq/tegra124-cpufreq.c index 43530254201a..4bb154f6c54c 100644 --- a/drivers/cpufreq/tegra124-cpufreq.c +++ b/drivers/cpufreq/tegra124-cpufreq.c @@ -134,6 +134,8 @@ static int tegra124_cpufreq_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); + of_node_put(np); + return 0; out_switch_to_pllx: diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 18f1639dbc4a..4e00301060cf 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -533,9 +533,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, return ret; } -static inline int -_generic_set_opp_clk_only(struct device *dev, struct clk *clk, - unsigned long old_freq, unsigned long freq) +static inline int _generic_set_opp_clk_only(struct device *dev, struct clk *clk, + unsigned long freq) { int ret; @@ -572,7 +571,7 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table, } /* Change frequency */ - ret = _generic_set_opp_clk_only(dev, opp_table->clk, old_freq, freq); + ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); if (ret) goto restore_voltage; @@ -586,7 +585,7 @@ static int _generic_set_opp_regulator(const struct opp_table *opp_table, return 0; restore_freq: - if (_generic_set_opp_clk_only(dev, opp_table->clk, freq, old_freq)) + if (_generic_set_opp_clk_only(dev, opp_table->clk, old_freq)) dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", __func__, old_freq); restore_voltage: @@ -759,7 +758,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) opp->supplies); } else { /* Only frequency scaling */ - ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq); + ret = _generic_set_opp_clk_only(dev, clk, freq); } /* Scaling down? Configure required OPPs after frequency */ diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 06f0f632ec47..cd58959e5158 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -20,6 +20,7 @@ #include <linux/pm_domain.h> #include <linux/slab.h> #include <linux/export.h> +#include <linux/energy_model.h> #include "opp.h" @@ -1047,3 +1048,101 @@ struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp) return of_node_get(opp->np); } EXPORT_SYMBOL_GPL(dev_pm_opp_get_of_node); + +/* + * Callback function provided to the Energy Model framework upon registration. + * This computes the power estimated by @CPU 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. The power is estimated as + * P = C * V^2 * f with C being the CPU's capacitance and V and f respectively + * the voltage and frequency of the OPP. + * + * Returns -ENODEV if the CPU device cannot be found, -EINVAL if the power + * calculation failed because of missing parameters, 0 otherwise. + */ +static int __maybe_unused _get_cpu_power(unsigned long *mW, unsigned long *kHz, + int cpu) +{ + struct device *cpu_dev; + struct dev_pm_opp *opp; + struct device_node *np; + unsigned long mV, Hz; + u32 cap; + u64 tmp; + int ret; + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) + return -ENODEV; + + np = of_node_get(cpu_dev->of_node); + if (!np) + return -EINVAL; + + ret = of_property_read_u32(np, "dynamic-power-coefficient", &cap); + of_node_put(np); + if (ret) + return -EINVAL; + + Hz = *kHz * 1000; + opp = dev_pm_opp_find_freq_ceil(cpu_dev, &Hz); + if (IS_ERR(opp)) + return -EINVAL; + + mV = dev_pm_opp_get_voltage(opp) / 1000; + dev_pm_opp_put(opp); + if (!mV) + return -EINVAL; + + tmp = (u64)cap * mV * mV * (Hz / 1000000); + do_div(tmp, 1000000000); + + *mW = (unsigned long)tmp; + *kHz = Hz / 1000; + + return 0; +} + +/** + * dev_pm_opp_of_register_em() - Attempt to register an Energy Model + * @cpus : CPUs for which an Energy Model has to be registered + * + * This checks whether the "dynamic-power-coefficient" devicetree property has + * been specified, and tries to register an Energy Model with it if it has. + */ +void dev_pm_opp_of_register_em(struct cpumask *cpus) +{ + struct em_data_callback em_cb = EM_DATA_CB(_get_cpu_power); + int ret, nr_opp, cpu = cpumask_first(cpus); + struct device *cpu_dev; + struct device_node *np; + u32 cap; + + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) + return; + + nr_opp = dev_pm_opp_get_opp_count(cpu_dev); + if (nr_opp <= 0) + return; + + np = of_node_get(cpu_dev->of_node); + if (!np) + return; + + /* + * Register an EM only if the 'dynamic-power-coefficient' property is + * set in devicetree. It is assumed the voltage values are known if that + * property is set since it is useless otherwise. If voltages are not + * known, just let the EM registration fail with an error to alert the + * user about the inconsistent configuration. + */ + ret = of_property_read_u32(np, "dynamic-power-coefficient", &cap); + of_node_put(np); + if (ret || !cap) + return; + + em_register_perf_domain(cpus, nr_opp, &em_cb); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_of_register_em); |