diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index a9ae2f84a4ef..09d4747c96ca 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -1,52 +1,59 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. */ -#include #include #include #include #include #include #include -#include -#include #define LUT_MAX_ENTRIES 40U -#define LUT_SRC GENMASK(31, 30) -#define LUT_L_VAL GENMASK(7, 0) -#define LUT_CORE_COUNT GENMASK(18, 16) -#define LUT_VOLT GENMASK(11, 0) +#define CORE_COUNT_VAL(val) (((val) & (GENMASK(18, 16))) >> 16) #define LUT_ROW_SIZE 32 #define CLK_HW_DIV 2 -#define LUT_TURBO_IND 1 -/* Register offsets */ -#define REG_ENABLE 0x0 -#define REG_FREQ_LUT 0x110 -#define REG_VOLT_LUT 0x114 -#define REG_PERF_STATE 0x920 +enum { + REG_ENABLE, + REG_LUT_TABLE, + REG_PERF_STATE, -static unsigned long cpu_hw_rate, xo_rate; -static struct platform_device *global_pdev; + REG_ARRAY_SIZE, +}; -static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, - unsigned int index) +struct cpufreq_qcom { + struct cpufreq_frequency_table *table; + void __iomem *reg_bases[REG_ARRAY_SIZE]; + cpumask_t related_cpus; + unsigned int max_cores; + unsigned long xo_rate; + unsigned long cpu_hw_rate; +}; + +static const u16 cpufreq_qcom_std_offsets[REG_ARRAY_SIZE] = { + [REG_ENABLE] = 0x0, + [REG_LUT_TABLE] = 0x110, + [REG_PERF_STATE] = 0x920, +}; + +static struct cpufreq_qcom *qcom_freq_domain_map[NR_CPUS]; + +static int +qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, + unsigned int index) { - void __iomem *perf_state_reg = policy->driver_data; - unsigned long freq = policy->freq_table[index].frequency; + struct cpufreq_qcom *c = policy->driver_data; - writel_relaxed(index, perf_state_reg); + writel_relaxed(index, c->reg_bases[REG_PERF_STATE]); - arch_set_freq_scale(policy->related_cpus, freq, - policy->cpuinfo.max_freq); return 0; } static unsigned int qcom_cpufreq_hw_get(unsigned int cpu) { - void __iomem *perf_state_reg; + struct cpufreq_qcom *c; struct cpufreq_policy *policy; unsigned int index; @@ -54,208 +61,45 @@ static unsigned int qcom_cpufreq_hw_get(unsigned int cpu) if (!policy) return 0; - perf_state_reg = policy->driver_data; + c = policy->driver_data; - index = readl_relaxed(perf_state_reg); + index = readl_relaxed(c->reg_bases[REG_PERF_STATE]); index = min(index, LUT_MAX_ENTRIES - 1); return policy->freq_table[index].frequency; } -static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, - unsigned int target_freq) +static unsigned int +qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, + unsigned int target_freq) { - void __iomem *perf_state_reg = policy->driver_data; + struct cpufreq_qcom *c = policy->driver_data; int index; - unsigned long freq; index = policy->cached_resolved_idx; if (index < 0) return 0; - writel_relaxed(index, perf_state_reg); + writel_relaxed(index, c->reg_bases[REG_PERF_STATE]); - freq = policy->freq_table[index].frequency; - arch_set_freq_scale(policy->related_cpus, freq, - policy->cpuinfo.max_freq); - - return freq; -} - -static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, - struct cpufreq_policy *policy, - void __iomem *base) -{ - u32 data, src, lval, i, core_count, prev_freq = 0, freq; - u32 volt; - struct cpufreq_frequency_table *table; - - table = kcalloc(LUT_MAX_ENTRIES + 1, sizeof(*table), GFP_KERNEL); - if (!table) - return -ENOMEM; - - for (i = 0; i < LUT_MAX_ENTRIES; i++) { - data = readl_relaxed(base + REG_FREQ_LUT + - i * LUT_ROW_SIZE); - src = FIELD_GET(LUT_SRC, data); - lval = FIELD_GET(LUT_L_VAL, data); - core_count = FIELD_GET(LUT_CORE_COUNT, data); - - data = readl_relaxed(base + REG_VOLT_LUT + - i * LUT_ROW_SIZE); - volt = FIELD_GET(LUT_VOLT, data) * 1000; - - if (src) - freq = xo_rate * lval / 1000; - else - freq = cpu_hw_rate / 1000; - - if (freq != prev_freq && core_count != LUT_TURBO_IND) { - table[i].frequency = freq; - dev_pm_opp_add(cpu_dev, freq * 1000, volt); - dev_dbg(cpu_dev, "index=%d freq=%d, core_count %d\n", i, - freq, core_count); - } else if (core_count == LUT_TURBO_IND) { - table[i].frequency = CPUFREQ_ENTRY_INVALID; - } - - /* - * Two of the same frequencies with the same core counts means - * end of table - */ - if (i > 0 && prev_freq == freq) { - struct cpufreq_frequency_table *prev = &table[i - 1]; - - /* - * Only treat the last frequency that might be a boost - * as the boost frequency - */ - if (prev->frequency == CPUFREQ_ENTRY_INVALID) { - prev->frequency = prev_freq; - prev->flags = CPUFREQ_BOOST_FREQ; - dev_pm_opp_add(cpu_dev, prev_freq * 1000, volt); - } - - break; - } - - prev_freq = freq; - } - - table[i].frequency = CPUFREQ_TABLE_END; - policy->freq_table = table; - dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); - - return 0; -} - -static void qcom_get_related_cpus(int index, struct cpumask *m) -{ - struct device_node *cpu_np; - struct of_phandle_args args; - int cpu, ret; - - for_each_possible_cpu(cpu) { - cpu_np = of_cpu_device_node_get(cpu); - if (!cpu_np) - continue; - - ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", - "#freq-domain-cells", 0, - &args); - of_node_put(cpu_np); - if (ret < 0) - continue; - - if (index == args.args[0]) - cpumask_set_cpu(cpu, m); - } + return policy->freq_table[index].frequency; } static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) { - struct device *dev = &global_pdev->dev; - struct of_phandle_args args; - struct device_node *cpu_np; - struct device *cpu_dev; - struct resource *res; - void __iomem *base; - int ret, index; + struct cpufreq_qcom *c; - cpu_dev = get_cpu_device(policy->cpu); - if (!cpu_dev) { - pr_err("%s: failed to get cpu%d device\n", __func__, - policy->cpu); + c = qcom_freq_domain_map[policy->cpu]; + if (!c) { + pr_err("No scaling support for CPU%d\n", policy->cpu); return -ENODEV; } - cpu_np = of_cpu_device_node_get(policy->cpu); - if (!cpu_np) - return -EINVAL; - - ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", - "#freq-domain-cells", 0, &args); - of_node_put(cpu_np); - if (ret) - return ret; - - index = args.args[0]; - - res = platform_get_resource(global_pdev, IORESOURCE_MEM, index); - if (!res) - return -ENODEV; - - base = devm_ioremap(dev, res->start, resource_size(res)); - if (!base) - return -ENOMEM; - - /* HW should be in enabled state to proceed */ - if (!(readl_relaxed(base + REG_ENABLE) & 0x1)) { - dev_err(dev, "Domain-%d cpufreq hardware not enabled\n", index); - ret = -ENODEV; - goto error; - } - - qcom_get_related_cpus(index, policy->cpus); - if (!cpumask_weight(policy->cpus)) { - dev_err(dev, "Domain-%d failed to get related CPUs\n", index); - ret = -ENOENT; - goto error; - } - - policy->driver_data = base + REG_PERF_STATE; - - ret = qcom_cpufreq_hw_read_lut(cpu_dev, policy, base); - if (ret) { - dev_err(dev, "Domain-%d failed to read LUT\n", index); - goto error; - } - - ret = dev_pm_opp_get_opp_count(cpu_dev); - if (ret <= 0) { - dev_err(cpu_dev, "Failed to add OPPs\n"); - ret = -ENODEV; - goto error; - } - - dev_pm_opp_of_register_em(policy->cpus); + cpumask_copy(policy->cpus, &c->related_cpus); policy->fast_switch_possible = true; - - return 0; -error: - devm_iounmap(dev, base); - return ret; -} - -static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) -{ - struct device *cpu_dev = get_cpu_device(policy->cpu); - void __iomem *base = policy->driver_data - REG_PERF_STATE; - - dev_pm_opp_remove_all_dynamic(cpu_dev); - kfree(policy->freq_table); - devm_iounmap(&global_pdev->dev, base); + policy->freq_table = c->table; + policy->driver_data = c; return 0; } @@ -268,62 +112,233 @@ static struct freq_attr *qcom_cpufreq_hw_attr[] = { static struct cpufreq_driver cpufreq_qcom_hw_driver = { .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK | - CPUFREQ_HAVE_GOVERNOR_PER_POLICY | - CPUFREQ_IS_COOLING_DEV, + CPUFREQ_HAVE_GOVERNOR_PER_POLICY, .verify = cpufreq_generic_frequency_table_verify, .target_index = qcom_cpufreq_hw_target_index, .get = qcom_cpufreq_hw_get, .init = qcom_cpufreq_hw_cpu_init, - .exit = qcom_cpufreq_hw_cpu_exit, .fast_switch = qcom_cpufreq_hw_fast_switch, .name = "qcom-cpufreq-hw", .attr = qcom_cpufreq_hw_attr, + .boost_enabled = true, }; -static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev) +static int qcom_cpufreq_hw_read_lut(struct platform_device *pdev, + struct cpufreq_qcom *c) { + struct device *dev = &pdev->dev; + void __iomem *base; + u32 data, src, lval, i, core_count, prev_cc, prev_freq, cur_freq; + + c->table = devm_kcalloc(dev, LUT_MAX_ENTRIES + 1, + sizeof(*c->table), GFP_KERNEL); + if (!c->table) + return -ENOMEM; + + base = c->reg_bases[REG_LUT_TABLE]; + + for (i = 0; i < LUT_MAX_ENTRIES; i++) { + data = readl_relaxed(base + i * LUT_ROW_SIZE); + src = (data & GENMASK(31, 30)) >> 30; + lval = data & GENMASK(7, 0); + core_count = CORE_COUNT_VAL(data); + + if (src) + c->table[i].frequency = c->xo_rate * lval / 1000; + else + c->table[i].frequency = c->cpu_hw_rate / 1000; + + cur_freq = c->table[i].frequency; + + dev_dbg(dev, "index=%d freq=%d, core_count %d\n", + i, c->table[i].frequency, core_count); + + if (core_count != c->max_cores) + cur_freq = CPUFREQ_ENTRY_INVALID; + + /* + * Two of the same frequencies with the same core counts means + * end of table. + */ + if (i > 0 && c->table[i - 1].frequency == + c->table[i].frequency && prev_cc == core_count) { + struct cpufreq_frequency_table *prev = &c->table[i - 1]; + + if (prev_freq == CPUFREQ_ENTRY_INVALID) + prev->flags = CPUFREQ_BOOST_FREQ; + break; + } + prev_cc = core_count; + prev_freq = cur_freq; + } + + c->table[i].frequency = CPUFREQ_TABLE_END; + + return 0; +} + +static int qcom_get_related_cpus(int index, struct cpumask *m) +{ + struct device_node *cpu_np; + struct of_phandle_args args; + int cpu, ret; + + for_each_possible_cpu(cpu) { + cpu_np = of_cpu_device_node_get(cpu); + if (!cpu_np) + continue; + of_node_put(cpu_np); + + ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", + "#freq-domain-cells", 0, &args); + if (ret < 0) + continue; + + if (index == args.args[0]) + cpumask_set_cpu(cpu, m); + } + + return 0; +} + +static int qcom_cpu_resources_init(struct platform_device *pdev, + unsigned int cpu, int index, + unsigned long xo_rate, + unsigned long cpu_hw_rate) +{ + struct cpufreq_qcom *c; + struct resource *res; + struct device *dev = &pdev->dev; + const u16 *offsets; + int ret, i, cpu_r; + void __iomem *base; + + if (qcom_freq_domain_map[cpu]) + return 0; + + c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + offsets = of_device_get_match_data(&pdev->dev); + if (!offsets) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, index); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + for (i = REG_ENABLE; i < REG_ARRAY_SIZE; i++) + c->reg_bases[i] = base + offsets[i]; + + /* HW should be in enabled state to proceed */ + if (!(readl_relaxed(c->reg_bases[REG_ENABLE]) & 0x1)) { + dev_err(dev, "Domain-%d cpufreq hardware not enabled\n", index); + return -ENODEV; + } + + ret = qcom_get_related_cpus(index, &c->related_cpus); + if (ret) { + dev_err(dev, "Domain-%d failed to get related CPUs\n", index); + return ret; + } + + c->max_cores = cpumask_weight(&c->related_cpus); + if (!c->max_cores) + return -ENOENT; + + c->xo_rate = xo_rate; + c->cpu_hw_rate = cpu_hw_rate; + + ret = qcom_cpufreq_hw_read_lut(pdev, c); + if (ret) { + dev_err(dev, "Domain-%d failed to read LUT\n", index); + return ret; + } + + for_each_cpu(cpu_r, &c->related_cpus) + qcom_freq_domain_map[cpu_r] = c; + + return 0; +} + +static int qcom_resources_init(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct of_phandle_args args; struct clk *clk; + unsigned int cpu; + unsigned long xo_rate, cpu_hw_rate; int ret; - clk = clk_get(&pdev->dev, "xo"); + clk = devm_clk_get(&pdev->dev, "xo"); if (IS_ERR(clk)) return PTR_ERR(clk); xo_rate = clk_get_rate(clk); - clk_put(clk); - clk = clk_get(&pdev->dev, "alternate"); + devm_clk_put(&pdev->dev, clk); + + clk = devm_clk_get(&pdev->dev, "cpu_clk"); if (IS_ERR(clk)) return PTR_ERR(clk); cpu_hw_rate = clk_get_rate(clk) / CLK_HW_DIV; - clk_put(clk); - global_pdev = pdev; + devm_clk_put(&pdev->dev, clk); - ret = cpufreq_register_driver(&cpufreq_qcom_hw_driver); - if (ret) - dev_err(&pdev->dev, "CPUFreq HW driver failed to register\n"); - else - dev_dbg(&pdev->dev, "QCOM CPUFreq HW driver initialized\n"); + for_each_possible_cpu(cpu) { + cpu_np = of_cpu_device_node_get(cpu); + if (!cpu_np) { + dev_dbg(&pdev->dev, "Failed to get cpu %d device\n", + cpu); + continue; + } - return ret; + ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", + "#freq-domain-cells", 0, &args); + if (ret < 0) + return ret; + + ret = qcom_cpu_resources_init(pdev, cpu, args.args[0], + xo_rate, cpu_hw_rate); + if (ret) + return ret; + } + + return 0; } -static int qcom_cpufreq_hw_driver_remove(struct platform_device *pdev) +static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev) { - return cpufreq_unregister_driver(&cpufreq_qcom_hw_driver); + int rc; + + /* Get the bases of cpufreq for domains */ + rc = qcom_resources_init(pdev); + if (rc) { + dev_err(&pdev->dev, "CPUFreq resource init failed\n"); + return rc; + } + + rc = cpufreq_register_driver(&cpufreq_qcom_hw_driver); + if (rc) { + dev_err(&pdev->dev, "CPUFreq HW driver failed to register\n"); + return rc; + } + + dev_dbg(&pdev->dev, "QCOM CPUFreq HW driver initialized\n"); + + return 0; } static const struct of_device_id qcom_cpufreq_hw_match[] = { - { .compatible = "qcom,cpufreq-hw" }, + { .compatible = "qcom,cpufreq-hw", .data = &cpufreq_qcom_std_offsets }, {} }; -MODULE_DEVICE_TABLE(of, qcom_cpufreq_hw_match); static struct platform_driver qcom_cpufreq_hw_driver = { .probe = qcom_cpufreq_hw_driver_probe, - .remove = qcom_cpufreq_hw_driver_remove, .driver = { .name = "qcom-cpufreq-hw", .of_match_table = qcom_cpufreq_hw_match, @@ -334,13 +349,6 @@ static int __init qcom_cpufreq_hw_init(void) { return platform_driver_register(&qcom_cpufreq_hw_driver); } -device_initcall(qcom_cpufreq_hw_init); +subsys_initcall(qcom_cpufreq_hw_init); -static void __exit qcom_cpufreq_hw_exit(void) -{ - platform_driver_unregister(&qcom_cpufreq_hw_driver); -} -module_exit(qcom_cpufreq_hw_exit); - -MODULE_DESCRIPTION("QCOM CPUFREQ HW Driver"); -MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("QCOM firmware-based CPU Frequency driver");