diff options
Diffstat (limited to 'drivers/cpufreq/qoriq-cpufreq.c')
-rw-r--r-- | drivers/cpufreq/qoriq-cpufreq.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/drivers/cpufreq/qoriq-cpufreq.c b/drivers/cpufreq/qoriq-cpufreq.c new file mode 100644 index 0000000000..0aecaecbb0 --- /dev/null +++ b/drivers/cpufreq/qoriq-cpufreq.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2013 Freescale Semiconductor, Inc. + * + * CPU Frequency Scaling driver for Freescale QorIQ SoCs. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/cpufreq.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include <linux/platform_device.h> + +/** + * struct cpu_data + * @pclk: the parent clock of cpu + * @table: frequency table + */ +struct cpu_data { + struct clk **pclk; + struct cpufreq_frequency_table *table; +}; + +/** + * struct soc_data - SoC specific data + * @flags: SOC_xxx + */ +struct soc_data { + u32 flags; +}; + +static u32 get_bus_freq(void) +{ + struct device_node *soc; + u32 sysfreq; + struct clk *pltclk; + int ret; + + /* get platform freq by searching bus-frequency property */ + soc = of_find_node_by_type(NULL, "soc"); + if (soc) { + ret = of_property_read_u32(soc, "bus-frequency", &sysfreq); + of_node_put(soc); + if (!ret) + return sysfreq; + } + + /* get platform freq by its clock name */ + pltclk = clk_get(NULL, "cg-pll0-div1"); + if (IS_ERR(pltclk)) { + pr_err("%s: can't get bus frequency %ld\n", + __func__, PTR_ERR(pltclk)); + return PTR_ERR(pltclk); + } + + return clk_get_rate(pltclk); +} + +static struct clk *cpu_to_clk(int cpu) +{ + struct device_node *np; + struct clk *clk; + + if (!cpu_present(cpu)) + return NULL; + + np = of_get_cpu_node(cpu, NULL); + if (!np) + return NULL; + + clk = of_clk_get(np, 0); + of_node_put(np); + return clk; +} + +/* traverse cpu nodes to get cpu mask of sharing clock wire */ +static void set_affected_cpus(struct cpufreq_policy *policy) +{ + struct cpumask *dstp = policy->cpus; + struct clk *clk; + int i; + + for_each_present_cpu(i) { + clk = cpu_to_clk(i); + if (IS_ERR(clk)) { + pr_err("%s: no clock for cpu %d\n", __func__, i); + continue; + } + + if (clk_is_match(policy->clk, clk)) + cpumask_set_cpu(i, dstp); + } +} + +/* reduce the duplicated frequencies in frequency table */ +static void freq_table_redup(struct cpufreq_frequency_table *freq_table, + int count) +{ + int i, j; + + for (i = 1; i < count; i++) { + for (j = 0; j < i; j++) { + if (freq_table[j].frequency == CPUFREQ_ENTRY_INVALID || + freq_table[j].frequency != + freq_table[i].frequency) + continue; + + freq_table[i].frequency = CPUFREQ_ENTRY_INVALID; + break; + } + } +} + +/* sort the frequencies in frequency table in descenting order */ +static void freq_table_sort(struct cpufreq_frequency_table *freq_table, + int count) +{ + int i, j, ind; + unsigned int freq, max_freq; + struct cpufreq_frequency_table table; + + for (i = 0; i < count - 1; i++) { + max_freq = freq_table[i].frequency; + ind = i; + for (j = i + 1; j < count; j++) { + freq = freq_table[j].frequency; + if (freq == CPUFREQ_ENTRY_INVALID || + freq <= max_freq) + continue; + ind = j; + max_freq = freq; + } + + if (ind != i) { + /* exchange the frequencies */ + table.driver_data = freq_table[i].driver_data; + table.frequency = freq_table[i].frequency; + freq_table[i].driver_data = freq_table[ind].driver_data; + freq_table[i].frequency = freq_table[ind].frequency; + freq_table[ind].driver_data = table.driver_data; + freq_table[ind].frequency = table.frequency; + } + } +} + +static int qoriq_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + struct device_node *np; + int i, count; + u32 freq; + struct clk *clk; + const struct clk_hw *hwclk; + struct cpufreq_frequency_table *table; + struct cpu_data *data; + unsigned int cpu = policy->cpu; + u64 u64temp; + + np = of_get_cpu_node(cpu, NULL); + if (!np) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + goto err_np; + + policy->clk = of_clk_get(np, 0); + if (IS_ERR(policy->clk)) { + pr_err("%s: no clock information\n", __func__); + goto err_nomem2; + } + + hwclk = __clk_get_hw(policy->clk); + count = clk_hw_get_num_parents(hwclk); + + data->pclk = kcalloc(count, sizeof(struct clk *), GFP_KERNEL); + if (!data->pclk) + goto err_nomem2; + + table = kcalloc(count + 1, sizeof(*table), GFP_KERNEL); + if (!table) + goto err_pclk; + + for (i = 0; i < count; i++) { + clk = clk_hw_get_parent_by_index(hwclk, i)->clk; + data->pclk[i] = clk; + freq = clk_get_rate(clk); + table[i].frequency = freq / 1000; + table[i].driver_data = i; + } + freq_table_redup(table, count); + freq_table_sort(table, count); + table[i].frequency = CPUFREQ_TABLE_END; + policy->freq_table = table; + data->table = table; + + /* update ->cpus if we have cluster, no harm if not */ + set_affected_cpus(policy); + policy->driver_data = data; + + /* Minimum transition latency is 12 platform clocks */ + u64temp = 12ULL * NSEC_PER_SEC; + do_div(u64temp, get_bus_freq()); + policy->cpuinfo.transition_latency = u64temp + 1; + + of_node_put(np); + + return 0; + +err_pclk: + kfree(data->pclk); +err_nomem2: + kfree(data); +err_np: + of_node_put(np); + + return -ENODEV; +} + +static int qoriq_cpufreq_cpu_exit(struct cpufreq_policy *policy) +{ + struct cpu_data *data = policy->driver_data; + + kfree(data->pclk); + kfree(data->table); + kfree(data); + policy->driver_data = NULL; + + return 0; +} + +static int qoriq_cpufreq_target(struct cpufreq_policy *policy, + unsigned int index) +{ + struct clk *parent; + struct cpu_data *data = policy->driver_data; + + parent = data->pclk[data->table[index].driver_data]; + return clk_set_parent(policy->clk, parent); +} + +static struct cpufreq_driver qoriq_cpufreq_driver = { + .name = "qoriq_cpufreq", + .flags = CPUFREQ_CONST_LOOPS | + CPUFREQ_IS_COOLING_DEV, + .init = qoriq_cpufreq_cpu_init, + .exit = qoriq_cpufreq_cpu_exit, + .verify = cpufreq_generic_frequency_table_verify, + .target_index = qoriq_cpufreq_target, + .get = cpufreq_generic_get, + .attr = cpufreq_generic_attr, +}; + +static const struct of_device_id qoriq_cpufreq_blacklist[] = { + /* e6500 cannot use cpufreq due to erratum A-008083 */ + { .compatible = "fsl,b4420-clockgen", }, + { .compatible = "fsl,b4860-clockgen", }, + { .compatible = "fsl,t2080-clockgen", }, + { .compatible = "fsl,t4240-clockgen", }, + {} +}; + +static int qoriq_cpufreq_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *np; + + np = of_find_matching_node(NULL, qoriq_cpufreq_blacklist); + if (np) { + of_node_put(np); + dev_info(&pdev->dev, "Disabling due to erratum A-008083"); + return -ENODEV; + } + + ret = cpufreq_register_driver(&qoriq_cpufreq_driver); + if (ret) + return ret; + + dev_info(&pdev->dev, "Freescale QorIQ CPU frequency scaling driver\n"); + return 0; +} + +static void qoriq_cpufreq_remove(struct platform_device *pdev) +{ + cpufreq_unregister_driver(&qoriq_cpufreq_driver); +} + +static struct platform_driver qoriq_cpufreq_platform_driver = { + .driver = { + .name = "qoriq-cpufreq", + }, + .probe = qoriq_cpufreq_probe, + .remove_new = qoriq_cpufreq_remove, +}; +module_platform_driver(qoriq_cpufreq_platform_driver); + +MODULE_ALIAS("platform:qoriq-cpufreq"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tang Yuantian <Yuantian.Tang@freescale.com>"); +MODULE_DESCRIPTION("cpufreq driver for Freescale QorIQ series SoCs"); |