From baea35e4db17a72145c84a401f70d496c8ebf833 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 17 Jul 2019 11:20:17 +0530 Subject: opp: Not all power-domains are scalable A device may have multiple power-domains and not all of them may be scalable (i.e. support performance states). But dev_pm_opp_attach_genpd() doesn't take that into account currently. Fix that by not verifying the names argument with "power-domain-names" DT property and finding the index into the required-opps array. The names argument will anyway get verified later on when we call dev_pm_domain_attach_by_name(). Fixes: 6319aee10e53 ("opp: Attach genpds to devices from within OPP core") Reported-by: Rajendra Nayak Tested-by: Rajendra Nayak Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index c094d5d20fd7..08b830f84d8c 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1784,12 +1784,15 @@ static void _opp_detach_genpd(struct opp_table *opp_table) * * This helper needs to be called once with a list of all genpd to attach. * Otherwise the original device structure will be used instead by the OPP core. + * + * The order of entries in the names array must match the order in which + * "required-opps" are added in DT. */ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names) { struct opp_table *opp_table; struct device *virt_dev; - int index, ret = -EINVAL; + int index = 0, ret = -EINVAL; const char **name = names; opp_table = dev_pm_opp_get_opp_table(dev); @@ -1815,14 +1818,6 @@ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names goto unlock; while (*name) { - index = of_property_match_string(dev->of_node, - "power-domain-names", *name); - if (index < 0) { - dev_err(dev, "Failed to find power domain: %s (%d)\n", - *name, index); - goto err; - } - if (index >= opp_table->required_opp_count) { dev_err(dev, "Index can't be greater than required-opp-count - 1, %s (%d : %d)\n", *name, opp_table->required_opp_count, index); @@ -1843,6 +1838,7 @@ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names } opp_table->genpd_virt_devs[index] = virt_dev; + index++; name++; } -- cgit v1.2.3 From 17a8f868ae3e85a173843b1ac65e744e8585bc5a Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 8 Jul 2019 11:24:56 +0530 Subject: opp: Return genpd virtual devices from dev_pm_opp_attach_genpd() The cpufreq drivers don't need to do runtime PM operations on the virtual devices returned by dev_pm_domain_attach_by_name() and so the virtual devices weren't shared with the callers of dev_pm_opp_attach_genpd() earlier. But the IO device drivers would want to do that. This patch updates the prototype of dev_pm_opp_attach_genpd() to accept another argument to return the pointer to the array of genpd virtual devices. Reported-by: Rajendra Nayak Tested-by: Rajendra Nayak Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 6 +++++- include/linux/pm_opp.h | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 08b830f84d8c..bdb435822401 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1771,6 +1771,7 @@ static void _opp_detach_genpd(struct opp_table *opp_table) * dev_pm_opp_attach_genpd - Attach genpd(s) for the device and save virtual device pointer * @dev: Consumer device for which the genpd is getting attached. * @names: Null terminated array of pointers containing names of genpd to attach. + * @virt_devs: Pointer to return the array of virtual devices. * * Multiple generic power domains for a device are supported with the help of * virtual genpd devices, which are created for each consumer device - genpd @@ -1788,7 +1789,8 @@ static void _opp_detach_genpd(struct opp_table *opp_table) * The order of entries in the names array must match the order in which * "required-opps" are added in DT. */ -struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names) +struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, + const char **names, struct device ***virt_devs) { struct opp_table *opp_table; struct device *virt_dev; @@ -1842,6 +1844,8 @@ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names name++; } + if (virt_devs) + *virt_devs = opp_table->genpd_virt_devs; mutex_unlock(&opp_table->genpd_virt_dev_lock); return opp_table; diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index af5021f27cb7..5bdceca5125d 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -128,7 +128,7 @@ struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char * name); void dev_pm_opp_put_clkname(struct opp_table *opp_table); struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)); void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table); -struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names); +struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); void dev_pm_opp_detach_genpd(struct opp_table *opp_table); int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate); int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq); @@ -292,7 +292,7 @@ static inline struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const static inline void dev_pm_opp_put_clkname(struct opp_table *opp_table) {} -static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names) +static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs) { return ERR_PTR(-ENOTSUPP); } -- cgit v1.2.3 From 71419d84c216cee8da3b19fb843b4242f112cde4 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 25 Jul 2019 12:41:29 +0200 Subject: opp: Add dev_pm_opp_find_level_exact() Since the performance states in the OPP table are unique, implement a dev_pm_opp_find_level_exact() in order to be able to fetch a specific OPP. Signed-off-by: Niklas Cassel [ Viresh: Updated commit log ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 8 ++++++++ 2 files changed, 56 insertions(+) (limited to 'drivers') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index bdb435822401..0ee8c0133d3e 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -401,6 +401,54 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, } EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_exact); +/** + * dev_pm_opp_find_level_exact() - search for an exact level + * @dev: device for which we do this operation + * @level: level to search for + * + * Return: Searches for exact match in the opp table and returns pointer to the + * matching opp if found, else returns ERR_PTR in case of error and should + * be handled using IS_ERR. Error return values can be: + * EINVAL: for bad pointer + * ERANGE: no match found for search + * ENODEV: if device not found in list of registered devices + * + * The callers are required to call dev_pm_opp_put() for the returned OPP after + * use. + */ +struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, + unsigned int level) +{ + struct opp_table *opp_table; + struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + int r = PTR_ERR(opp_table); + + dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r); + return ERR_PTR(r); + } + + mutex_lock(&opp_table->lock); + + list_for_each_entry(temp_opp, &opp_table->opp_list, node) { + if (temp_opp->level == level) { + opp = temp_opp; + + /* Increment the reference count of OPP */ + dev_pm_opp_get(opp); + break; + } + } + + mutex_unlock(&opp_table->lock); + dev_pm_opp_put_opp_table(opp_table); + + return opp; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_exact); + static noinline struct dev_pm_opp *_find_freq_ceil(struct opp_table *opp_table, unsigned long *freq) { diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 5bdceca5125d..b8197ab014f2 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -96,6 +96,8 @@ unsigned long dev_pm_opp_get_suspend_opp_freq(struct device *dev); struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, unsigned long freq, bool available); +struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, + unsigned int level); struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, unsigned long *freq); @@ -200,6 +202,12 @@ static inline struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, return ERR_PTR(-ENOTSUPP); } +static inline struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, + unsigned int level) +{ + return ERR_PTR(-ENOTSUPP); +} + static inline struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, unsigned long *freq) { -- cgit v1.2.3 From 7d12709544b8b3fb9727a34a664b8380e1e3493a Mon Sep 17 00:00:00 2001 From: Sricharan R Date: Thu, 25 Jul 2019 12:41:31 +0200 Subject: cpufreq: qcom: Re-organise kryo cpufreq to use it for other nvmem based qcom socs The kryo cpufreq driver reads the nvmem cell and uses that data to populate the opps. There are other qcom cpufreq socs like krait which does similar thing. Except for the interpretation of the read data, rest of the driver is same for both the cases. So pull the common things out for reuse. Signed-off-by: Sricharan R [niklas.cassel@linaro.org: split dt-binding into a separate patch and do not rename the compatible string. Update MAINTAINERS file.] Signed-off-by: Niklas Cassel Reviewed-by: Ilia Lin Reviewed-by: Stephen Boyd Signed-off-by: Viresh Kumar --- MAINTAINERS | 4 +- drivers/cpufreq/Kconfig.arm | 4 +- drivers/cpufreq/Makefile | 2 +- drivers/cpufreq/qcom-cpufreq-kryo.c | 249 -------------------------------- drivers/cpufreq/qcom-cpufreq-nvmem.c | 273 +++++++++++++++++++++++++++++++++++ 5 files changed, 278 insertions(+), 254 deletions(-) delete mode 100644 drivers/cpufreq/qcom-cpufreq-kryo.c create mode 100644 drivers/cpufreq/qcom-cpufreq-nvmem.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 783569e3c4b4..cc73f908d5f9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13285,8 +13285,8 @@ QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096 M: Ilia Lin L: linux-pm@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/opp/kryo-cpufreq.txt -F: drivers/cpufreq/qcom-cpufreq-kryo.c +F: Documentation/devicetree/bindings/opp/qcom-nvmem-cpufreq.txt +F: drivers/cpufreq/qcom-cpufreq-nvmem.c QUALCOMM EMAC GIGABIT ETHERNET DRIVER M: Timur Tabi diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 56c31a78c692..b1aa485a28dd 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -120,8 +120,8 @@ config ARM_OMAP2PLUS_CPUFREQ depends on ARCH_OMAP2PLUS default ARCH_OMAP2PLUS -config ARM_QCOM_CPUFREQ_KRYO - tristate "Qualcomm Kryo based CPUFreq" +config ARM_QCOM_CPUFREQ_NVMEM + tristate "Qualcomm nvmem based CPUFreq" depends on ARM64 depends on QCOM_QFPROM depends on QCOM_SMEM diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 5a6c70d26c98..8572a918aa75 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -64,7 +64,7 @@ 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_QCOM_CPUFREQ_HW) += qcom-cpufreq-hw.o -obj-$(CONFIG_ARM_QCOM_CPUFREQ_KRYO) += qcom-cpufreq-kryo.o +obj-$(CONFIG_ARM_QCOM_CPUFREQ_NVMEM) += qcom-cpufreq-nvmem.o obj-$(CONFIG_ARM_RASPBERRYPI_CPUFREQ) += raspberrypi-cpufreq.o obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o obj-$(CONFIG_ARM_S3C2412_CPUFREQ) += s3c2412-cpufreq.o diff --git a/drivers/cpufreq/qcom-cpufreq-kryo.c b/drivers/cpufreq/qcom-cpufreq-kryo.c deleted file mode 100644 index dd64dcf89c74..000000000000 --- a/drivers/cpufreq/qcom-cpufreq-kryo.c +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2018, The Linux Foundation. All rights reserved. - */ - -/* - * In Certain QCOM SoCs like apq8096 and msm8996 that have KRYO processors, - * the CPU frequency subset and voltage value of each OPP varies - * based on the silicon variant in use. Qualcomm Process Voltage Scaling Tables - * defines the voltage and frequency value based on the msm-id in SMEM - * and speedbin blown in the efuse combination. - * The qcom-cpufreq-kryo driver reads the msm-id and efuse value from the SoC - * to provide the OPP framework with required information. - * This is used to determine the voltage and frequency value for each OPP of - * operating-points-v2 table when it is parsed by the OPP framework. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MSM_ID_SMEM 137 - -enum _msm_id { - MSM8996V3 = 0xF6ul, - APQ8096V3 = 0x123ul, - MSM8996SG = 0x131ul, - APQ8096SG = 0x138ul, -}; - -enum _msm8996_version { - MSM8996_V3, - MSM8996_SG, - NUM_OF_MSM8996_VERSIONS, -}; - -static struct platform_device *cpufreq_dt_pdev, *kryo_cpufreq_pdev; - -static enum _msm8996_version qcom_cpufreq_kryo_get_msm_id(void) -{ - size_t len; - u32 *msm_id; - enum _msm8996_version version; - - msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len); - if (IS_ERR(msm_id)) - return NUM_OF_MSM8996_VERSIONS; - - /* The first 4 bytes are format, next to them is the actual msm-id */ - msm_id++; - - switch ((enum _msm_id)*msm_id) { - case MSM8996V3: - case APQ8096V3: - version = MSM8996_V3; - break; - case MSM8996SG: - case APQ8096SG: - version = MSM8996_SG; - break; - default: - version = NUM_OF_MSM8996_VERSIONS; - } - - return version; -} - -static int qcom_cpufreq_kryo_probe(struct platform_device *pdev) -{ - struct opp_table **opp_tables; - enum _msm8996_version msm8996_version; - struct nvmem_cell *speedbin_nvmem; - struct device_node *np; - struct device *cpu_dev; - unsigned cpu; - u8 *speedbin; - u32 versions; - size_t len; - int ret; - - cpu_dev = get_cpu_device(0); - if (!cpu_dev) - return -ENODEV; - - msm8996_version = qcom_cpufreq_kryo_get_msm_id(); - if (NUM_OF_MSM8996_VERSIONS == msm8996_version) { - dev_err(cpu_dev, "Not Snapdragon 820/821!"); - return -ENODEV; - } - - np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); - if (!np) - return -ENOENT; - - ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu"); - if (!ret) { - of_node_put(np); - return -ENOENT; - } - - speedbin_nvmem = of_nvmem_cell_get(np, NULL); - of_node_put(np); - if (IS_ERR(speedbin_nvmem)) { - if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) - dev_err(cpu_dev, "Could not get nvmem cell: %ld\n", - PTR_ERR(speedbin_nvmem)); - return PTR_ERR(speedbin_nvmem); - } - - speedbin = nvmem_cell_read(speedbin_nvmem, &len); - nvmem_cell_put(speedbin_nvmem); - if (IS_ERR(speedbin)) - return PTR_ERR(speedbin); - - switch (msm8996_version) { - case MSM8996_V3: - versions = 1 << (unsigned int)(*speedbin); - break; - case MSM8996_SG: - versions = 1 << ((unsigned int)(*speedbin) + 4); - break; - default: - BUG(); - break; - } - kfree(speedbin); - - opp_tables = kcalloc(num_possible_cpus(), sizeof(*opp_tables), GFP_KERNEL); - if (!opp_tables) - return -ENOMEM; - - for_each_possible_cpu(cpu) { - cpu_dev = get_cpu_device(cpu); - if (NULL == cpu_dev) { - ret = -ENODEV; - goto free_opp; - } - - opp_tables[cpu] = dev_pm_opp_set_supported_hw(cpu_dev, - &versions, 1); - if (IS_ERR(opp_tables[cpu])) { - ret = PTR_ERR(opp_tables[cpu]); - dev_err(cpu_dev, "Failed to set supported hardware\n"); - goto free_opp; - } - } - - cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, - NULL, 0); - if (!IS_ERR(cpufreq_dt_pdev)) { - platform_set_drvdata(pdev, opp_tables); - return 0; - } - - ret = PTR_ERR(cpufreq_dt_pdev); - dev_err(cpu_dev, "Failed to register platform device\n"); - -free_opp: - for_each_possible_cpu(cpu) { - if (IS_ERR_OR_NULL(opp_tables[cpu])) - break; - dev_pm_opp_put_supported_hw(opp_tables[cpu]); - } - kfree(opp_tables); - - return ret; -} - -static int qcom_cpufreq_kryo_remove(struct platform_device *pdev) -{ - struct opp_table **opp_tables = platform_get_drvdata(pdev); - unsigned int cpu; - - platform_device_unregister(cpufreq_dt_pdev); - - for_each_possible_cpu(cpu) - dev_pm_opp_put_supported_hw(opp_tables[cpu]); - - kfree(opp_tables); - - return 0; -} - -static struct platform_driver qcom_cpufreq_kryo_driver = { - .probe = qcom_cpufreq_kryo_probe, - .remove = qcom_cpufreq_kryo_remove, - .driver = { - .name = "qcom-cpufreq-kryo", - }, -}; - -static const struct of_device_id qcom_cpufreq_kryo_match_list[] __initconst = { - { .compatible = "qcom,apq8096", }, - { .compatible = "qcom,msm8996", }, - {} -}; - -/* - * Since the driver depends on smem and nvmem drivers, which may - * return EPROBE_DEFER, all the real activity is done in the probe, - * which may be defered as well. The init here is only registering - * the driver and the platform device. - */ -static int __init qcom_cpufreq_kryo_init(void) -{ - struct device_node *np = of_find_node_by_path("/"); - const struct of_device_id *match; - int ret; - - if (!np) - return -ENODEV; - - match = of_match_node(qcom_cpufreq_kryo_match_list, np); - of_node_put(np); - if (!match) - return -ENODEV; - - ret = platform_driver_register(&qcom_cpufreq_kryo_driver); - if (unlikely(ret < 0)) - return ret; - - kryo_cpufreq_pdev = platform_device_register_simple( - "qcom-cpufreq-kryo", -1, NULL, 0); - ret = PTR_ERR_OR_ZERO(kryo_cpufreq_pdev); - if (0 == ret) - return 0; - - platform_driver_unregister(&qcom_cpufreq_kryo_driver); - return ret; -} -module_init(qcom_cpufreq_kryo_init); - -static void __exit qcom_cpufreq_kryo_exit(void) -{ - platform_device_unregister(kryo_cpufreq_pdev); - platform_driver_unregister(&qcom_cpufreq_kryo_driver); -} -module_exit(qcom_cpufreq_kryo_exit); - -MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Kryo CPUfreq driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c new file mode 100644 index 000000000000..fd08120768af --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +/* + * In Certain QCOM SoCs like apq8096 and msm8996 that have KRYO processors, + * the CPU frequency subset and voltage value of each OPP varies + * based on the silicon variant in use. Qualcomm Process Voltage Scaling Tables + * defines the voltage and frequency value based on the msm-id in SMEM + * and speedbin blown in the efuse combination. + * The qcom-cpufreq-nvmem driver reads the msm-id and efuse value from the SoC + * to provide the OPP framework with required information. + * This is used to determine the voltage and frequency value for each OPP of + * operating-points-v2 table when it is parsed by the OPP framework. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSM_ID_SMEM 137 + +enum _msm_id { + MSM8996V3 = 0xF6ul, + APQ8096V3 = 0x123ul, + MSM8996SG = 0x131ul, + APQ8096SG = 0x138ul, +}; + +enum _msm8996_version { + MSM8996_V3, + MSM8996_SG, + NUM_OF_MSM8996_VERSIONS, +}; + +static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev; + +static enum _msm8996_version qcom_cpufreq_get_msm_id(void) +{ + size_t len; + u32 *msm_id; + enum _msm8996_version version; + + msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len); + if (IS_ERR(msm_id)) + return NUM_OF_MSM8996_VERSIONS; + + /* The first 4 bytes are format, next to them is the actual msm-id */ + msm_id++; + + switch ((enum _msm_id)*msm_id) { + case MSM8996V3: + case APQ8096V3: + version = MSM8996_V3; + break; + case MSM8996SG: + case APQ8096SG: + version = MSM8996_SG; + break; + default: + version = NUM_OF_MSM8996_VERSIONS; + } + + return version; +} + +static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, + struct nvmem_cell *speedbin_nvmem, + u32 *versions) +{ + size_t len; + u8 *speedbin; + enum _msm8996_version msm8996_version; + + msm8996_version = qcom_cpufreq_get_msm_id(); + if (NUM_OF_MSM8996_VERSIONS == msm8996_version) { + dev_err(cpu_dev, "Not Snapdragon 820/821!"); + return -ENODEV; + } + + speedbin = nvmem_cell_read(speedbin_nvmem, &len); + if (IS_ERR(speedbin)) + return PTR_ERR(speedbin); + + switch (msm8996_version) { + case MSM8996_V3: + *versions = 1 << (unsigned int)(*speedbin); + break; + case MSM8996_SG: + *versions = 1 << ((unsigned int)(*speedbin) + 4); + break; + default: + BUG(); + break; + } + + kfree(speedbin); + return 0; +} + +static int qcom_cpufreq_probe(struct platform_device *pdev) +{ + struct opp_table **opp_tables; + int (*get_version)(struct device *cpu_dev, + struct nvmem_cell *speedbin_nvmem, + u32 *versions); + struct nvmem_cell *speedbin_nvmem; + struct device_node *np; + struct device *cpu_dev; + unsigned cpu; + u32 versions; + const struct of_device_id *match; + int ret; + + cpu_dev = get_cpu_device(0); + if (!cpu_dev) + return -ENODEV; + + match = pdev->dev.platform_data; + get_version = match->data; + if (!get_version) + return -ENODEV; + + np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); + if (!np) + return -ENOENT; + + ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu"); + if (!ret) { + of_node_put(np); + return -ENOENT; + } + + speedbin_nvmem = of_nvmem_cell_get(np, NULL); + of_node_put(np); + if (IS_ERR(speedbin_nvmem)) { + if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) + dev_err(cpu_dev, "Could not get nvmem cell: %ld\n", + PTR_ERR(speedbin_nvmem)); + return PTR_ERR(speedbin_nvmem); + } + + ret = get_version(cpu_dev, speedbin_nvmem, &versions); + nvmem_cell_put(speedbin_nvmem); + if (ret) + return ret; + + opp_tables = kcalloc(num_possible_cpus(), sizeof(*opp_tables), GFP_KERNEL); + if (!opp_tables) + return -ENOMEM; + + for_each_possible_cpu(cpu) { + cpu_dev = get_cpu_device(cpu); + if (NULL == cpu_dev) { + ret = -ENODEV; + goto free_opp; + } + + opp_tables[cpu] = dev_pm_opp_set_supported_hw(cpu_dev, + &versions, 1); + if (IS_ERR(opp_tables[cpu])) { + ret = PTR_ERR(opp_tables[cpu]); + dev_err(cpu_dev, "Failed to set supported hardware\n"); + goto free_opp; + } + } + + cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, + NULL, 0); + if (!IS_ERR(cpufreq_dt_pdev)) { + platform_set_drvdata(pdev, opp_tables); + return 0; + } + + ret = PTR_ERR(cpufreq_dt_pdev); + dev_err(cpu_dev, "Failed to register platform device\n"); + +free_opp: + for_each_possible_cpu(cpu) { + if (IS_ERR_OR_NULL(opp_tables[cpu])) + break; + dev_pm_opp_put_supported_hw(opp_tables[cpu]); + } + kfree(opp_tables); + + return ret; +} + +static int qcom_cpufreq_remove(struct platform_device *pdev) +{ + struct opp_table **opp_tables = platform_get_drvdata(pdev); + unsigned int cpu; + + platform_device_unregister(cpufreq_dt_pdev); + + for_each_possible_cpu(cpu) + dev_pm_opp_put_supported_hw(opp_tables[cpu]); + + kfree(opp_tables); + + return 0; +} + +static struct platform_driver qcom_cpufreq_driver = { + .probe = qcom_cpufreq_probe, + .remove = qcom_cpufreq_remove, + .driver = { + .name = "qcom-cpufreq-nvmem", + }, +}; + +static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { + { .compatible = "qcom,apq8096", + .data = qcom_cpufreq_kryo_name_version }, + { .compatible = "qcom,msm8996", + .data = qcom_cpufreq_kryo_name_version }, + {}, +}; + +/* + * Since the driver depends on smem and nvmem drivers, which may + * return EPROBE_DEFER, all the real activity is done in the probe, + * which may be defered as well. The init here is only registering + * the driver and the platform device. + */ +static int __init qcom_cpufreq_init(void) +{ + struct device_node *np = of_find_node_by_path("/"); + const struct of_device_id *match; + int ret; + + if (!np) + return -ENODEV; + + match = of_match_node(qcom_cpufreq_match_list, np); + of_node_put(np); + if (!match) + return -ENODEV; + + ret = platform_driver_register(&qcom_cpufreq_driver); + if (unlikely(ret < 0)) + return ret; + + cpufreq_pdev = platform_device_register_data(NULL, "qcom-cpufreq-nvmem", + -1, match, sizeof(*match)); + ret = PTR_ERR_OR_ZERO(cpufreq_pdev); + if (0 == ret) + return 0; + + platform_driver_unregister(&qcom_cpufreq_driver); + return ret; +} +module_init(qcom_cpufreq_init); + +static void __exit qcom_cpufreq_exit(void) +{ + platform_device_unregister(cpufreq_pdev); + platform_driver_unregister(&qcom_cpufreq_driver); +} +module_exit(qcom_cpufreq_exit); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. CPUfreq driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 57f2f8b4aa0c6b41a284da82bfa40dc3b2abe9a5 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 25 Jul 2019 12:41:33 +0200 Subject: cpufreq: qcom: Refactor the driver to make it easier to extend Refactor the driver to make it easier to extend in a later commit. Create a driver struct to collect all common resources, in order to make it easier to free up all common resources. Create a driver match_data struct to make it easier to extend the driver with support for new features that might only be supported on certain SoCs. Co-developed-by: Jorge Ramirez-Ortiz Signed-off-by: Jorge Ramirez-Ortiz Signed-off-by: Niklas Cassel Reviewed-by: Ilia Lin Signed-off-by: Viresh Kumar --- drivers/cpufreq/qcom-cpufreq-nvmem.c | 123 ++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 44 deletions(-) (limited to 'drivers') diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c index fd08120768af..2d798a1685c5 100644 --- a/drivers/cpufreq/qcom-cpufreq-nvmem.c +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -43,6 +43,20 @@ enum _msm8996_version { NUM_OF_MSM8996_VERSIONS, }; +struct qcom_cpufreq_drv; + +struct qcom_cpufreq_match_data { + int (*get_version)(struct device *cpu_dev, + struct nvmem_cell *speedbin_nvmem, + struct qcom_cpufreq_drv *drv); +}; + +struct qcom_cpufreq_drv { + struct opp_table **opp_tables; + u32 versions; + const struct qcom_cpufreq_match_data *data; +}; + static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev; static enum _msm8996_version qcom_cpufreq_get_msm_id(void) @@ -76,7 +90,7 @@ static enum _msm8996_version qcom_cpufreq_get_msm_id(void) static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, struct nvmem_cell *speedbin_nvmem, - u32 *versions) + struct qcom_cpufreq_drv *drv) { size_t len; u8 *speedbin; @@ -94,10 +108,10 @@ static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, switch (msm8996_version) { case MSM8996_V3: - *versions = 1 << (unsigned int)(*speedbin); + drv->versions = 1 << (unsigned int)(*speedbin); break; case MSM8996_SG: - *versions = 1 << ((unsigned int)(*speedbin) + 4); + drv->versions = 1 << ((unsigned int)(*speedbin) + 4); break; default: BUG(); @@ -108,17 +122,17 @@ static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, return 0; } +static const struct qcom_cpufreq_match_data match_data_kryo = { + .get_version = qcom_cpufreq_kryo_name_version, +}; + static int qcom_cpufreq_probe(struct platform_device *pdev) { - struct opp_table **opp_tables; - int (*get_version)(struct device *cpu_dev, - struct nvmem_cell *speedbin_nvmem, - u32 *versions); + struct qcom_cpufreq_drv *drv; struct nvmem_cell *speedbin_nvmem; struct device_node *np; struct device *cpu_dev; unsigned cpu; - u32 versions; const struct of_device_id *match; int ret; @@ -126,11 +140,6 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) if (!cpu_dev) return -ENODEV; - match = pdev->dev.platform_data; - get_version = match->data; - if (!get_version) - return -ENODEV; - np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); if (!np) return -ENOENT; @@ -141,23 +150,43 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) return -ENOENT; } - speedbin_nvmem = of_nvmem_cell_get(np, NULL); - of_node_put(np); - if (IS_ERR(speedbin_nvmem)) { - if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) - dev_err(cpu_dev, "Could not get nvmem cell: %ld\n", - PTR_ERR(speedbin_nvmem)); - return PTR_ERR(speedbin_nvmem); + drv = kzalloc(sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + match = pdev->dev.platform_data; + drv->data = match->data; + if (!drv->data) { + ret = -ENODEV; + goto free_drv; } - ret = get_version(cpu_dev, speedbin_nvmem, &versions); - nvmem_cell_put(speedbin_nvmem); - if (ret) - return ret; + if (drv->data->get_version) { + speedbin_nvmem = of_nvmem_cell_get(np, NULL); + if (IS_ERR(speedbin_nvmem)) { + if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) + dev_err(cpu_dev, + "Could not get nvmem cell: %ld\n", + PTR_ERR(speedbin_nvmem)); + ret = PTR_ERR(speedbin_nvmem); + goto free_drv; + } - opp_tables = kcalloc(num_possible_cpus(), sizeof(*opp_tables), GFP_KERNEL); - if (!opp_tables) - return -ENOMEM; + ret = drv->data->get_version(cpu_dev, speedbin_nvmem, drv); + if (ret) { + nvmem_cell_put(speedbin_nvmem); + goto free_drv; + } + nvmem_cell_put(speedbin_nvmem); + } + of_node_put(np); + + drv->opp_tables = kcalloc(num_possible_cpus(), sizeof(*drv->opp_tables), + GFP_KERNEL); + if (!drv->opp_tables) { + ret = -ENOMEM; + goto free_drv; + } for_each_possible_cpu(cpu) { cpu_dev = get_cpu_device(cpu); @@ -166,19 +195,23 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) goto free_opp; } - opp_tables[cpu] = dev_pm_opp_set_supported_hw(cpu_dev, - &versions, 1); - if (IS_ERR(opp_tables[cpu])) { - ret = PTR_ERR(opp_tables[cpu]); - dev_err(cpu_dev, "Failed to set supported hardware\n"); - goto free_opp; + if (drv->data->get_version) { + drv->opp_tables[cpu] = + dev_pm_opp_set_supported_hw(cpu_dev, + &drv->versions, 1); + if (IS_ERR(drv->opp_tables[cpu])) { + ret = PTR_ERR(drv->opp_tables[cpu]); + dev_err(cpu_dev, + "Failed to set supported hardware\n"); + goto free_opp; + } } } cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); if (!IS_ERR(cpufreq_dt_pdev)) { - platform_set_drvdata(pdev, opp_tables); + platform_set_drvdata(pdev, drv); return 0; } @@ -187,26 +220,30 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) free_opp: for_each_possible_cpu(cpu) { - if (IS_ERR_OR_NULL(opp_tables[cpu])) + if (IS_ERR_OR_NULL(drv->opp_tables[cpu])) break; - dev_pm_opp_put_supported_hw(opp_tables[cpu]); + dev_pm_opp_put_supported_hw(drv->opp_tables[cpu]); } - kfree(opp_tables); + kfree(drv->opp_tables); +free_drv: + kfree(drv); return ret; } static int qcom_cpufreq_remove(struct platform_device *pdev) { - struct opp_table **opp_tables = platform_get_drvdata(pdev); + struct qcom_cpufreq_drv *drv = platform_get_drvdata(pdev); unsigned int cpu; platform_device_unregister(cpufreq_dt_pdev); for_each_possible_cpu(cpu) - dev_pm_opp_put_supported_hw(opp_tables[cpu]); + if (drv->opp_tables[cpu]) + dev_pm_opp_put_supported_hw(drv->opp_tables[cpu]); - kfree(opp_tables); + kfree(drv->opp_tables); + kfree(drv); return 0; } @@ -220,10 +257,8 @@ static struct platform_driver qcom_cpufreq_driver = { }; static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { - { .compatible = "qcom,apq8096", - .data = qcom_cpufreq_kryo_name_version }, - { .compatible = "qcom,msm8996", - .data = qcom_cpufreq_kryo_name_version }, + { .compatible = "qcom,apq8096", .data = &match_data_kryo }, + { .compatible = "qcom,msm8996", .data = &match_data_kryo }, {}, }; -- cgit v1.2.3 From 1cb8339ca225601610b11c7a47e69faa48943077 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 25 Jul 2019 12:41:35 +0200 Subject: cpufreq: qcom: Add support for qcs404 on nvmem driver Add support for qcs404 on nvmem driver. The qcs404 SoC has support for Core Power Reduction (CPR), which is implemented as a power domain provider, therefore add optional support in this driver to attach to a genpd power domain. Co-developed-by: Jorge Ramirez-Ortiz Signed-off-by: Jorge Ramirez-Ortiz Signed-off-by: Niklas Cassel Signed-off-by: Viresh Kumar --- drivers/cpufreq/qcom-cpufreq-nvmem.c | 50 +++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c index 2d798a1685c5..f0d2d5035413 100644 --- a/drivers/cpufreq/qcom-cpufreq-nvmem.c +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -49,10 +50,12 @@ struct qcom_cpufreq_match_data { int (*get_version)(struct device *cpu_dev, struct nvmem_cell *speedbin_nvmem, struct qcom_cpufreq_drv *drv); + const char **genpd_names; }; struct qcom_cpufreq_drv { struct opp_table **opp_tables; + struct opp_table **genpd_opp_tables; u32 versions; const struct qcom_cpufreq_match_data *data; }; @@ -126,6 +129,12 @@ static const struct qcom_cpufreq_match_data match_data_kryo = { .get_version = qcom_cpufreq_kryo_name_version, }; +static const char *qcs404_genpd_names[] = { "cpr", NULL }; + +static const struct qcom_cpufreq_match_data match_data_qcs404 = { + .genpd_names = qcs404_genpd_names, +}; + static int qcom_cpufreq_probe(struct platform_device *pdev) { struct qcom_cpufreq_drv *drv; @@ -188,11 +197,19 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) goto free_drv; } + drv->genpd_opp_tables = kcalloc(num_possible_cpus(), + sizeof(*drv->genpd_opp_tables), + GFP_KERNEL); + if (!drv->genpd_opp_tables) { + ret = -ENOMEM; + goto free_opp; + } + for_each_possible_cpu(cpu) { cpu_dev = get_cpu_device(cpu); if (NULL == cpu_dev) { ret = -ENODEV; - goto free_opp; + goto free_genpd_opp; } if (drv->data->get_version) { @@ -203,7 +220,22 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) ret = PTR_ERR(drv->opp_tables[cpu]); dev_err(cpu_dev, "Failed to set supported hardware\n"); - goto free_opp; + goto free_genpd_opp; + } + } + + if (drv->data->genpd_names) { + drv->genpd_opp_tables[cpu] = + dev_pm_opp_attach_genpd(cpu_dev, + drv->data->genpd_names, + NULL); + if (IS_ERR(drv->genpd_opp_tables[cpu])) { + ret = PTR_ERR(drv->genpd_opp_tables[cpu]); + if (ret != -EPROBE_DEFER) + dev_err(cpu_dev, + "Could not attach to pm_domain: %d\n", + ret); + goto free_genpd_opp; } } } @@ -218,6 +250,13 @@ static int qcom_cpufreq_probe(struct platform_device *pdev) ret = PTR_ERR(cpufreq_dt_pdev); dev_err(cpu_dev, "Failed to register platform device\n"); +free_genpd_opp: + for_each_possible_cpu(cpu) { + if (IS_ERR_OR_NULL(drv->genpd_opp_tables[cpu])) + break; + dev_pm_opp_detach_genpd(drv->genpd_opp_tables[cpu]); + } + kfree(drv->genpd_opp_tables); free_opp: for_each_possible_cpu(cpu) { if (IS_ERR_OR_NULL(drv->opp_tables[cpu])) @@ -238,11 +277,15 @@ static int qcom_cpufreq_remove(struct platform_device *pdev) platform_device_unregister(cpufreq_dt_pdev); - for_each_possible_cpu(cpu) + for_each_possible_cpu(cpu) { if (drv->opp_tables[cpu]) dev_pm_opp_put_supported_hw(drv->opp_tables[cpu]); + if (drv->genpd_opp_tables[cpu]) + dev_pm_opp_detach_genpd(drv->genpd_opp_tables[cpu]); + } kfree(drv->opp_tables); + kfree(drv->genpd_opp_tables); kfree(drv); return 0; @@ -259,6 +302,7 @@ static struct platform_driver qcom_cpufreq_driver = { static const struct of_device_id qcom_cpufreq_match_list[] __initconst = { { .compatible = "qcom,apq8096", .data = &match_data_kryo }, { .compatible = "qcom,msm8996", .data = &match_data_kryo }, + { .compatible = "qcom,qcs404", .data = &match_data_qcs404 }, {}, }; -- cgit v1.2.3 From 248b5f297cf809b6a61e38db22049011f619518b Mon Sep 17 00:00:00 2001 From: Jorge Ramirez-Ortiz Date: Thu, 25 Jul 2019 12:41:36 +0200 Subject: cpufreq: Add qcs404 to cpufreq-dt-platdev blacklist Add qcs404 to cpufreq-dt-platdev blacklist. Signed-off-by: Jorge Ramirez-Ortiz Co-developed-by: Niklas Cassel Signed-off-by: Niklas Cassel Signed-off-by: Viresh Kumar --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 03dc4244ab00..ec6ef996e637 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -123,6 +123,7 @@ static const struct of_device_id blacklist[] __initconst = { { .compatible = "qcom,apq8096", }, { .compatible = "qcom,msm8996", }, + { .compatible = "qcom,qcs404", }, { .compatible = "st,stih407", }, { .compatible = "st,stih410", }, -- cgit v1.2.3