diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/cpufreq/Kconfig.arm | 88 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 9 | ||||
-rw-r--r-- | drivers/cpufreq/arm_big_little.c | 23 | ||||
-rw-r--r-- | drivers/cpufreq/armada-37xx-cpufreq.c | 241 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq-dt-platdev.c | 8 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq-dt.c | 27 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq.c | 55 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq_stats.c | 3 | ||||
-rw-r--r-- | drivers/cpufreq/imx6q-cpufreq.c | 171 | ||||
-rw-r--r-- | drivers/cpufreq/intel_pstate.c | 14 | ||||
-rw-r--r-- | drivers/cpufreq/longhaul.c | 2 | ||||
-rw-r--r-- | drivers/cpufreq/mediatek-cpufreq.c | 23 | ||||
-rw-r--r-- | drivers/cpufreq/mvebu-cpufreq.c | 16 | ||||
-rw-r--r-- | drivers/cpufreq/powernv-cpufreq.c | 143 | ||||
-rw-r--r-- | drivers/cpufreq/qoriq-cpufreq.c | 14 | ||||
-rw-r--r-- | drivers/cpufreq/scpi-cpufreq.c | 193 | ||||
-rw-r--r-- | drivers/cpufreq/ti-cpufreq.c | 51 | ||||
-rw-r--r-- | drivers/firmware/psci_checker.c | 46 | ||||
-rw-r--r-- | drivers/opp/Makefile | 1 | ||||
-rw-r--r-- | drivers/opp/ti-opp-supply.c | 425 | ||||
-rw-r--r-- | drivers/thermal/cpu_cooling.c | 201 |
21 files changed, 1253 insertions, 501 deletions
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index bdce4488ded1..3a88e33b0cfe 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -2,6 +2,29 @@ # ARM CPU Frequency scaling drivers # +config ACPI_CPPC_CPUFREQ + tristate "CPUFreq driver based on the ACPI CPPC spec" + depends on ACPI_PROCESSOR + select ACPI_CPPC_LIB + help + This adds a CPUFreq driver which uses CPPC methods + as described in the ACPIv5.1 spec. CPPC stands for + Collaborative Processor Performance Controls. It + is based on an abstract continuous scale of CPU + performance values which allows the remote power + processor to flexibly optimize for power and + performance. CPPC relies on power management firmware + support for its operation. + + If in doubt, say N. + +config ARM_ARMADA_37XX_CPUFREQ + tristate "Armada 37xx CPUFreq support" + depends on ARCH_MVEBU + help + This adds the CPUFreq driver support for Marvell Armada 37xx SoCs. + The Armada 37xx PMU supports 4 frequency and VDD levels. + # big LITTLE core layer and glue drivers config ARM_BIG_LITTLE_CPUFREQ tristate "Generic ARM big LITTLE CPUfreq driver" @@ -12,6 +35,30 @@ config ARM_BIG_LITTLE_CPUFREQ help This enables the Generic CPUfreq driver for ARM big.LITTLE platforms. +config ARM_DT_BL_CPUFREQ + tristate "Generic probing via DT for ARM big LITTLE CPUfreq driver" + depends on ARM_BIG_LITTLE_CPUFREQ && OF + help + This enables probing via DT for Generic CPUfreq driver for ARM + big.LITTLE platform. This gets frequency tables from DT. + +config ARM_SCPI_CPUFREQ + tristate "SCPI based CPUfreq driver" + depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI + help + This adds the CPUfreq driver support for ARM big.LITTLE platforms + using SCPI protocol for CPU power management. + + This driver uses SCPI Message Protocol driver to interact with the + firmware providing the CPU DVFS functionality. + +config ARM_VEXPRESS_SPC_CPUFREQ + tristate "Versatile Express SPC based CPUfreq driver" + depends on ARM_BIG_LITTLE_CPUFREQ && ARCH_VEXPRESS_SPC + help + This add the CPUfreq driver support for Versatile Express + big.LITTLE platforms using SPC for power management. + config ARM_BRCMSTB_AVS_CPUFREQ tristate "Broadcom STB AVS CPUfreq driver" depends on ARCH_BRCMSTB || COMPILE_TEST @@ -33,20 +80,6 @@ config ARM_BRCMSTB_AVS_CPUFREQ_DEBUG If in doubt, say N. -config ARM_DT_BL_CPUFREQ - tristate "Generic probing via DT for ARM big LITTLE CPUfreq driver" - depends on ARM_BIG_LITTLE_CPUFREQ && OF - help - This enables probing via DT for Generic CPUfreq driver for ARM - big.LITTLE platform. This gets frequency tables from DT. - -config ARM_VEXPRESS_SPC_CPUFREQ - tristate "Versatile Express SPC based CPUfreq driver" - depends on ARM_BIG_LITTLE_CPUFREQ && ARCH_VEXPRESS_SPC - help - This add the CPUfreq driver support for Versatile Express - big.LITTLE platforms using SPC for power management. - config ARM_EXYNOS5440_CPUFREQ tristate "SAMSUNG EXYNOS5440" depends on SOC_EXYNOS5440 @@ -205,16 +238,6 @@ config ARM_SA1100_CPUFREQ config ARM_SA1110_CPUFREQ bool -config ARM_SCPI_CPUFREQ - tristate "SCPI based CPUfreq driver" - depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI - help - This adds the CPUfreq driver support for ARM big.LITTLE platforms - using SCPI protocol for CPU power management. - - This driver uses SCPI Message Protocol driver to interact with the - firmware providing the CPU DVFS functionality. - config ARM_SPEAR_CPUFREQ bool "SPEAr CPUFreq support" depends on PLAT_SPEAR @@ -275,20 +298,3 @@ config ARM_PXA2xx_CPUFREQ This add the CPUFreq driver support for Intel PXA2xx SOCs. If in doubt, say N. - -config ACPI_CPPC_CPUFREQ - tristate "CPUFreq driver based on the ACPI CPPC spec" - depends on ACPI_PROCESSOR - select ACPI_CPPC_LIB - default n - help - This adds a CPUFreq driver which uses CPPC methods - as described in the ACPIv5.1 spec. CPPC stands for - Collaborative Processor Performance Controls. It - is based on an abstract continuous scale of CPU - performance values which allows the remote power - processor to flexibly optimize for power and - performance. CPPC relies on power management firmware - support for its operation. - - If in doubt, say N. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 812f9e0d01a3..e07715ce8844 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -52,23 +52,26 @@ obj-$(CONFIG_ARM_BIG_LITTLE_CPUFREQ) += arm_big_little.o # LITTLE drivers, so that it is probed last. obj-$(CONFIG_ARM_DT_BL_CPUFREQ) += arm_big_little_dt.o +obj-$(CONFIG_ARM_ARMADA_37XX_CPUFREQ) += armada-37xx-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 obj-$(CONFIG_ARM_EXYNOS5440_CPUFREQ) += exynos5440-cpufreq.o obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o obj-$(CONFIG_ARM_MEDIATEK_CPUFREQ) += mediatek-cpufreq.o +obj-$(CONFIG_MACH_MVEBU_V7) += mvebu-cpufreq.o obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o -obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o -obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o obj-$(CONFIG_ARM_S3C2412_CPUFREQ) += s3c2412-cpufreq.o obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o obj-$(CONFIG_ARM_S3C2440_CPUFREQ) += s3c2440-cpufreq.o obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o +obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o +obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o @@ -81,8 +84,6 @@ obj-$(CONFIG_ARM_TEGRA124_CPUFREQ) += tegra124-cpufreq.o obj-$(CONFIG_ARM_TEGRA186_CPUFREQ) += tegra186-cpufreq.o obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-cpufreq.o obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o -obj-$(CONFIG_ACPI_CPPC_CPUFREQ) += cppc_cpufreq.o -obj-$(CONFIG_MACH_MVEBU_V7) += mvebu-cpufreq.o ################################################################################## diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c index 65ec5f01aa8d..c56b57dcfda5 100644 --- a/drivers/cpufreq/arm_big_little.c +++ b/drivers/cpufreq/arm_big_little.c @@ -526,34 +526,13 @@ static int bL_cpufreq_exit(struct cpufreq_policy *policy) static void bL_cpufreq_ready(struct cpufreq_policy *policy) { - struct device *cpu_dev = get_cpu_device(policy->cpu); int cur_cluster = cpu_to_cluster(policy->cpu); - struct device_node *np; /* Do not register a cpu_cooling device if we are in IKS mode */ if (cur_cluster >= MAX_CLUSTERS) return; - np = of_node_get(cpu_dev->of_node); - if (WARN_ON(!np)) - return; - - if (of_find_property(np, "#cooling-cells", NULL)) { - u32 power_coefficient = 0; - - of_property_read_u32(np, "dynamic-power-coefficient", - &power_coefficient); - - cdev[cur_cluster] = of_cpufreq_power_cooling_register(np, - policy, power_coefficient, NULL); - if (IS_ERR(cdev[cur_cluster])) { - dev_err(cpu_dev, - "running cpufreq without cooling device: %ld\n", - PTR_ERR(cdev[cur_cluster])); - cdev[cur_cluster] = NULL; - } - } - of_node_put(np); + cdev[cur_cluster] = of_cpufreq_cooling_register(policy); } static struct cpufreq_driver bL_cpufreq_driver = { diff --git a/drivers/cpufreq/armada-37xx-cpufreq.c b/drivers/cpufreq/armada-37xx-cpufreq.c new file mode 100644 index 000000000000..c6ebc88a7d8d --- /dev/null +++ b/drivers/cpufreq/armada-37xx-cpufreq.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * CPU frequency scaling support for Armada 37xx platform. + * + * Copyright (C) 2017 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + */ + +#include <linux/clk.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/* Power management in North Bridge register set */ +#define ARMADA_37XX_NB_L0L1 0x18 +#define ARMADA_37XX_NB_L2L3 0x1C +#define ARMADA_37XX_NB_TBG_DIV_OFF 13 +#define ARMADA_37XX_NB_TBG_DIV_MASK 0x7 +#define ARMADA_37XX_NB_CLK_SEL_OFF 11 +#define ARMADA_37XX_NB_CLK_SEL_MASK 0x1 +#define ARMADA_37XX_NB_CLK_SEL_TBG 0x1 +#define ARMADA_37XX_NB_TBG_SEL_OFF 9 +#define ARMADA_37XX_NB_TBG_SEL_MASK 0x3 +#define ARMADA_37XX_NB_VDD_SEL_OFF 6 +#define ARMADA_37XX_NB_VDD_SEL_MASK 0x3 +#define ARMADA_37XX_NB_CONFIG_SHIFT 16 +#define ARMADA_37XX_NB_DYN_MOD 0x24 +#define ARMADA_37XX_NB_CLK_SEL_EN BIT(26) +#define ARMADA_37XX_NB_TBG_EN BIT(28) +#define ARMADA_37XX_NB_DIV_EN BIT(29) +#define ARMADA_37XX_NB_VDD_EN BIT(30) +#define ARMADA_37XX_NB_DFS_EN BIT(31) +#define ARMADA_37XX_NB_CPU_LOAD 0x30 +#define ARMADA_37XX_NB_CPU_LOAD_MASK 0x3 +#define ARMADA_37XX_DVFS_LOAD_0 0 +#define ARMADA_37XX_DVFS_LOAD_1 1 +#define ARMADA_37XX_DVFS_LOAD_2 2 +#define ARMADA_37XX_DVFS_LOAD_3 3 + +/* + * On Armada 37xx the Power management manages 4 level of CPU load, + * each level can be associated with a CPU clock source, a CPU + * divider, a VDD level, etc... + */ +#define LOAD_LEVEL_NR 4 + +struct armada_37xx_dvfs { + u32 cpu_freq_max; + u8 divider[LOAD_LEVEL_NR]; +}; + +static struct armada_37xx_dvfs armada_37xx_dvfs[] = { + {.cpu_freq_max = 1200*1000*1000, .divider = {1, 2, 4, 6} }, + {.cpu_freq_max = 1000*1000*1000, .divider = {1, 2, 4, 5} }, + {.cpu_freq_max = 800*1000*1000, .divider = {1, 2, 3, 4} }, + {.cpu_freq_max = 600*1000*1000, .divider = {2, 4, 5, 6} }, +}; + +static struct armada_37xx_dvfs *armada_37xx_cpu_freq_info_get(u32 freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(armada_37xx_dvfs); i++) { + if (freq == armada_37xx_dvfs[i].cpu_freq_max) + return &armada_37xx_dvfs[i]; + } + + pr_err("Unsupported CPU frequency %d MHz\n", freq/1000000); + return NULL; +} + +/* + * Setup the four level managed by the hardware. Once the four level + * will be configured then the DVFS will be enabled. + */ +static void __init armada37xx_cpufreq_dvfs_setup(struct regmap *base, + struct clk *clk, u8 *divider) +{ + int load_lvl; + struct clk *parent; + + for (load_lvl = 0; load_lvl < LOAD_LEVEL_NR; load_lvl++) { + unsigned int reg, mask, val, offset = 0; + + if (load_lvl <= ARMADA_37XX_DVFS_LOAD_1) + reg = ARMADA_37XX_NB_L0L1; + else + reg = ARMADA_37XX_NB_L2L3; + + if (load_lvl == ARMADA_37XX_DVFS_LOAD_0 || + load_lvl == ARMADA_37XX_DVFS_LOAD_2) + offset += ARMADA_37XX_NB_CONFIG_SHIFT; + + /* Set cpu clock source, for all the level we use TBG */ + val = ARMADA_37XX_NB_CLK_SEL_TBG << ARMADA_37XX_NB_CLK_SEL_OFF; + mask = (ARMADA_37XX_NB_CLK_SEL_MASK + << ARMADA_37XX_NB_CLK_SEL_OFF); + + /* + * Set cpu divider based on the pre-computed array in + * order to have balanced step. + */ + val |= divider[load_lvl] << ARMADA_37XX_NB_TBG_DIV_OFF; + mask |= (ARMADA_37XX_NB_TBG_DIV_MASK + << ARMADA_37XX_NB_TBG_DIV_OFF); + + /* Set VDD divider which is actually the load level. */ + val |= load_lvl << ARMADA_37XX_NB_VDD_SEL_OFF; + mask |= (ARMADA_37XX_NB_VDD_SEL_MASK + << ARMADA_37XX_NB_VDD_SEL_OFF); + + val <<= offset; + mask <<= offset; + + regmap_update_bits(base, reg, mask, val); + } + + /* + * Set cpu clock source, for all the level we keep the same + * clock source that the one already configured. For this one + * we need to use the clock framework + */ + parent = clk_get_parent(clk); + clk_set_parent(clk, parent); +} + +static void __init armada37xx_cpufreq_disable_dvfs(struct regmap *base) +{ + unsigned int reg = ARMADA_37XX_NB_DYN_MOD, + mask = ARMADA_37XX_NB_DFS_EN; + + regmap_update_bits(base, reg, mask, 0); +} + +static void __init armada37xx_cpufreq_enable_dvfs(struct regmap *base) +{ + unsigned int val, reg = ARMADA_37XX_NB_CPU_LOAD, + mask = ARMADA_37XX_NB_CPU_LOAD_MASK; + + /* Start with the highest load (0) */ + val = ARMADA_37XX_DVFS_LOAD_0; + regmap_update_bits(base, reg, mask, val); + + /* Now enable DVFS for the CPUs */ + reg = ARMADA_37XX_NB_DYN_MOD; + mask = ARMADA_37XX_NB_CLK_SEL_EN | ARMADA_37XX_NB_TBG_EN | + ARMADA_37XX_NB_DIV_EN | ARMADA_37XX_NB_VDD_EN | + ARMADA_37XX_NB_DFS_EN; + + regmap_update_bits(base, reg, mask, mask); +} + +static int __init armada37xx_cpufreq_driver_init(void) +{ + struct armada_37xx_dvfs *dvfs; + struct platform_device *pdev; + unsigned int cur_frequency; + struct regmap *nb_pm_base; + struct device *cpu_dev; + int load_lvl, ret; + struct clk *clk; + + nb_pm_base = + syscon_regmap_lookup_by_compatible("marvell,armada-3700-nb-pm"); + + if (IS_ERR(nb_pm_base)) + return -ENODEV; + + /* Before doing any configuration on the DVFS first, disable it */ + armada37xx_cpufreq_disable_dvfs(nb_pm_base); + + /* + * On CPU 0 register the operating points supported (which are + * the nominal CPU frequency and full integer divisions of + * it). + */ + cpu_dev = get_cpu_device(0); + if (!cpu_dev) { + dev_err(cpu_dev, "Cannot get CPU\n"); + return -ENODEV; + } + + clk = clk_get(cpu_dev, 0); + if (IS_ERR(clk)) { + dev_err(cpu_dev, "Cannot get clock for CPU0\n"); + return PTR_ERR(clk); + } + + /* Get nominal (current) CPU frequency */ + cur_frequency = clk_get_rate(clk); + if (!cur_frequency) { + dev_err(cpu_dev, "Failed to get clock rate for CPU\n"); + return -EINVAL; + } + + dvfs = armada_37xx_cpu_freq_info_get(cur_frequency); + if (!dvfs) + return -EINVAL; + + armada37xx_cpufreq_dvfs_setup(nb_pm_base, clk, dvfs->divider); + + for (load_lvl = ARMADA_37XX_DVFS_LOAD_0; load_lvl < LOAD_LEVEL_NR; + load_lvl++) { + unsigned long freq = cur_frequency / dvfs->divider[load_lvl]; + + ret = dev_pm_opp_add(cpu_dev, freq, 0); + if (ret) { + /* clean-up the already added opp before leaving */ + while (load_lvl-- > ARMADA_37XX_DVFS_LOAD_0) { + freq = cur_frequency / dvfs->divider[load_lvl]; + dev_pm_opp_remove(cpu_dev, freq); + } + return ret; + } + } + + /* Now that everything is setup, enable the DVFS at hardware level */ + armada37xx_cpufreq_enable_dvfs(nb_pm_base); + + pdev = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); + + return PTR_ERR_OR_ZERO(pdev); +} +/* late_initcall, to guarantee the driver is loaded after A37xx clock driver */ +late_initcall(armada37xx_cpufreq_driver_init); + +MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>"); +MODULE_DESCRIPTION("Armada 37xx cpufreq driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index ecc56e26f8f6..3b585e4bfac5 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -108,6 +108,14 @@ static const struct of_device_id blacklist[] __initconst = { { .compatible = "marvell,armadaxp", }, + { .compatible = "mediatek,mt2701", }, + { .compatible = "mediatek,mt2712", }, + { .compatible = "mediatek,mt7622", }, + { .compatible = "mediatek,mt7623", }, + { .compatible = "mediatek,mt817x", }, + { .compatible = "mediatek,mt8173", }, + { .compatible = "mediatek,mt8176", }, + { .compatible = "nvidia,tegra124", }, { .compatible = "st,stih407", }, diff --git a/drivers/cpufreq/cpufreq-dt.c b/drivers/cpufreq/cpufreq-dt.c index 545946ad0752..de3d104c25d7 100644 --- a/drivers/cpufreq/cpufreq-dt.c +++ b/drivers/cpufreq/cpufreq-dt.c @@ -319,33 +319,8 @@ static int cpufreq_exit(struct cpufreq_policy *policy) static void cpufreq_ready(struct cpufreq_policy *policy) { struct private_data *priv = policy->driver_data; - struct device_node *np = of_node_get(priv->cpu_dev->of_node); - if (WARN_ON(!np)) - return; - - /* - * For now, just loading the cooling device; - * thermal DT code takes care of matching them. - */ - if (of_find_property(np, "#cooling-cells", NULL)) { - u32 power_coefficient = 0; - - of_property_read_u32(np, "dynamic-power-coefficient", - &power_coefficient); - - priv->cdev = of_cpufreq_power_cooling_register(np, - policy, power_coefficient, NULL); - if (IS_ERR(priv->cdev)) { - dev_err(priv->cpu_dev, - "running cpufreq without cooling device: %ld\n", - PTR_ERR(priv->cdev)); - - priv->cdev = NULL; - } - } - - of_node_put(np); + priv->cdev = of_cpufreq_cooling_register(policy); } static struct cpufreq_driver dt_cpufreq_driver = { diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 41d148af7748..421f318c0e66 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -601,19 +601,18 @@ static struct cpufreq_governor *find_governor(const char *str_governor) /** * cpufreq_parse_governor - parse a governor string */ -static int cpufreq_parse_governor(char *str_governor, unsigned int *policy, - struct cpufreq_governor **governor) +static int cpufreq_parse_governor(char *str_governor, + struct cpufreq_policy *policy) { - int err = -EINVAL; - if (cpufreq_driver->setpolicy) { if (!strncasecmp(str_governor, "performance", CPUFREQ_NAME_LEN)) { - *policy = CPUFREQ_POLICY_PERFORMANCE; - err = 0; - } else if (!strncasecmp(str_governor, "powersave", - CPUFREQ_NAME_LEN)) { - *policy = CPUFREQ_POLICY_POWERSAVE; - err = 0; + policy->policy = CPUFREQ_POLICY_PERFORMANCE; + return 0; + } + + if (!strncasecmp(str_governor, "powersave", CPUFREQ_NAME_LEN)) { + policy->policy = CPUFREQ_POLICY_POWERSAVE; + return 0; } } else { struct cpufreq_governor *t; @@ -621,26 +620,31 @@ static int cpufreq_parse_governor(char *str_governor, unsigned int *policy, mutex_lock(&cpufreq_governor_mutex); t = find_governor(str_governor); - - if (t == NULL) { + if (!t) { int ret; mutex_unlock(&cpufreq_governor_mutex); + ret = request_module("cpufreq_%s", str_governor); - mutex_lock(&cpufreq_governor_mutex); + if (ret) + return -EINVAL; - if (ret == 0) - t = find_governor(str_governor); - } + mutex_lock(&cpufreq_governor_mutex); - if (t != NULL) { - *governor = t; - err = 0; + t = find_governor(str_governor); } + if (t && !try_module_get(t->owner)) + t = NULL; mutex_unlock(&cpufreq_governor_mutex); + + if (t) { + policy->governor = t; + return 0; + } } - return err; + + return -EINVAL; } /** @@ -760,11 +764,14 @@ static ssize_t store_scaling_governor(struct cpufreq_policy *policy, if (ret != 1) return -EINVAL; - if (cpufreq_parse_governor(str_governor, &new_policy.policy, - &new_policy.governor)) + if (cpufreq_parse_governor(str_governor, &new_policy)) return -EINVAL; ret = cpufreq_set_policy(policy, &new_policy); + + if (new_policy.governor) + module_put(new_policy.governor->owner); + return ret ? ret : count; } @@ -1044,8 +1051,7 @@ static int cpufreq_init_policy(struct cpufreq_policy *policy) if (policy->last_policy) new_policy.policy = policy->last_policy; else - cpufreq_parse_governor(gov->name, &new_policy.policy, - NULL); + cpufreq_parse_governor(gov->name, &new_policy); } /* set default policy */ return cpufreq_set_policy(policy, &new_policy); @@ -2160,7 +2166,6 @@ void cpufreq_unregister_governor(struct cpufreq_governor *governor) mutex_lock(&cpufreq_governor_mutex); list_del(&governor->governor_list); mutex_unlock(&cpufreq_governor_mutex); - return; } EXPORT_SYMBOL_GPL(cpufreq_unregister_governor); diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c index 1e55b5790853..1572129844a5 100644 --- a/drivers/cpufreq/cpufreq_stats.c +++ b/drivers/cpufreq/cpufreq_stats.c @@ -27,7 +27,7 @@ struct cpufreq_stats { unsigned int *trans_table; }; -static int cpufreq_stats_update(struct cpufreq_stats *stats) +static void cpufreq_stats_update(struct cpufreq_stats *stats) { unsigned long long cur_time = get_jiffies_64(); @@ -35,7 +35,6 @@ static int cpufreq_stats_update(struct cpufreq_stats *stats) stats->time_in_state[stats->last_index] += cur_time - stats->last_time; stats->last_time = cur_time; spin_unlock(&cpufreq_stats_lock); - return 0; } static void cpufreq_stats_clear_table(struct cpufreq_stats *stats) diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c index d9b2c2de49c4..741f22e5cee3 100644 --- a/drivers/cpufreq/imx6q-cpufreq.c +++ b/drivers/cpufreq/imx6q-cpufreq.c @@ -25,15 +25,29 @@ static struct regulator *arm_reg; static struct regulator *pu_reg; static struct regulator *soc_reg; -static struct clk *arm_clk; -static struct clk *pll1_sys_clk; -static struct clk *pll1_sw_clk; -static struct clk *step_clk; -static struct clk *pll2_pfd2_396m_clk; - -/* clk used by i.MX6UL */ -static struct clk *pll2_bus_clk; -static struct clk *secondary_sel_clk; +enum IMX6_CPUFREQ_CLKS { + ARM, + PLL1_SYS, + STEP, + PLL1_SW, + PLL2_PFD2_396M, + /* MX6UL requires two more clks */ + PLL2_BUS, + SECONDARY_SEL, +}; +#define IMX6Q_CPUFREQ_CLK_NUM 5 +#define IMX6UL_CPUFREQ_CLK_NUM 7 + +static int num_clks; +static struct clk_bulk_data clks[] = { + { .id = "arm" }, + { .id = "pll1_sys" }, + { .id = "step" }, + { .id = "pll1_sw" }, + { .id = "pll2_pfd2_396m" }, + { .id = "pll2_bus" }, + { .id = "secondary_sel" }, +}; static struct device *cpu_dev; static bool free_opp; @@ -53,7 +67,7 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index) new_freq = freq_table[index].frequency; freq_hz = new_freq * 1000; - old_freq = clk_get_rate(arm_clk) / 1000; + old_freq = clk_get_rate(clks[ARM].clk) / 1000; opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz); if (IS_ERR(opp)) { @@ -112,29 +126,35 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index) * voltage of 528MHz, so lower the CPU frequency to one * half before changing CPU frequency. */ - clk_set_rate(arm_clk, (old_freq >> 1) * 1000); - clk_set_parent(pll1_sw_clk, pll1_sys_clk); - if (freq_hz > clk_get_rate(pll2_pfd2_396m_clk)) - clk_set_parent(secondary_sel_clk, pll2_bus_clk); + clk_set_rate(clks[ARM].clk, (old_freq >> 1) * 1000); + clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk); + if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk)) + clk_set_parent(clks[SECONDARY_SEL].clk, + clks[PLL2_BUS].clk); else - clk_set_parent(secondary_sel_clk, pll2_pfd2_396m_clk); - clk_set_parent(step_clk, secondary_sel_clk); - clk_set_parent(pll1_sw_clk, step_clk); + clk_set_parent(clks[SECONDARY_SEL].clk, + clks[PLL2_PFD2_396M].clk); + clk_set_parent(clks[STEP].clk, clks[SECONDARY_SEL].clk); + clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk); + if (freq_hz > clk_get_rate(clks[PLL2_BUS].clk)) { + clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000); + clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk); + } } else { - clk_set_parent(step_clk, pll2_pfd2_396m_clk); - clk_set_parent(pll1_sw_clk, step_clk); - if (freq_hz > clk_get_rate(pll2_pfd2_396m_clk)) { - clk_set_rate(pll1_sys_clk, new_freq * 1000); - clk_set_parent(pll1_sw_clk, pll1_sys_clk); + clk_set_parent(clks[STEP].clk, clks[PLL2_PFD2_396M].clk); + clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk); + if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk)) { + clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000); + clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk); } else { /* pll1_sys needs to be enabled for divider rate change to work. */ pll1_sys_temp_enabled = true; - clk_prepare_enable(pll1_sys_clk); + clk_prepare_enable(clks[PLL1_SYS].clk); } } /* Ensure the arm clock divider is what we expect */ - ret = clk_set_rate(arm_clk, new_freq * 1000); + ret = clk_set_rate(clks[ARM].clk, new_freq * 1000); if (ret) { dev_err(cpu_dev, "failed to set clock rate: %d\n", ret); regulator_set_voltage_tol(arm_reg, volt_old, 0); @@ -143,7 +163,7 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index) /* PLL1 is only needed until after ARM-PODF is set. */ if (pll1_sys_temp_enabled) - clk_disable_unprepare(pll1_sys_clk); + clk_disable_unprepare(clks[PLL1_SYS].clk); /* scaling down? scale voltage after frequency */ if (new_freq < old_freq) { @@ -174,7 +194,7 @@ static int imx6q_cpufreq_init(struct cpufreq_policy *policy) { int ret; - policy->clk = arm_clk; + policy->clk = clks[ARM].clk; ret = cpufreq_generic_init(policy, freq_table, transition_latency); policy->suspend_freq = policy->max; @@ -244,6 +264,43 @@ put_node: of_node_put(np); } +#define OCOTP_CFG3_6UL_SPEED_696MHZ 0x2 + +static void imx6ul_opp_check_speed_grading(struct device *dev) +{ + struct device_node *np; + void __iomem *base; + u32 val; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-ocotp"); + if (!np) + return; + + base = of_iomap(np, 0); + if (!base) { + dev_err(dev, "failed to map ocotp\n"); + goto put_node; + } + + /* + * Speed GRADING[1:0] defines the max speed of ARM: + * 2b'00: Reserved; + * 2b'01: 528000000Hz; + * 2b'10: 696000000Hz; + * 2b'11: Reserved; + * We need to set the max speed of ARM according to fuse map. + */ + val = readl_relaxed(base + OCOTP_CFG3); + val >>= OCOTP_CFG3_SPEED_SHIFT; + val &= 0x3; + if (val != OCOTP_CFG3_6UL_SPEED_696MHZ) + if (dev_pm_opp_disable(dev, 696000000)) + dev_warn(dev, "failed to disable 696MHz OPP\n"); + iounmap(base); +put_node: + of_node_put(np); +} + static int imx6q_cpufreq_probe(struct platform_device *pdev) { struct device_node *np; @@ -266,28 +323,15 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev) return -ENOENT; } - arm_clk = clk_get(cpu_dev, "arm"); - pll1_sys_clk = clk_get(cpu_dev, "pll1_sys"); - pll1_sw_clk = clk_get(cpu_dev, "pll1_sw"); - step_clk = clk_get(cpu_dev, "step"); - pll2_pfd2_396m_clk = clk_get(cpu_dev, "pll2_pfd2_396m"); - if (IS_ERR(arm_clk) || IS_ERR(pll1_sys_clk) || IS_ERR(pll1_sw_clk) || - IS_ERR(step_clk) || IS_ERR(pll2_pfd2_396m_clk)) { - dev_err(cpu_dev, "failed to get clocks\n"); - ret = -ENOENT; - goto put_clk; - } - if (of_machine_is_compatible("fsl,imx6ul") || - of_machine_is_compatible("fsl,imx6ull")) { - pll2_bus_clk = clk_get(cpu_dev, "pll2_bus"); - secondary_sel_clk = clk_get(cpu_dev, "secondary_sel"); - if (IS_ERR(pll2_bus_clk) || IS_ERR(secondary_sel_clk)) { - dev_err(cpu_dev, "failed to get clocks specific to imx6ul\n"); - ret = -ENOENT; - goto put_clk; - } - } + of_machine_is_compatible("fsl,imx6ull")) + num_clks = IMX6UL_CPUFREQ_CLK_NUM; + else + num_clks = IMX6Q_CPUFREQ_CLK_NUM; + + ret = clk_bulk_get(cpu_dev, num_clks, clks); + if (ret) + goto put_node; arm_reg = regulator_get(cpu_dev, "arm"); pu_reg = regulator_get_optional(cpu_dev, "pu"); @@ -311,7 +355,10 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev) goto put_reg; } - imx6q_opp_check_speed_grading(cpu_dev); + if (of_machine_is_compatible("fsl,imx6ul")) + imx6ul_opp_check_speed_grading(cpu_dev); + else + imx6q_opp_check_speed_grading(cpu_dev); /* Because we have added the OPPs here, we must free them */ free_opp = true; @@ -424,22 +471,11 @@ put_reg: regulator_put(pu_reg); if (!IS_ERR(soc_reg)) regulator_put(soc_reg); -put_clk: - if (!IS_ERR(arm_clk)) - clk_put(arm_clk); - if (!IS_ERR(pll1_sys_clk)) - clk_put(pll1_sys_clk); - if (!IS_ERR(pll1_sw_clk)) - clk_put(pll1_sw_clk); - if (!IS_ERR(step_clk)) - clk_put(step_clk); - if (!IS_ERR(pll2_pfd2_396m_clk)) - clk_put(pll2_pfd2_396m_clk); - if (!IS_ERR(pll2_bus_clk)) - clk_put(pll2_bus_clk); - if (!IS_ERR(secondary_sel_clk)) - clk_put(secondary_sel_clk); + + clk_bulk_put(num_clks, clks); +put_node: of_node_put(np); + return ret; } @@ -453,13 +489,8 @@ static int imx6q_cpufreq_remove(struct platform_device *pdev) if (!IS_ERR(pu_reg)) regulator_put(pu_reg); regulator_put(soc_reg); - clk_put(arm_clk); - clk_put(pll1_sys_clk); - clk_put(pll1_sw_clk); - clk_put(step_clk); - clk_put(pll2_pfd2_396m_clk); - clk_put(pll2_bus_clk); - clk_put(secondary_sel_clk); + + clk_bulk_put(num_clks, clks); return 0; } diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 93a0e88bef76..7edf7a0e5a96 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1595,15 +1595,6 @@ static const struct pstate_funcs knl_funcs = { .get_val = core_get_val, }; -static const struct pstate_funcs bxt_funcs = { - .get_max = core_get_max_pstate, - .get_max_physical = core_get_max_pstate_physical, - .get_min = core_get_min_pstate, - .get_turbo = core_get_turbo_pstate, - .get_scaling = core_get_scaling, - .get_val = core_get_val, -}; - #define ICPU(model, policy) \ { X86_VENDOR_INTEL, 6, model, X86_FEATURE_APERFMPERF,\ (unsigned long)&policy } @@ -1627,8 +1618,9 @@ static const struct x86_cpu_id intel_pstate_cpu_ids[] = { ICPU(INTEL_FAM6_BROADWELL_XEON_D, core_funcs), ICPU(INTEL_FAM6_XEON_PHI_KNL, knl_funcs), ICPU(INTEL_FAM6_XEON_PHI_KNM, knl_funcs), - ICPU(INTEL_FAM6_ATOM_GOLDMONT, bxt_funcs), - ICPU(INTEL_FAM6_ATOM_GEMINI_LAKE, bxt_funcs), + ICPU(INTEL_FAM6_ATOM_GOLDMONT, core_funcs), + ICPU(INTEL_FAM6_ATOM_GEMINI_LAKE, core_funcs), + ICPU(INTEL_FAM6_SKYLAKE_X, core_funcs), {} }; MODULE_DEVICE_TABLE(x86cpu, intel_pstate_cpu_ids); diff --git a/drivers/cpufreq/longhaul.c b/drivers/cpufreq/longhaul.c index c46a12df40dd..5faa37c5b091 100644 --- a/drivers/cpufreq/longhaul.c +++ b/drivers/cpufreq/longhaul.c @@ -894,7 +894,7 @@ static int longhaul_cpu_init(struct cpufreq_policy *policy) if ((longhaul_version != TYPE_LONGHAUL_V1) && (scale_voltage != 0)) longhaul_setup_voltagescaling(); - policy->cpuinfo.transition_latency = 200000; /* nsec */ + policy->transition_delay_us = 200000; /* usec */ return cpufreq_table_validate_and_show(policy, longhaul_table); } diff --git a/drivers/cpufreq/mediatek-cpufreq.c b/drivers/cpufreq/mediatek-cpufreq.c index e0d5090b303d..8c04dddd3c28 100644 --- a/drivers/cpufreq/mediatek-cpufreq.c +++ b/drivers/cpufreq/mediatek-cpufreq.c @@ -310,28 +310,8 @@ static int mtk_cpufreq_set_target(struct cpufreq_policy *policy, static void mtk_cpufreq_ready(struct cpufreq_policy *policy) { struct mtk_cpu_dvfs_info *info = policy->driver_data; - struct device_node *np = of_node_get(info->cpu_dev->of_node); - u32 capacitance = 0; - if (WARN_ON(!np)) - return; - - if (of_find_property(np, "#cooling-cells", NULL)) { - of_property_read_u32(np, DYNAMIC_POWER, &capacitance); - - info->cdev = of_cpufreq_power_cooling_register(np, - policy, capacitance, NULL); - - if (IS_ERR(info->cdev)) { - dev_err(info->cpu_dev, - "running cpufreq without cooling device: %ld\n", - PTR_ERR(info->cdev)); - - info->cdev = NULL; - } - } - - of_node_put(np); + info->cdev = of_cpufreq_cooling_register(policy); } static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu) @@ -574,6 +554,7 @@ static struct platform_driver mtk_cpufreq_platdrv = { /* List of machines supported by this driver */ static const struct of_device_id mtk_cpufreq_machines[] __initconst = { { .compatible = "mediatek,mt2701", }, + { .compatible = "mediatek,mt2712", }, { .compatible = "mediatek,mt7622", }, { .compatible = "mediatek,mt7623", }, { .compatible = "mediatek,mt817x", }, diff --git a/drivers/cpufreq/mvebu-cpufreq.c b/drivers/cpufreq/mvebu-cpufreq.c index ed915ee85dd9..31513bd42705 100644 --- a/drivers/cpufreq/mvebu-cpufreq.c +++ b/drivers/cpufreq/mvebu-cpufreq.c @@ -76,12 +76,6 @@ static int __init armada_xp_pmsu_cpufreq_init(void) return PTR_ERR(clk); } - /* - * In case of a failure of dev_pm_opp_add(), we don't - * bother with cleaning up the registered OPP (there's - * no function to do so), and simply cancel the - * registration of the cpufreq device. - */ ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk), 0); if (ret) { clk_put(clk); @@ -91,7 +85,8 @@ static int __init armada_xp_pmsu_cpufreq_init(void) ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk) / 2, 0); if (ret) { clk_put(clk); - return ret; + dev_err(cpu_dev, "Failed to register OPPs\n"); + goto opp_register_failed; } ret = dev_pm_opp_set_sharing_cpus(cpu_dev, @@ -99,9 +94,16 @@ static int __init armada_xp_pmsu_cpufreq_init(void) if (ret) dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", __func__, ret); + clk_put(clk); } platform_device_register_simple("cpufreq-dt", -1, NULL, 0); return 0; + +opp_register_failed: + /* As registering has failed remove all the opp for all cpus */ + dev_pm_opp_cpumask_remove_table(cpu_possible_mask); + + return ret; } device_initcall(armada_xp_pmsu_cpufreq_init); diff --git a/drivers/cpufreq/powernv-cpufreq.c b/drivers/cpufreq/powernv-cpufreq.c index b6d7c4c98d0a..29cdec198657 100644 --- a/drivers/cpufreq/powernv-cpufreq.c +++ b/drivers/cpufreq/powernv-cpufreq.c @@ -29,6 +29,7 @@ #include <linux/reboot.h> #include <linux/slab.h> #include <linux/cpu.h> +#include <linux/hashtable.h> #include <trace/events/power.h> #include <asm/cputhreads.h> @@ -38,14 +39,13 @@ #include <asm/opal.h> #include <linux/timer.h> -#define POWERNV_MAX_PSTATES 256 +#define POWERNV_MAX_PSTATES_ORDER 8 +#define POWERNV_MAX_PSTATES (1UL << (POWERNV_MAX_PSTATES_ORDER)) #define PMSR_PSAFE_ENABLE (1UL << 30) #define PMSR_SPR_EM_DISABLE (1UL << 31) -#define PMSR_MAX(x) ((x >> 32) & 0xFF) +#define MAX_PSTATE_SHIFT 32 #define LPSTATE_SHIFT 48 #define GPSTATE_SHIFT 56 -#define GET_LPSTATE(x) (((x) >> LPSTATE_SHIFT) & 0xFF) -#define GET_GPSTATE(x) (((x) >> GPSTATE_SHIFT) & 0xFF) #define MAX_RAMP_DOWN_TIME 5120 /* @@ -94,6 +94,27 @@ struct global_pstate_info { }; static struct cpufreq_frequency_table powernv_freqs[POWERNV_MAX_PSTATES+1]; + +DEFINE_HASHTABLE(pstate_revmap, POWERNV_MAX_PSTATES_ORDER); +/** + * struct pstate_idx_revmap_data: Entry in the hashmap pstate_revmap + * indexed by a function of pstate id. + * + * @pstate_id: pstate id for this entry. + * + * @cpufreq_table_idx: Index into the powernv_freqs + * cpufreq_frequency_table for frequency + * corresponding to pstate_id. + * + * @hentry: hlist_node that hooks this entry into the pstate_revmap + * hashtable + */ +struct pstate_idx_revmap_data { + u8 pstate_id; + unsigned int cpufreq_table_idx; + struct hlist_node hentry; +}; + static bool rebooting, throttled, occ_reset; static const char * const throttle_reason[] = { @@ -148,39 +169,56 @@ static struct powernv_pstate_info { bool wof_enabled; } powernv_pstate_info; -/* Use following macros for conversions between pstate_id and index */ -static inline int idx_to_pstate(unsigned int i) +static inline u8 extract_pstate(u64 pmsr_val, unsigned int shift) +{ + return ((pmsr_val >> shift) & 0xFF); +} + +#define extract_local_pstate(x) extract_pstate(x, LPSTATE_SHIFT) +#define extract_global_pstate(x) extract_pstate(x, GPSTATE_SHIFT) +#define extract_max_pstate(x) extract_pstate(x, MAX_PSTATE_SHIFT) + +/* Use following functions for conversions between pstate_id and index */ + +/** + * idx_to_pstate : Returns the pstate id corresponding to the + * frequency in the cpufreq frequency table + * powernv_freqs indexed by @i. + * + * If @i is out of bound, this will return the pstate + * corresponding to the nominal frequency. + */ +static inline u8 idx_to_pstate(unsigned int i) { if (unlikely(i >= powernv_pstate_info.nr_pstates)) { - pr_warn_once("index %u is out of bound\n", i); + pr_warn_once("idx_to_pstate: index %u is out of bound\n", i); return powernv_freqs[powernv_pstate_info.nominal].driver_data; } return powernv_freqs[i].driver_data; } -static inline unsigned int pstate_to_idx(int pstate) +/** + * pstate_to_idx : Returns the index in the cpufreq frequencytable + * powernv_freqs for the frequency whose corresponding + * pstate id is @pstate. + * + * If no frequency corresponding to @pstate is found, + * this will return the index of the nominal + * frequency. + */ +static unsigned int pstate_to_idx(u8 pstate) { - int min = powernv_freqs[powernv_pstate_info.min].driver_data; - int max = powernv_freqs[powernv_pstate_info.max].driver_data; + unsigned int key = pstate % POWERNV_MAX_PSTATES; + struct pstate_idx_revmap_data *revmap_data; - if (min > 0) { - if (unlikely((pstate < max) || (pstate > min))) { - pr_warn_once("pstate %d is out of bound\n", pstate); - return powernv_pstate_info.nominal; - } - } else { - if (unlikely((pstate > max) || (pstate < min))) { - pr_warn_once("pstate %d is out of bound\n", pstate); - return powernv_pstate_info.nominal; - } + hash_for_each_possible(pstate_revmap, revmap_data, hentry, key) { + if (revmap_data->pstate_id == pstate) + return revmap_data->cpufreq_table_idx; } - /* - * abs() is deliberately used so that is works with - * both monotonically increasing and decreasing - * pstate values - */ - return abs(pstate - idx_to_pstate(powernv_pstate_info.max)); + + pr_warn_once("pstate_to_idx: pstate 0x%x not found\n", pstate); + return powernv_pstate_info.nominal; } static inline void reset_gpstates(struct cpufreq_policy *policy) @@ -247,7 +285,7 @@ static int init_powernv_pstates(void) powernv_pstate_info.wof_enabled = true; next: - pr_info("cpufreq pstate min %d nominal %d max %d\n", pstate_min, + pr_info("cpufreq pstate min 0x%x nominal 0x%x max 0x%x\n", pstate_min, pstate_nominal, pstate_max); pr_info("Workload Optimized Frequency is %s in the platform\n", (powernv_pstate_info.wof_enabled) ? "enabled" : "disabled"); @@ -278,19 +316,30 @@ next: powernv_pstate_info.nr_pstates = nr_pstates; pr_debug("NR PStates %d\n", nr_pstates); + for (i = 0; i < nr_pstates; i++) { u32 id = be32_to_cpu(pstate_ids[i]); u32 freq = be32_to_cpu(pstate_freqs[i]); + struct pstate_idx_revmap_data *revmap_data; + unsigned int key; pr_debug("PState id %d freq %d MHz\n", id, freq); powernv_freqs[i].frequency = freq * 1000; /* kHz */ - powernv_freqs[i].driver_data = id; + powernv_freqs[i].driver_data = id & 0xFF; + + revmap_data = (struct pstate_idx_revmap_data *) + kmalloc(sizeof(*revmap_data), GFP_KERNEL); + + revmap_data->pstate_id = id & 0xFF; + revmap_data->cpufreq_table_idx = i; + key = (revmap_data->pstate_id) % POWERNV_MAX_PSTATES; + hash_add(pstate_revmap, &revmap_data->hentry, key); if (id == pstate_max) powernv_pstate_info.max = i; - else if (id == pstate_nominal) + if (id == pstate_nominal) powernv_pstate_info.nominal = i; - else if (id == pstate_min) + if (id == pstate_min) powernv_pstate_info.min = i; if (powernv_pstate_info.wof_enabled && id == pstate_turbo) { @@ -307,14 +356,13 @@ next: } /* Returns the CPU frequency corresponding to the pstate_id. */ -static unsigned int pstate_id_to_freq(int pstate_id) +static unsigned int pstate_id_to_freq(u8 pstate_id) { int i; i = pstate_to_idx(pstate_id); if (i >= powernv_pstate_info.nr_pstates || i < 0) { - pr_warn("PState id %d outside of PState table, " - "reporting nominal id %d instead\n", + pr_warn("PState id 0x%x outside of PState table, reporting nominal id 0x%x instead\n", pstate_id, idx_to_pstate(powernv_pstate_info.nominal)); i = powernv_pstate_info.nominal; } @@ -420,8 +468,8 @@ static inline void set_pmspr(unsigned long sprn, unsigned long val) */ struct powernv_smp_call_data { unsigned int freq; - int pstate_id; - int gpstate_id; + u8 pstate_id; + u8 gpstate_id; }; /* @@ -438,22 +486,15 @@ struct powernv_smp_call_data { static void powernv_read_cpu_freq(void *arg) { unsigned long pmspr_val; - s8 local_pstate_id; struct powernv_smp_call_data *freq_data = arg; pmspr_val = get_pmspr(SPRN_PMSR); - - /* - * The local pstate id corresponds bits 48..55 in the PMSR. - * Note: Watch out for the sign! - */ - local_pstate_id = (pmspr_val >> 48) & 0xFF; - freq_data->pstate_id = local_pstate_id; + freq_data->pstate_id = extract_local_pstate(pmspr_val); freq_data->freq = pstate_id_to_freq(freq_data->pstate_id); - pr_debug("cpu %d pmsr %016lX pstate_id %d frequency %d kHz\n", - raw_smp_processor_id(), pmspr_val, freq_data->pstate_id, - freq_data->freq); + pr_debug("cpu %d pmsr %016lX pstate_id 0x%x frequency %d kHz\n", + raw_smp_processor_id(), pmspr_val, freq_data->pstate_id, + freq_data->freq); } /* @@ -515,21 +556,21 @@ static void powernv_cpufreq_throttle_check(void *data) struct chip *chip; unsigned int cpu = smp_processor_id(); unsigned long pmsr; - int pmsr_pmax; + u8 pmsr_pmax; unsigned int pmsr_pmax_idx; pmsr = get_pmspr(SPRN_PMSR); chip = this_cpu_read(chip_info); /* Check for Pmax Capping */ - pmsr_pmax = (s8)PMSR_MAX(pmsr); + pmsr_pmax = extract_max_pstate(pmsr); pmsr_pmax_idx = pstate_to_idx(pmsr_pmax); if (pmsr_pmax_idx != powernv_pstate_info.max) { if (chip->throttled) goto next; chip->throttled = true; if (pmsr_pmax_idx > powernv_pstate_info.nominal) { - pr_warn_once("CPU %d on Chip %u has Pmax(%d) reduced below nominal frequency(%d)\n", + pr_warn_once("CPU %d on Chip %u has Pmax(0x%x) reduced below that of nominal frequency(0x%x)\n", cpu, chip->id, pmsr_pmax, idx_to_pstate(powernv_pstate_info.nominal)); chip->throttle_sub_turbo++; @@ -645,8 +686,8 @@ void gpstate_timer_handler(struct timer_list *t) * value. Hence, read from PMCR to get correct data. */ val = get_pmspr(SPRN_PMCR); - freq_data.gpstate_id = (s8)GET_GPSTATE(val); - freq_data.pstate_id = (s8)GET_LPSTATE(val); + freq_data.gpstate_id = extract_global_pstate(val); + freq_data.pstate_id = extract_local_pstate(val); if (freq_data.gpstate_id == freq_data.pstate_id) { reset_gpstates(policy); spin_unlock(&gpstates->gpstate_lock); diff --git a/drivers/cpufreq/qoriq-cpufreq.c b/drivers/cpufreq/qoriq-cpufreq.c index 4ada55b8856e..0562761a3dec 100644 --- a/drivers/cpufreq/qoriq-cpufreq.c +++ b/drivers/cpufreq/qoriq-cpufreq.c @@ -275,20 +275,8 @@ static int qoriq_cpufreq_target(struct cpufreq_policy *policy, static void qoriq_cpufreq_ready(struct cpufreq_policy *policy) { struct cpu_data *cpud = policy->driver_data; - struct device_node *np = of_get_cpu_node(policy->cpu, NULL); - if (of_find_property(np, "#cooling-cells", NULL)) { - cpud->cdev = of_cpufreq_cooling_register(np, policy); - - if (IS_ERR(cpud->cdev) && PTR_ERR(cpud->cdev) != -ENOSYS) { - pr_err("cpu%d is not running as cooling device: %ld\n", - policy->cpu, PTR_ERR(cpud->cdev)); - - cpud->cdev = NULL; - } - } - - of_node_put(np); + cpud->cdev = of_cpufreq_cooling_register(policy); } static struct cpufreq_driver qoriq_cpufreq_driver = { diff --git a/drivers/cpufreq/scpi-cpufreq.c b/drivers/cpufreq/scpi-cpufreq.c index 05d299052c5c..247fcbfa4cb5 100644 --- a/drivers/cpufreq/scpi-cpufreq.c +++ b/drivers/cpufreq/scpi-cpufreq.c @@ -18,27 +18,89 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/clk.h> #include <linux/cpu.h> #include <linux/cpufreq.h> +#include <linux/cpumask.h> +#include <linux/cpu_cooling.h> +#include <linux/export.h> #include <linux/module.h> -#include <linux/platform_device.h> +#include <linux/of_platform.h> #include <linux/pm_opp.h> #include <linux/scpi_protocol.h> +#include <linux/slab.h> #include <linux/types.h> -#include "arm_big_little.h" +struct scpi_data { + struct clk *clk; + struct device *cpu_dev; + struct thermal_cooling_device *cdev; +}; static struct scpi_ops *scpi_ops; -static int scpi_get_transition_latency(struct device *cpu_dev) +static unsigned int scpi_cpufreq_get_rate(unsigned int cpu) +{ + struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu); + struct scpi_data *priv = policy->driver_data; + unsigned long rate = clk_get_rate(priv->clk); + + return rate / 1000; +} + +static int +scpi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index) +{ + struct scpi_data *priv = policy->driver_data; + u64 rate = policy->freq_table[index].frequency * 1000; + int ret; + + ret = clk_set_rate(priv->clk, rate); + if (!ret && (clk_get_rate(priv->clk) != rate)) + ret = -EIO; + + return ret; +} + +static int +scpi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) { - return scpi_ops->get_transition_latency(cpu_dev); + int cpu, domain, tdomain; + struct device *tcpu_dev; + + domain = scpi_ops->device_domain_id(cpu_dev); + if (domain < 0) + return domain; + + for_each_possible_cpu(cpu) { + if (cpu == cpu_dev->id) + continue; + + tcpu_dev = get_cpu_device(cpu); + if (!tcpu_dev) + continue; + + tdomain = scpi_ops->device_domain_id(tcpu_dev); + if (tdomain == domain) + cpumask_set_cpu(cpu, cpumask); + } + + return 0; } -static int scpi_init_opp_table(const struct cpumask *cpumask) +static int scpi_cpufreq_init(struct cpufreq_policy *policy) { int ret; - struct device *cpu_dev = get_cpu_device(cpumask_first(cpumask)); + unsigned int latency; + struct device *cpu_dev; + struct scpi_data *priv; + struct cpufreq_frequency_table *freq_table; + + cpu_dev = get_cpu_device(policy->cpu); + if (!cpu_dev) { + pr_err("failed to get cpu%d device\n", policy->cpu); + return -ENODEV; + } ret = scpi_ops->add_opps_to_device(cpu_dev); if (ret) { @@ -46,32 +108,133 @@ static int scpi_init_opp_table(const struct cpumask *cpumask) return ret; } - ret = dev_pm_opp_set_sharing_cpus(cpu_dev, cpumask); - if (ret) + ret = scpi_get_sharing_cpus(cpu_dev, policy->cpus); + if (ret) { + dev_warn(cpu_dev, "failed to get sharing cpumask\n"); + return ret; + } + + ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); + if (ret) { dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n", __func__, ret); + return ret; + } + + ret = dev_pm_opp_get_opp_count(cpu_dev); + if (ret <= 0) { + dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n"); + ret = -EPROBE_DEFER; + goto out_free_opp; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto out_free_opp; + } + + ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); + if (ret) { + dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); + goto out_free_priv; + } + + priv->cpu_dev = cpu_dev; + priv->clk = clk_get(cpu_dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d\n", + __func__, cpu_dev->id); + goto out_free_cpufreq_table; + } + + policy->driver_data = priv; + + ret = cpufreq_table_validate_and_show(policy, freq_table); + if (ret) { + dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__, + ret); + goto out_put_clk; + } + + /* scpi allows DVFS request for any domain from any CPU */ + policy->dvfs_possible_from_any_cpu = true; + + latency = scpi_ops->get_transition_latency(cpu_dev); + if (!latency) + latency = CPUFREQ_ETERNAL; + + policy->cpuinfo.transition_latency = latency; + + policy->fast_switch_possible = false; + return 0; + +out_put_clk: + clk_put(priv->clk); +out_free_cpufreq_table: + dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); +out_free_priv: + kfree(priv); +out_free_opp: + dev_pm_opp_cpumask_remove_table(policy->cpus); + return ret; } -static const struct cpufreq_arm_bL_ops scpi_cpufreq_ops = { - .name = "scpi", - .get_transition_latency = scpi_get_transition_latency, - .init_opp_table = scpi_init_opp_table, - .free_opp_table = dev_pm_opp_cpumask_remove_table, +static int scpi_cpufreq_exit(struct cpufreq_policy *policy) +{ + struct scpi_data *priv = policy->driver_data; + + cpufreq_cooling_unregister(priv->cdev); + clk_put(priv->clk); + dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); + kfree(priv); + dev_pm_opp_cpumask_remove_table(policy->related_cpus); + + return 0; +} + +static void scpi_cpufreq_ready(struct cpufreq_policy *policy) +{ + struct scpi_data *priv = policy->driver_data; + struct thermal_cooling_device *cdev; + + cdev = of_cpufreq_cooling_register(policy); + if (!IS_ERR(cdev)) + priv->cdev = cdev; +} + +static struct cpufreq_driver scpi_cpufreq_driver = { + .name = "scpi-cpufreq", + .flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY | + CPUFREQ_NEED_INITIAL_FREQ_CHECK, + .verify = cpufreq_generic_frequency_table_verify, + .attr = cpufreq_generic_attr, + .get = scpi_cpufreq_get_rate, + .init = scpi_cpufreq_init, + .exit = scpi_cpufreq_exit, + .ready = scpi_cpufreq_ready, + .target_index = scpi_cpufreq_set_target, }; static int scpi_cpufreq_probe(struct platform_device *pdev) { + int ret; + scpi_ops = get_scpi_ops(); if (!scpi_ops) return -EIO; - return bL_cpufreq_register(&scpi_cpufreq_ops); + ret = cpufreq_register_driver(&scpi_cpufreq_driver); + if (ret) + dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n", + __func__, ret); + return ret; } static int scpi_cpufreq_remove(struct platform_device *pdev) { - bL_cpufreq_unregister(&scpi_cpufreq_ops); + cpufreq_unregister_driver(&scpi_cpufreq_driver); scpi_ops = NULL; return 0; } diff --git a/drivers/cpufreq/ti-cpufreq.c b/drivers/cpufreq/ti-cpufreq.c index 923317f03b4b..a099b7bf74cd 100644 --- a/drivers/cpufreq/ti-cpufreq.c +++ b/drivers/cpufreq/ti-cpufreq.c @@ -17,6 +17,7 @@ #include <linux/cpu.h> #include <linux/io.h> #include <linux/mfd/syscon.h> +#include <linux/module.h> #include <linux/init.h> #include <linux/of.h> #include <linux/of_platform.h> @@ -50,6 +51,7 @@ struct ti_cpufreq_soc_data { unsigned long efuse_mask; unsigned long efuse_shift; unsigned long rev_offset; + bool multi_regulator; }; struct ti_cpufreq_data { @@ -57,6 +59,7 @@ struct ti_cpufreq_data { struct device_node *opp_node; struct regmap *syscon; const struct ti_cpufreq_soc_data *soc_data; + struct opp_table *opp_table; }; static unsigned long amx3_efuse_xlate(struct ti_cpufreq_data *opp_data, @@ -95,6 +98,7 @@ static struct ti_cpufreq_soc_data am3x_soc_data = { .efuse_offset = 0x07fc, .efuse_mask = 0x1fff, .rev_offset = 0x600, + .multi_regulator = false, }; static struct ti_cpufreq_soc_data am4x_soc_data = { @@ -103,6 +107,7 @@ static struct ti_cpufreq_soc_data am4x_soc_data = { .efuse_offset = 0x0610, .efuse_mask = 0x3f, .rev_offset = 0x600, + .multi_regulator = false, }; static struct ti_cpufreq_soc_data dra7_soc_data = { @@ -111,6 +116,7 @@ static struct ti_cpufreq_soc_data dra7_soc_data = { .efuse_mask = 0xf80000, .efuse_shift = 19, .rev_offset = 0x204, + .multi_regulator = true, }; /** @@ -195,12 +201,14 @@ static const struct of_device_id ti_cpufreq_of_match[] = { {}, }; -static int ti_cpufreq_init(void) +static int ti_cpufreq_probe(struct platform_device *pdev) { u32 version[VERSION_COUNT]; struct device_node *np; const struct of_device_id *match; + struct opp_table *ti_opp_table; struct ti_cpufreq_data *opp_data; + const char * const reg_names[] = {"vdd", "vbb"}; int ret; np = of_find_node_by_path("/"); @@ -247,16 +255,29 @@ static int ti_cpufreq_init(void) if (ret) goto fail_put_node; - ret = PTR_ERR_OR_ZERO(dev_pm_opp_set_supported_hw(opp_data->cpu_dev, - version, VERSION_COUNT)); - if (ret) { + ti_opp_table = dev_pm_opp_set_supported_hw(opp_data->cpu_dev, + version, VERSION_COUNT); + if (IS_ERR(ti_opp_table)) { dev_err(opp_data->cpu_dev, "Failed to set supported hardware\n"); + ret = PTR_ERR(ti_opp_table); goto fail_put_node; } - of_node_put(opp_data->opp_node); + opp_data->opp_table = ti_opp_table; + + if (opp_data->soc_data->multi_regulator) { + ti_opp_table = dev_pm_opp_set_regulators(opp_data->cpu_dev, + reg_names, + ARRAY_SIZE(reg_names)); + if (IS_ERR(ti_opp_table)) { + dev_pm_opp_put_supported_hw(opp_data->opp_table); + ret = PTR_ERR(ti_opp_table); + goto fail_put_node; + } + } + of_node_put(opp_data->opp_node); register_cpufreq_dt: platform_device_register_simple("cpufreq-dt", -1, NULL, 0); @@ -269,4 +290,22 @@ free_opp_data: return ret; } -device_initcall(ti_cpufreq_init); + +static int ti_cpufreq_init(void) +{ + platform_device_register_simple("ti-cpufreq", -1, NULL, 0); + return 0; +} +module_init(ti_cpufreq_init); + +static struct platform_driver ti_cpufreq_driver = { + .probe = ti_cpufreq_probe, + .driver = { + .name = "ti-cpufreq", + }, +}; +module_platform_driver(ti_cpufreq_driver); + +MODULE_DESCRIPTION("TI CPUFreq/OPP hw-supported driver"); +MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/psci_checker.c b/drivers/firmware/psci_checker.c index f3f4f810e5df..bb1c068bff19 100644 --- a/drivers/firmware/psci_checker.c +++ b/drivers/firmware/psci_checker.c @@ -77,8 +77,8 @@ static int psci_ops_check(void) return 0; } -static int find_clusters(const struct cpumask *cpus, - const struct cpumask **clusters) +static int find_cpu_groups(const struct cpumask *cpus, + const struct cpumask **cpu_groups) { unsigned int nb = 0; cpumask_var_t tmp; @@ -88,11 +88,11 @@ static int find_clusters(const struct cpumask *cpus, cpumask_copy(tmp, cpus); while (!cpumask_empty(tmp)) { - const struct cpumask *cluster = + const struct cpumask *cpu_group = topology_core_cpumask(cpumask_any(tmp)); - clusters[nb++] = cluster; - cpumask_andnot(tmp, tmp, cluster); + cpu_groups[nb++] = cpu_group; + cpumask_andnot(tmp, tmp, cpu_group); } free_cpumask_var(tmp); @@ -170,24 +170,24 @@ static int hotplug_tests(void) { int err; cpumask_var_t offlined_cpus; - int i, nb_cluster; - const struct cpumask **clusters; + int i, nb_cpu_group; + const struct cpumask **cpu_groups; char *page_buf; err = -ENOMEM; if (!alloc_cpumask_var(&offlined_cpus, GFP_KERNEL)) return err; - /* We may have up to nb_available_cpus clusters. */ - clusters = kmalloc_array(nb_available_cpus, sizeof(*clusters), - GFP_KERNEL); - if (!clusters) + /* We may have up to nb_available_cpus cpu_groups. */ + cpu_groups = kmalloc_array(nb_available_cpus, sizeof(*cpu_groups), + GFP_KERNEL); + if (!cpu_groups) goto out_free_cpus; page_buf = (char *)__get_free_page(GFP_KERNEL); if (!page_buf) - goto out_free_clusters; + goto out_free_cpu_groups; err = 0; - nb_cluster = find_clusters(cpu_online_mask, clusters); + nb_cpu_group = find_cpu_groups(cpu_online_mask, cpu_groups); /* * Of course the last CPU cannot be powered down and cpu_down() should @@ -197,24 +197,22 @@ static int hotplug_tests(void) err += down_and_up_cpus(cpu_online_mask, offlined_cpus); /* - * Take down CPUs by cluster this time. When the last CPU is turned - * off, the cluster itself should shut down. + * Take down CPUs by cpu group this time. When the last CPU is turned + * off, the cpu group itself should shut down. */ - for (i = 0; i < nb_cluster; ++i) { - int cluster_id = - topology_physical_package_id(cpumask_any(clusters[i])); + for (i = 0; i < nb_cpu_group; ++i) { ssize_t len = cpumap_print_to_pagebuf(true, page_buf, - clusters[i]); + cpu_groups[i]); /* Remove trailing newline. */ page_buf[len - 1] = '\0'; - pr_info("Trying to turn off and on again cluster %d " - "(CPUs %s)\n", cluster_id, page_buf); - err += down_and_up_cpus(clusters[i], offlined_cpus); + pr_info("Trying to turn off and on again group %d (CPUs %s)\n", + i, page_buf); + err += down_and_up_cpus(cpu_groups[i], offlined_cpus); } free_page((unsigned long)page_buf); -out_free_clusters: - kfree(clusters); +out_free_cpu_groups: + kfree(cpu_groups); out_free_cpus: free_cpumask_var(offlined_cpus); return err; diff --git a/drivers/opp/Makefile b/drivers/opp/Makefile index e70ceb406fe9..6ce6aefacc81 100644 --- a/drivers/opp/Makefile +++ b/drivers/opp/Makefile @@ -2,3 +2,4 @@ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG obj-y += core.o cpu.o obj-$(CONFIG_OF) += of.o obj-$(CONFIG_DEBUG_FS) += debugfs.o +obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-opp-supply.o diff --git a/drivers/opp/ti-opp-supply.c b/drivers/opp/ti-opp-supply.c new file mode 100644 index 000000000000..370eff3acd8a --- /dev/null +++ b/drivers/opp/ti-opp-supply.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + * Nishanth Menon <nm@ti.com> + * Dave Gerlach <d-gerlach@ti.com> + * + * TI OPP supply driver that provides override into the regulator control + * for generic opp core to handle devices with ABB regulator and/or + * SmartReflex Class0. + */ +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +/** + * struct ti_opp_supply_optimum_voltage_table - optimized voltage table + * @reference_uv: reference voltage (usually Nominal voltage) + * @optimized_uv: Optimized voltage from efuse + */ +struct ti_opp_supply_optimum_voltage_table { + unsigned int reference_uv; + unsigned int optimized_uv; +}; + +/** + * struct ti_opp_supply_data - OMAP specific opp supply data + * @vdd_table: Optimized voltage mapping table + * @num_vdd_table: number of entries in vdd_table + * @vdd_absolute_max_voltage_uv: absolute maximum voltage in UV for the supply + */ +struct ti_opp_supply_data { + struct ti_opp_supply_optimum_voltage_table *vdd_table; + u32 num_vdd_table; + u32 vdd_absolute_max_voltage_uv; +}; + +static struct ti_opp_supply_data opp_data; + +/** + * struct ti_opp_supply_of_data - device tree match data + * @flags: specific type of opp supply + * @efuse_voltage_mask: mask required for efuse register representing voltage + * @efuse_voltage_uv: Are the efuse entries in micro-volts? if not, assume + * milli-volts. + */ +struct ti_opp_supply_of_data { +#define OPPDM_EFUSE_CLASS0_OPTIMIZED_VOLTAGE BIT(1) +#define OPPDM_HAS_NO_ABB BIT(2) + const u8 flags; + const u32 efuse_voltage_mask; + const bool efuse_voltage_uv; +}; + +/** + * _store_optimized_voltages() - store optimized voltages + * @dev: ti opp supply device for which we need to store info + * @data: data specific to the device + * + * Picks up efuse based optimized voltages for VDD unique per device and + * stores it in internal data structure for use during transition requests. + * + * Return: If successful, 0, else appropriate error value. + */ +static int _store_optimized_voltages(struct device *dev, + struct ti_opp_supply_data *data) +{ + void __iomem *base; + struct property *prop; + struct resource *res; + const __be32 *val; + int proplen, i; + int ret = 0; + struct ti_opp_supply_optimum_voltage_table *table; + const struct ti_opp_supply_of_data *of_data = dev_get_drvdata(dev); + + /* pick up Efuse based voltages */ + res = platform_get_resource(to_platform_device(dev), IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Unable to get IO resource\n"); + ret = -ENODEV; + goto out_map; + } + + base = ioremap_nocache(res->start, resource_size(res)); + if (!base) { + dev_err(dev, "Unable to map Efuse registers\n"); + ret = -ENOMEM; + goto out_map; + } + + /* Fetch efuse-settings. */ + prop = of_find_property(dev->of_node, "ti,efuse-settings", NULL); + if (!prop) { + dev_err(dev, "No 'ti,efuse-settings' property found\n"); + ret = -EINVAL; + goto out; + } + + proplen = prop->length / sizeof(int); + data->num_vdd_table = proplen / 2; + /* Verify for corrupted OPP entries in dt */ + if (data->num_vdd_table * 2 * sizeof(int) != prop->length) { + dev_err(dev, "Invalid 'ti,efuse-settings'\n"); + ret = -EINVAL; + goto out; + } + + ret = of_property_read_u32(dev->of_node, "ti,absolute-max-voltage-uv", + &data->vdd_absolute_max_voltage_uv); + if (ret) { + dev_err(dev, "ti,absolute-max-voltage-uv is missing\n"); + ret = -EINVAL; + goto out; + } + + table = kzalloc(sizeof(*data->vdd_table) * + data->num_vdd_table, GFP_KERNEL); + if (!table) { + ret = -ENOMEM; + goto out; + } + data->vdd_table = table; + + val = prop->value; + for (i = 0; i < data->num_vdd_table; i++, table++) { + u32 efuse_offset; + u32 tmp; + + table->reference_uv = be32_to_cpup(val++); + efuse_offset = be32_to_cpup(val++); + + tmp = readl(base + efuse_offset); + tmp &= of_data->efuse_voltage_mask; + tmp >>= __ffs(of_data->efuse_voltage_mask); + + table->optimized_uv = of_data->efuse_voltage_uv ? tmp : + tmp * 1000; + + dev_dbg(dev, "[%d] efuse=0x%08x volt_table=%d vset=%d\n", + i, efuse_offset, table->reference_uv, + table->optimized_uv); + + /* + * Some older samples might not have optimized efuse + * Use reference voltage for those - just add debug message + * for them. + */ + if (!table->optimized_uv) { + dev_dbg(dev, "[%d] efuse=0x%08x volt_table=%d:vset0\n", + i, efuse_offset, table->reference_uv); + table->optimized_uv = table->reference_uv; + } + } +out: + iounmap(base); +out_map: + return ret; +} + +/** + * _free_optimized_voltages() - free resources for optvoltages + * @dev: device for which we need to free info + * @data: data specific to the device + */ +static void _free_optimized_voltages(struct device *dev, + struct ti_opp_supply_data *data) +{ + kfree(data->vdd_table); + data->vdd_table = NULL; + data->num_vdd_table = 0; +} + +/** + * _get_optimal_vdd_voltage() - Finds optimal voltage for the supply + * @dev: device for which we need to find info + * @data: data specific to the device + * @reference_uv: reference voltage (OPP voltage) for which we need value + * + * Return: if a match is found, return optimized voltage, else return + * reference_uv, also return reference_uv if no optimization is needed. + */ +static int _get_optimal_vdd_voltage(struct device *dev, + struct ti_opp_supply_data *data, + int reference_uv) +{ + int i; + struct ti_opp_supply_optimum_voltage_table *table; + + if (!data->num_vdd_table) + return reference_uv; + + table = data->vdd_table; + if (!table) + return -EINVAL; + + /* Find a exact match - this list is usually very small */ + for (i = 0; i < data->num_vdd_table; i++, table++) + if (table->reference_uv == reference_uv) + return table->optimized_uv; + + /* IF things are screwed up, we'd make a mess on console.. ratelimit */ + dev_err_ratelimited(dev, "%s: Failed optimized voltage match for %d\n", + __func__, reference_uv); + return reference_uv; +} + +static int _opp_set_voltage(struct device *dev, + struct dev_pm_opp_supply *supply, + int new_target_uv, struct regulator *reg, + char *reg_name) +{ + int ret; + unsigned long vdd_uv, uv_max; + + if (new_target_uv) + vdd_uv = new_target_uv; + else + vdd_uv = supply->u_volt; + + /* + * If we do have an absolute max voltage specified, then we should + * use that voltage instead to allow for cases where the voltage rails + * are ganged (example if we set the max for an opp as 1.12v, and + * the absolute max is 1.5v, for another rail to get 1.25v, it cannot + * be achieved if the regulator is constrainted to max of 1.12v, even + * if it can function at 1.25v + */ + if (opp_data.vdd_absolute_max_voltage_uv) + uv_max = opp_data.vdd_absolute_max_voltage_uv; + else + uv_max = supply->u_volt_max; + + if (vdd_uv > uv_max || + vdd_uv < supply->u_volt_min || + supply->u_volt_min > uv_max) { + dev_warn(dev, + "Invalid range voltages [Min:%lu target:%lu Max:%lu]\n", + supply->u_volt_min, vdd_uv, uv_max); + return -EINVAL; + } + + dev_dbg(dev, "%s scaling to %luuV[min %luuV max %luuV]\n", reg_name, + vdd_uv, supply->u_volt_min, + uv_max); + + ret = regulator_set_voltage_triplet(reg, + supply->u_volt_min, + vdd_uv, + uv_max); + if (ret) { + dev_err(dev, "%s failed for %luuV[min %luuV max %luuV]\n", + reg_name, vdd_uv, supply->u_volt_min, + uv_max); + return ret; + } + + return 0; +} + +/** + * ti_opp_supply_set_opp() - do the opp supply transition + * @data: information on regulators and new and old opps provided by + * opp core to use in transition + * + * Return: If successful, 0, else appropriate error value. + */ +static int ti_opp_supply_set_opp(struct dev_pm_set_opp_data *data) +{ + struct dev_pm_opp_supply *old_supply_vdd = &data->old_opp.supplies[0]; + struct dev_pm_opp_supply *old_supply_vbb = &data->old_opp.supplies[1]; + struct dev_pm_opp_supply *new_supply_vdd = &data->new_opp.supplies[0]; + struct dev_pm_opp_supply *new_supply_vbb = &data->new_opp.supplies[1]; + struct device *dev = data->dev; + unsigned long old_freq = data->old_opp.rate, freq = data->new_opp.rate; + struct clk *clk = data->clk; + struct regulator *vdd_reg = data->regulators[0]; + struct regulator *vbb_reg = data->regulators[1]; + int vdd_uv; + int ret; + + vdd_uv = _get_optimal_vdd_voltage(dev, &opp_data, + new_supply_vbb->u_volt); + + /* Scaling up? Scale voltage before frequency */ + if (freq > old_freq) { + ret = _opp_set_voltage(dev, new_supply_vdd, vdd_uv, vdd_reg, + "vdd"); + if (ret) + goto restore_voltage; + + ret = _opp_set_voltage(dev, new_supply_vbb, 0, vbb_reg, "vbb"); + if (ret) + goto restore_voltage; + } + + /* Change frequency */ + dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", + __func__, old_freq, freq); + + ret = clk_set_rate(clk, freq); + if (ret) { + dev_err(dev, "%s: failed to set clock rate: %d\n", __func__, + ret); + goto restore_voltage; + } + + /* Scaling down? Scale voltage after frequency */ + if (freq < old_freq) { + ret = _opp_set_voltage(dev, new_supply_vbb, 0, vbb_reg, "vbb"); + if (ret) + goto restore_freq; + + ret = _opp_set_voltage(dev, new_supply_vdd, vdd_uv, vdd_reg, + "vdd"); + if (ret) + goto restore_freq; + } + + return 0; + +restore_freq: + ret = clk_set_rate(clk, old_freq); + if (ret) + dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", + __func__, old_freq); +restore_voltage: + /* This shouldn't harm even if the voltages weren't updated earlier */ + if (old_supply_vdd->u_volt) { + ret = _opp_set_voltage(dev, old_supply_vbb, 0, vbb_reg, "vbb"); + if (ret) + return ret; + + ret = _opp_set_voltage(dev, old_supply_vdd, 0, vdd_reg, + "vdd"); + if (ret) + return ret; + } + + return ret; +} + +static const struct ti_opp_supply_of_data omap_generic_of_data = { +}; + +static const struct ti_opp_supply_of_data omap_omap5_of_data = { + .flags = OPPDM_EFUSE_CLASS0_OPTIMIZED_VOLTAGE, + .efuse_voltage_mask = 0xFFF, + .efuse_voltage_uv = false, +}; + +static const struct ti_opp_supply_of_data omap_omap5core_of_data = { + .flags = OPPDM_EFUSE_CLASS0_OPTIMIZED_VOLTAGE | OPPDM_HAS_NO_ABB, + .efuse_voltage_mask = 0xFFF, + .efuse_voltage_uv = false, +}; + +static const struct of_device_id ti_opp_supply_of_match[] = { + {.compatible = "ti,omap-opp-supply", .data = &omap_generic_of_data}, + {.compatible = "ti,omap5-opp-supply", .data = &omap_omap5_of_data}, + {.compatible = "ti,omap5-core-opp-supply", + .data = &omap_omap5core_of_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, ti_opp_supply_of_match); + +static int ti_opp_supply_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *cpu_dev = get_cpu_device(0); + const struct of_device_id *match; + const struct ti_opp_supply_of_data *of_data; + int ret = 0; + + match = of_match_device(ti_opp_supply_of_match, dev); + if (!match) { + /* We do not expect this to happen */ + dev_err(dev, "%s: Unable to match device\n", __func__); + return -ENODEV; + } + if (!match->data) { + /* Again, unlikely.. but mistakes do happen */ + dev_err(dev, "%s: Bad data in match\n", __func__); + return -EINVAL; + } + of_data = match->data; + + dev_set_drvdata(dev, (void *)of_data); + + /* If we need optimized voltage */ + if (of_data->flags & OPPDM_EFUSE_CLASS0_OPTIMIZED_VOLTAGE) { + ret = _store_optimized_voltages(dev, &opp_data); + if (ret) + return ret; + } + + ret = PTR_ERR_OR_ZERO(dev_pm_opp_register_set_opp_helper(cpu_dev, + ti_opp_supply_set_opp)); + if (ret) + _free_optimized_voltages(dev, &opp_data); + + return ret; +} + +static struct platform_driver ti_opp_supply_driver = { + .probe = ti_opp_supply_probe, + .driver = { + .name = "ti_opp_supply", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ti_opp_supply_of_match), + }, +}; +module_platform_driver(ti_opp_supply_driver); + +MODULE_DESCRIPTION("Texas Instruments OMAP OPP Supply driver"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index dc63aba092e4..dfd23245f778 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -88,7 +88,6 @@ struct time_in_idle { * @policy: cpufreq policy. * @node: list_head to link all cpufreq_cooling_device together. * @idle_time: idle time stats - * @plat_get_static_power: callback to calculate the static power * * This structure is required for keeping information of each registered * cpufreq_cooling_device. @@ -104,7 +103,6 @@ struct cpufreq_cooling_device { struct cpufreq_policy *policy; struct list_head node; struct time_in_idle *idle_time; - get_static_t plat_get_static_power; }; static DEFINE_IDA(cpufreq_ida); @@ -319,60 +317,6 @@ static u32 get_load(struct cpufreq_cooling_device *cpufreq_cdev, int cpu, } /** - * get_static_power() - calculate the static power consumed by the cpus - * @cpufreq_cdev: struct &cpufreq_cooling_device for this cpu cdev - * @tz: thermal zone device in which we're operating - * @freq: frequency in KHz - * @power: pointer in which to store the calculated static power - * - * Calculate the static power consumed by the cpus described by - * @cpu_actor running at frequency @freq. This function relies on a - * platform specific function that should have been provided when the - * actor was registered. If it wasn't, the static power is assumed to - * be negligible. The calculated static power is stored in @power. - * - * Return: 0 on success, -E* on failure. - */ -static int get_static_power(struct cpufreq_cooling_device *cpufreq_cdev, - struct thermal_zone_device *tz, unsigned long freq, - u32 *power) -{ - struct dev_pm_opp *opp; - unsigned long voltage; - struct cpufreq_policy *policy = cpufreq_cdev->policy; - struct cpumask *cpumask = policy->related_cpus; - unsigned long freq_hz = freq * 1000; - struct device *dev; - - if (!cpufreq_cdev->plat_get_static_power) { - *power = 0; - return 0; - } - - dev = get_cpu_device(policy->cpu); - WARN_ON(!dev); - - opp = dev_pm_opp_find_freq_exact(dev, freq_hz, true); - if (IS_ERR(opp)) { - dev_warn_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n", - freq_hz, PTR_ERR(opp)); - return -EINVAL; - } - - voltage = dev_pm_opp_get_voltage(opp); - dev_pm_opp_put(opp); - - if (voltage == 0) { - dev_err_ratelimited(dev, "Failed to get voltage for frequency %lu\n", - freq_hz); - return -EINVAL; - } - - return cpufreq_cdev->plat_get_static_power(cpumask, tz->passive_delay, - voltage, power); -} - -/** * get_dynamic_power() - calculate the dynamic power * @cpufreq_cdev: &cpufreq_cooling_device for this cdev * @freq: current frequency @@ -491,8 +435,8 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev, u32 *power) { unsigned long freq; - int i = 0, cpu, ret; - u32 static_power, dynamic_power, total_load = 0; + int i = 0, cpu; + u32 total_load = 0; struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata; struct cpufreq_policy *policy = cpufreq_cdev->policy; u32 *load_cpu = NULL; @@ -522,22 +466,15 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev, cpufreq_cdev->last_load = total_load; - dynamic_power = get_dynamic_power(cpufreq_cdev, freq); - ret = get_static_power(cpufreq_cdev, tz, freq, &static_power); - if (ret) { - kfree(load_cpu); - return ret; - } + *power = get_dynamic_power(cpufreq_cdev, freq); if (load_cpu) { trace_thermal_power_cpu_get_power(policy->related_cpus, freq, - load_cpu, i, dynamic_power, - static_power); + load_cpu, i, *power); kfree(load_cpu); } - *power = static_power + dynamic_power; return 0; } @@ -561,8 +498,6 @@ static int cpufreq_state2power(struct thermal_cooling_device *cdev, unsigned long state, u32 *power) { unsigned int freq, num_cpus; - u32 static_power, dynamic_power; - int ret; struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata; /* Request state should be less than max_level */ @@ -572,13 +507,9 @@ static int cpufreq_state2power(struct thermal_cooling_device *cdev, num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus); freq = cpufreq_cdev->freq_table[state].frequency; - dynamic_power = cpu_freq_to_power(cpufreq_cdev, freq) * num_cpus; - ret = get_static_power(cpufreq_cdev, tz, freq, &static_power); - if (ret) - return ret; + *power = cpu_freq_to_power(cpufreq_cdev, freq) * num_cpus; - *power = static_power + dynamic_power; - return ret; + return 0; } /** @@ -606,21 +537,14 @@ static int cpufreq_power2state(struct thermal_cooling_device *cdev, unsigned long *state) { unsigned int cur_freq, target_freq; - int ret; - s32 dyn_power; - u32 last_load, normalised_power, static_power; + u32 last_load, normalised_power; struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata; struct cpufreq_policy *policy = cpufreq_cdev->policy; cur_freq = cpufreq_quick_get(policy->cpu); - ret = get_static_power(cpufreq_cdev, tz, cur_freq, &static_power); - if (ret) - return ret; - - dyn_power = power - static_power; - dyn_power = dyn_power > 0 ? dyn_power : 0; + power = power > 0 ? power : 0; last_load = cpufreq_cdev->last_load ?: 1; - normalised_power = (dyn_power * 100) / last_load; + normalised_power = (power * 100) / last_load; target_freq = cpu_power_to_freq(cpufreq_cdev, normalised_power); *state = get_level(cpufreq_cdev, target_freq); @@ -671,8 +595,6 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table, * @policy: cpufreq policy * Normally this should be same as cpufreq policy->related_cpus. * @capacitance: dynamic power coefficient for these cpus - * @plat_static_func: function to calculate the static power consumed by these - * cpus (optional) * * This interface function registers the cpufreq cooling device with the name * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq @@ -684,8 +606,7 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table, */ static struct thermal_cooling_device * __cpufreq_cooling_register(struct device_node *np, - struct cpufreq_policy *policy, u32 capacitance, - get_static_t plat_static_func) + struct cpufreq_policy *policy, u32 capacitance) { struct thermal_cooling_device *cdev; struct cpufreq_cooling_device *cpufreq_cdev; @@ -755,8 +676,6 @@ __cpufreq_cooling_register(struct device_node *np, } if (capacitance) { - cpufreq_cdev->plat_get_static_power = plat_static_func; - ret = update_freq_table(cpufreq_cdev, capacitance); if (ret) { cdev = ERR_PTR(ret); @@ -813,13 +732,12 @@ free_cdev: struct thermal_cooling_device * cpufreq_cooling_register(struct cpufreq_policy *policy) { - return __cpufreq_cooling_register(NULL, policy, 0, NULL); + return __cpufreq_cooling_register(NULL, policy, 0); } EXPORT_SYMBOL_GPL(cpufreq_cooling_register); /** * of_cpufreq_cooling_register - function to create cpufreq cooling device. - * @np: a valid struct device_node to the cooling device device tree node * @policy: cpufreq policy * * This interface function registers the cpufreq cooling device with the name @@ -827,86 +745,45 @@ EXPORT_SYMBOL_GPL(cpufreq_cooling_register); * cooling devices. Using this API, the cpufreq cooling device will be * linked to the device tree node provided. * - * Return: a valid struct thermal_cooling_device pointer on success, - * on failure, it returns a corresponding ERR_PTR(). - */ -struct thermal_cooling_device * -of_cpufreq_cooling_register(struct device_node *np, - struct cpufreq_policy *policy) -{ - if (!np) - return ERR_PTR(-EINVAL); - - return __cpufreq_cooling_register(np, policy, 0, NULL); -} -EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register); - -/** - * cpufreq_power_cooling_register() - create cpufreq cooling device with power extensions - * @policy: cpufreq policy - * @capacitance: dynamic power coefficient for these cpus - * @plat_static_func: function to calculate the static power consumed by these - * cpus (optional) - * - * This interface function registers the cpufreq cooling device with - * the name "thermal-cpufreq-%x". This api can support multiple - * instances of cpufreq cooling devices. Using this function, the - * cooling device will implement the power extensions by using a - * simple cpu power model. The cpus must have registered their OPPs - * using the OPP library. - * - * An optional @plat_static_func may be provided to calculate the - * static power consumed by these cpus. If the platform's static - * power consumption is unknown or negligible, make it NULL. - * - * Return: a valid struct thermal_cooling_device pointer on success, - * on failure, it returns a corresponding ERR_PTR(). - */ -struct thermal_cooling_device * -cpufreq_power_cooling_register(struct cpufreq_policy *policy, u32 capacitance, - get_static_t plat_static_func) -{ - return __cpufreq_cooling_register(NULL, policy, capacitance, - plat_static_func); -} -EXPORT_SYMBOL(cpufreq_power_cooling_register); - -/** - * of_cpufreq_power_cooling_register() - create cpufreq cooling device with power extensions - * @np: a valid struct device_node to the cooling device device tree node - * @policy: cpufreq policy - * @capacitance: dynamic power coefficient for these cpus - * @plat_static_func: function to calculate the static power consumed by these - * cpus (optional) - * - * This interface function registers the cpufreq cooling device with - * the name "thermal-cpufreq-%x". This api can support multiple - * instances of cpufreq cooling devices. Using this API, the cpufreq - * cooling device will be linked to the device tree node provided. * Using this function, the cooling device will implement the power * extensions by using a simple cpu power model. The cpus must have * registered their OPPs using the OPP library. * - * An optional @plat_static_func may be provided to calculate the - * static power consumed by these cpus. If the platform's static - * power consumption is unknown or negligible, make it NULL. + * It also takes into account, if property present in policy CPU node, the + * static power consumed by the cpu. * * Return: a valid struct thermal_cooling_device pointer on success, - * on failure, it returns a corresponding ERR_PTR(). + * and NULL on failure. */ struct thermal_cooling_device * -of_cpufreq_power_cooling_register(struct device_node *np, - struct cpufreq_policy *policy, - u32 capacitance, - get_static_t plat_static_func) +of_cpufreq_cooling_register(struct cpufreq_policy *policy) { - if (!np) - return ERR_PTR(-EINVAL); + struct device_node *np = of_get_cpu_node(policy->cpu, NULL); + struct thermal_cooling_device *cdev = NULL; + u32 capacitance = 0; + + if (!np) { + pr_err("cpu_cooling: OF node not available for cpu%d\n", + policy->cpu); + return NULL; + } + + if (of_find_property(np, "#cooling-cells", NULL)) { + of_property_read_u32(np, "dynamic-power-coefficient", + &capacitance); - return __cpufreq_cooling_register(np, policy, capacitance, - plat_static_func); + cdev = __cpufreq_cooling_register(np, policy, capacitance); + if (IS_ERR(cdev)) { + pr_err("cpu_cooling: cpu%d is not running as cooling device: %ld\n", + policy->cpu, PTR_ERR(cdev)); + cdev = NULL; + } + } + + of_node_put(np); + return cdev; } -EXPORT_SYMBOL(of_cpufreq_power_cooling_register); +EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register); /** * cpufreq_cooling_unregister - function to remove cpufreq cooling device. |