From dcb879fbf7e3a4e3d96cbc9ef6c111c653ff21b0 Mon Sep 17 00:00:00 2001 From: Shaleen Agrawal Date: Wed, 19 Dec 2018 16:45:48 -0800 Subject: [PATCH] cpufreq: Add snapshot of qcom-cpufreq driver This is a snapshot of qcom-cpufreq as of msm-4.19 commit 9a1757a1cb90 ("qcom-cpufreq: Add missing cpufreq registers when use common table"). Change-Id: I4458f50dff9e9d44ec56d0214a60bf26673706a6 Signed-off-by: Shaleen Agrawal --- drivers/cpufreq/Kconfig.arm | 6 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/cpufreq.c | 16 +- drivers/cpufreq/qcom-cpufreq.c | 548 +++++++++++++++++++++++++++++++++ include/linux/cpufreq.h | 1 + include/linux/cpuhotplug.h | 6 + 6 files changed, 568 insertions(+), 10 deletions(-) create mode 100644 drivers/cpufreq/qcom-cpufreq.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index a905796f7f85..f20e5fa478c8 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -338,3 +338,9 @@ config ARM_PXA2xx_CPUFREQ This add the CPUFreq driver support for Intel PXA2xx SOCs. If in doubt, say N. + +config CPU_FREQ_MSM + bool "MSM CPUFreq support" + depends on CPU_FREQ + help + This enables the CPUFreq driver for Qualcomm Technologies, Inc. CPUs. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 33d6b64f9203..99bcc45f67c5 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_ARM_IMX_CPUFREQ_DT) += imx-cpufreq-dt.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_CPU_FREQ_MSM) += qcom-cpufreq.o obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index d88321051130..9a2a51ec68b4 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1130,7 +1130,8 @@ static int cpufreq_add_policy_cpu(struct cpufreq_policy *policy, unsigned int cp if (has_target()) { ret = cpufreq_start_governor(policy); if (ret) - pr_err("%s: Failed to start governor\n", __func__); + pr_err("%s: Failed to start governor for CPU%u, policy CPU%u\n", + __func__, cpu, policy->cpu); } up_write(&policy->rwsem); return ret; @@ -2162,15 +2163,6 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy, pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n", policy->cpu, target_freq, relation, old_target_freq); - /* - * This might look like a redundant call as we are checking it again - * after finding index. But it is left intentionally for cases where - * exactly same freq is called again and so we can save on few function - * calls. - */ - if (target_freq == policy->cur) - return 0; - /* Save last value to restore later on errors */ policy->restore_freq = policy->cur; @@ -2430,6 +2422,10 @@ int cpufreq_set_policy(struct cpufreq_policy *policy, if (ret) return ret; + /* adjust if necessary - hardware incompatibility */ + blocking_notifier_call_chain(&cpufreq_policy_notifier_list, + CPUFREQ_INCOMPATIBLE, new_policy); + policy->min = new_policy->min; policy->max = new_policy->max; trace_cpu_frequency_limits(policy); diff --git a/drivers/cpufreq/qcom-cpufreq.c b/drivers/cpufreq/qcom-cpufreq.c new file mode 100644 index 000000000000..2797f2f112f6 --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq.c @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/cpufreq/qcom-cpufreq.c + * + * MSM architecture cpufreq driver + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2019, The Linux Foundation. All rights reserved. + * Author: Mike A. Chan + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_MUTEX(l2bw_lock); + +static struct thermal_cooling_device *cdev[NR_CPUS]; +static struct clk *cpu_clk[NR_CPUS]; +static struct clk *l2_clk; +static DEFINE_PER_CPU(struct cpufreq_frequency_table *, freq_table); +static bool hotplug_ready; + +struct cpufreq_suspend_t { + struct mutex suspend_mutex; + int device_suspended; +}; + +static DEFINE_PER_CPU(struct cpufreq_suspend_t, suspend_data); +static DEFINE_PER_CPU(int, cached_resolve_idx); +static DEFINE_PER_CPU(unsigned int, cached_resolve_freq); + +static int set_cpu_freq(struct cpufreq_policy *policy, unsigned int new_freq, + unsigned int index) +{ + int ret = 0; + struct cpufreq_freqs freqs; + unsigned long rate; + + freqs.old = policy->cur; + freqs.new = new_freq; + freqs.cpu = policy->cpu; + + trace_cpu_frequency_switch_start(freqs.old, freqs.new, policy->cpu); + cpufreq_freq_transition_begin(policy, &freqs); + + rate = new_freq * 1000; + rate = clk_round_rate(cpu_clk[policy->cpu], rate); + ret = clk_set_rate(cpu_clk[policy->cpu], rate); + cpufreq_freq_transition_end(policy, &freqs, ret); + if (!ret) { + arch_set_freq_scale(policy->related_cpus, new_freq, + policy->cpuinfo.max_freq); + trace_cpu_frequency_switch_end(policy->cpu); + } + + return ret; +} + +static int msm_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int ret = 0; + int index; + struct cpufreq_frequency_table *table; + int first_cpu = cpumask_first(policy->related_cpus); + + mutex_lock(&per_cpu(suspend_data, policy->cpu).suspend_mutex); + + if (target_freq == policy->cur) + goto done; + + if (per_cpu(suspend_data, policy->cpu).device_suspended) { + pr_debug("cpufreq: cpu%d scheduling frequency change in suspend\n", + policy->cpu); + ret = -EFAULT; + goto done; + } + + table = policy->freq_table; + if (per_cpu(cached_resolve_freq, first_cpu) == target_freq) + index = per_cpu(cached_resolve_idx, first_cpu); + else + index = cpufreq_frequency_table_target(policy, target_freq, + relation); + + pr_debug("CPU[%d] target %d relation %d (%d-%d) selected %d\n", + policy->cpu, target_freq, relation, + policy->min, policy->max, table[index].frequency); + + ret = set_cpu_freq(policy, table[index].frequency, + table[index].driver_data); +done: + mutex_unlock(&per_cpu(suspend_data, policy->cpu).suspend_mutex); + return ret; +} + +static unsigned int msm_cpufreq_resolve_freq(struct cpufreq_policy *policy, + unsigned int target_freq) +{ + int index; + int first_cpu = cpumask_first(policy->related_cpus); + unsigned int freq; + + index = cpufreq_frequency_table_target(policy, target_freq, + CPUFREQ_RELATION_L); + freq = policy->freq_table[index].frequency; + + per_cpu(cached_resolve_idx, first_cpu) = index; + per_cpu(cached_resolve_freq, first_cpu) = freq; + + return freq; +} + +static int msm_cpufreq_verify(struct cpufreq_policy *policy) +{ + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + return 0; +} + +static unsigned int msm_cpufreq_get_freq(unsigned int cpu) +{ + return clk_get_rate(cpu_clk[cpu]) / 1000; +} + +static int msm_cpufreq_init(struct cpufreq_policy *policy) +{ + int cur_freq; + int index; + int ret = 0; + struct cpufreq_frequency_table *table = + per_cpu(freq_table, policy->cpu); + int cpu; + + /* + * In some SoC, some cores are clocked by same source, and their + * frequencies can not be changed independently. Find all other + * CPUs that share same clock, and mark them as controlled by + * same policy. + */ + for_each_possible_cpu(cpu) + if (cpu_clk[cpu] == cpu_clk[policy->cpu]) + cpumask_set_cpu(cpu, policy->cpus); + + policy->freq_table = table; + ret = cpufreq_table_validate_and_sort(policy); + if (ret) { + pr_err("cpufreq: failed to get policy min/max\n"); + return ret; + } + + cur_freq = clk_get_rate(cpu_clk[policy->cpu])/1000; + + index = cpufreq_frequency_table_target(policy, cur_freq, + CPUFREQ_RELATION_H); + /* + * Call set_cpu_freq unconditionally so that when cpu is set to + * online, frequency limit will always be updated. + */ + ret = set_cpu_freq(policy, table[index].frequency, + table[index].driver_data); + if (ret) + return ret; + pr_debug("cpufreq: cpu%d init at %d switching to %d\n", + policy->cpu, cur_freq, table[index].frequency); + policy->cur = table[index].frequency; + policy->dvfs_possible_from_any_cpu = true; + + return 0; +} + +static int qcom_cpufreq_dead_cpu(unsigned int cpu) +{ + /* Fail hotplug until this driver can get CPU clocks */ + if (!hotplug_ready) + return -EINVAL; + + clk_unprepare(cpu_clk[cpu]); + clk_unprepare(l2_clk); + return 0; +} + +static int qcom_cpufreq_up_cpu(unsigned int cpu) +{ + int rc; + + /* Fail hotplug until this driver can get CPU clocks */ + if (!hotplug_ready) + return -EINVAL; + + rc = clk_prepare(l2_clk); + if (rc < 0) + return rc; + rc = clk_prepare(cpu_clk[cpu]); + if (rc < 0) + clk_unprepare(l2_clk); + return rc; +} + +static int qcom_cpufreq_dying_cpu(unsigned int cpu) +{ + /* Fail hotplug until this driver can get CPU clocks */ + if (!hotplug_ready) + return -EINVAL; + + clk_disable(cpu_clk[cpu]); + clk_disable(l2_clk); + return 0; +} + +static int qcom_cpufreq_starting_cpu(unsigned int cpu) +{ + int rc; + + /* Fail hotplug until this driver can get CPU clocks */ + if (!hotplug_ready) + return -EINVAL; + + rc = clk_enable(l2_clk); + if (rc < 0) + return rc; + rc = clk_enable(cpu_clk[cpu]); + if (rc < 0) + clk_disable(l2_clk); + return rc; +} + +static int msm_cpufreq_suspend(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + mutex_lock(&per_cpu(suspend_data, cpu).suspend_mutex); + per_cpu(suspend_data, cpu).device_suspended = 1; + mutex_unlock(&per_cpu(suspend_data, cpu).suspend_mutex); + } + + return NOTIFY_DONE; +} + +static int msm_cpufreq_resume(void) +{ + int cpu, ret; + struct cpufreq_policy policy; + + for_each_possible_cpu(cpu) { + per_cpu(suspend_data, cpu).device_suspended = 0; + } + + /* + * Freq request might be rejected during suspend, resulting + * in policy->cur violating min/max constraint. + * Correct the frequency as soon as possible. + */ + get_online_cpus(); + for_each_online_cpu(cpu) { + ret = cpufreq_get_policy(&policy, cpu); + if (ret) + continue; + if (policy.cur <= policy.max && policy.cur >= policy.min) + continue; + cpufreq_update_policy(cpu); + } + put_online_cpus(); + + return NOTIFY_DONE; +} + +static int msm_cpufreq_pm_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + return msm_cpufreq_resume(); + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + return msm_cpufreq_suspend(); + default: + return NOTIFY_DONE; + } +} + +static struct notifier_block msm_cpufreq_pm_notifier = { + .notifier_call = msm_cpufreq_pm_event, +}; + +static struct freq_attr *msm_freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static void msm_cpufreq_ready(struct cpufreq_policy *policy) +{ + struct device_node *np; + unsigned int cpu = policy->cpu; + + if (cdev[cpu]) + return; + + np = of_cpu_device_node_get(cpu); + 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)) { + cdev[cpu] = of_cpufreq_cooling_register(policy); + if (IS_ERR(cdev[cpu])) { + pr_err("running cpufreq for CPU%d without cooling dev: %ld\n", + cpu, PTR_ERR(cdev[cpu])); + cdev[cpu] = NULL; + } + } + + of_node_put(np); +} + +static struct cpufreq_driver msm_cpufreq_driver = { + /* lps calculations are handled here. */ + .flags = CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS | + CPUFREQ_NEED_INITIAL_FREQ_CHECK, + .init = msm_cpufreq_init, + .verify = msm_cpufreq_verify, + .target = msm_cpufreq_target, + .resolve_freq = msm_cpufreq_resolve_freq, + .get = msm_cpufreq_get_freq, + .name = "msm", + .attr = msm_freq_attr, + .ready = msm_cpufreq_ready, +}; + +static struct cpufreq_frequency_table *cpufreq_parse_dt(struct device *dev, + char *tbl_name, int cpu) +{ + int ret, nf, i, j; + u32 *data; + struct cpufreq_frequency_table *ftbl; + + /* Parse list of usable CPU frequencies. */ + if (!of_find_property(dev->of_node, tbl_name, &nf)) + return ERR_PTR(-EINVAL); + nf /= sizeof(*data); + + if (nf == 0) + return ERR_PTR(-EINVAL); + + data = devm_kzalloc(dev, nf * sizeof(*data), GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32_array(dev->of_node, tbl_name, data, nf); + if (ret) + return ERR_PTR(ret); + + ftbl = devm_kzalloc(dev, (nf + 1) * sizeof(*ftbl), GFP_KERNEL); + if (!ftbl) + return ERR_PTR(-ENOMEM); + + j = 0; + for (i = 0; i < nf; i++) { + unsigned long f; + + f = clk_round_rate(cpu_clk[cpu], data[i] * 1000); + if (IS_ERR_VALUE(f)) + break; + f /= 1000; + + /* + * Don't repeat frequencies if they round up to the same clock + * frequency. + * + */ + if (j > 0 && f <= ftbl[j - 1].frequency) + continue; + + ftbl[j].driver_data = j; + ftbl[j].frequency = f; + j++; + } + + ftbl[j].driver_data = j; + ftbl[j].frequency = CPUFREQ_TABLE_END; + + devm_kfree(dev, data); + + return ftbl; +} + +static int msm_cpufreq_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + char clk_name[] = "cpu??_clk"; + char tbl_name[] = "qcom,cpufreq-table-??"; + struct clk *c; + int cpu, ret; + struct cpufreq_frequency_table *ftbl; + + l2_clk = devm_clk_get(dev, "l2_clk"); + if (IS_ERR(l2_clk)) + l2_clk = NULL; + + for_each_possible_cpu(cpu) { + snprintf(clk_name, sizeof(clk_name), "cpu%d_clk", cpu); + c = devm_clk_get(dev, clk_name); + if (cpu == 0 && IS_ERR(c)) + return PTR_ERR(c); + else if (IS_ERR(c)) + c = cpu_clk[cpu-1]; + cpu_clk[cpu] = c; + } + hotplug_ready = true; + + /* Use per-policy governor tunable for some targets */ + if (of_property_read_bool(dev->of_node, "qcom,governor-per-policy")) + msm_cpufreq_driver.flags |= CPUFREQ_HAVE_GOVERNOR_PER_POLICY; + + /* Parse commong cpufreq table for all CPUs */ + ftbl = cpufreq_parse_dt(dev, "qcom,cpufreq-table", 0); + if (!IS_ERR(ftbl)) { + for_each_possible_cpu(cpu) + per_cpu(freq_table, cpu) = ftbl; + goto out_register; + } + + /* + * No common table. Parse individual tables for each unique + * CPU clock. + */ + for_each_possible_cpu(cpu) { + snprintf(tbl_name, sizeof(tbl_name), + "qcom,cpufreq-table-%d", cpu); + ftbl = cpufreq_parse_dt(dev, tbl_name, cpu); + + /* CPU0 must contain freq table */ + if (cpu == 0 && IS_ERR(ftbl)) { + dev_err(dev, "Failed to parse CPU0's freq table\n"); + return PTR_ERR(ftbl); + } + if (cpu == 0) { + per_cpu(freq_table, cpu) = ftbl; + continue; + } + + if (cpu_clk[cpu] != cpu_clk[cpu - 1] && IS_ERR(ftbl)) { + dev_err(dev, "Failed to parse CPU%d's freq table\n", + cpu); + return PTR_ERR(ftbl); + } + + /* Use previous CPU's table if it shares same clock */ + if (cpu_clk[cpu] == cpu_clk[cpu - 1]) { + if (!IS_ERR(ftbl)) { + dev_warn(dev, "Conflicting tables for CPU%d\n", + cpu); + devm_kfree(dev, ftbl); + } + ftbl = per_cpu(freq_table, cpu - 1); + } + per_cpu(freq_table, cpu) = ftbl; + } + +out_register: + ret = register_pm_notifier(&msm_cpufreq_pm_notifier); + if (ret) + return ret; + + ret = cpufreq_register_driver(&msm_cpufreq_driver); + if (ret) + unregister_pm_notifier(&msm_cpufreq_pm_notifier); + + return ret; +} + +static const struct of_device_id msm_cpufreq_match_table[] = { + { .compatible = "qcom,msm-cpufreq" }, + {} +}; + +static struct platform_driver msm_cpufreq_plat_driver = { + .probe = msm_cpufreq_probe, + .driver = { + .name = "msm-cpufreq", + .of_match_table = msm_cpufreq_match_table, + }, +}; + +static int __init msm_cpufreq_register(void) +{ + int cpu, rc; + + for_each_possible_cpu(cpu) { + mutex_init(&(per_cpu(suspend_data, cpu).suspend_mutex)); + per_cpu(suspend_data, cpu).device_suspended = 0; + per_cpu(cached_resolve_freq, cpu) = UINT_MAX; + } + + rc = platform_driver_register(&msm_cpufreq_plat_driver); + if (rc < 0) { + /* Unblock hotplug if msm-cpufreq probe fails */ + cpuhp_remove_state_nocalls(CPUHP_QCOM_CPUFREQ_PREPARE); + cpuhp_remove_state_nocalls(CPUHP_AP_QCOM_CPUFREQ_STARTING); + for_each_possible_cpu(cpu) + mutex_destroy(&(per_cpu(suspend_data, cpu). + suspend_mutex)); + return rc; + } + + return 0; +} + +subsys_initcall(msm_cpufreq_register); + +static int __init msm_cpufreq_early_register(void) +{ + int ret; + + ret = cpuhp_setup_state_nocalls(CPUHP_AP_QCOM_CPUFREQ_STARTING, + "AP_QCOM_CPUFREQ_STARTING", + qcom_cpufreq_starting_cpu, + qcom_cpufreq_dying_cpu); + if (ret) + return ret; + + ret = cpuhp_setup_state_nocalls(CPUHP_QCOM_CPUFREQ_PREPARE, + "QCOM_CPUFREQ_PREPARE", + qcom_cpufreq_up_cpu, + qcom_cpufreq_dead_cpu); + if (!ret) + return ret; + cpuhp_remove_state_nocalls(CPUHP_AP_QCOM_CPUFREQ_STARTING); + return ret; +} +core_initcall(msm_cpufreq_early_register); diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 088ebde79033..a7ba448d2963 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -461,6 +461,7 @@ static inline void cpufreq_resume(void) {} /* Policy Notifiers */ #define CPUFREQ_CREATE_POLICY (0) #define CPUFREQ_REMOVE_POLICY (1) +#define CPUFREQ_INCOMPATIBLE (2) #ifdef CONFIG_CPU_FREQ int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list); diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h index 5e59495791f1..206612b1f58e 100644 --- a/include/linux/cpuhotplug.h +++ b/include/linux/cpuhotplug.h @@ -77,6 +77,9 @@ enum cpuhp_state { CPUHP_POWERPC_MMU_CTX_PREPARE, CPUHP_XEN_PREPARE, CPUHP_XEN_EVTCHN_PREPARE, +#ifdef CONFIG_CPU_FREQ_MSM + CPUHP_QCOM_CPUFREQ_PREPARE, +#endif CPUHP_ARM_SHMOBILE_SCU_PREPARE, CPUHP_SH_SH3X_PREPARE, CPUHP_NET_FLOW_PREPARE, @@ -126,6 +129,9 @@ enum cpuhp_state { CPUHP_AP_ARM_TWD_STARTING, CPUHP_AP_QCOM_TIMER_STARTING, CPUHP_AP_TEGRA_TIMER_STARTING, +#ifdef CONFIG_CPU_FREQ_MSM + CPUHP_AP_QCOM_CPUFREQ_STARTING, +#endif CPUHP_AP_ARMADA_TIMER_STARTING, CPUHP_AP_MARCO_TIMER_STARTING, CPUHP_AP_MIPS_GIC_TIMER_STARTING,