1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * CPUFreq support for Armada 8K
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (C) 2018 Marvell
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Omri Itach <omrii@marvell.com>
8*4882a593Smuzhiyun * Gregory Clement <gregory.clement@bootlin.com>
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12*4882a593Smuzhiyun
13*4882a593Smuzhiyun #include <linux/clk.h>
14*4882a593Smuzhiyun #include <linux/cpu.h>
15*4882a593Smuzhiyun #include <linux/err.h>
16*4882a593Smuzhiyun #include <linux/init.h>
17*4882a593Smuzhiyun #include <linux/kernel.h>
18*4882a593Smuzhiyun #include <linux/module.h>
19*4882a593Smuzhiyun #include <linux/of.h>
20*4882a593Smuzhiyun #include <linux/platform_device.h>
21*4882a593Smuzhiyun #include <linux/pm_opp.h>
22*4882a593Smuzhiyun #include <linux/slab.h>
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun /*
25*4882a593Smuzhiyun * Setup the opps list with the divider for the max frequency, that
26*4882a593Smuzhiyun * will be filled at runtime.
27*4882a593Smuzhiyun */
28*4882a593Smuzhiyun static const int opps_div[] __initconst = {1, 2, 3, 4};
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun static struct platform_device *armada_8k_pdev;
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun struct freq_table {
33*4882a593Smuzhiyun struct device *cpu_dev;
34*4882a593Smuzhiyun unsigned int freq[ARRAY_SIZE(opps_div)];
35*4882a593Smuzhiyun };
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun /* If the CPUs share the same clock, then they are in the same cluster. */
armada_8k_get_sharing_cpus(struct clk * cur_clk,struct cpumask * cpumask)38*4882a593Smuzhiyun static void __init armada_8k_get_sharing_cpus(struct clk *cur_clk,
39*4882a593Smuzhiyun struct cpumask *cpumask)
40*4882a593Smuzhiyun {
41*4882a593Smuzhiyun int cpu;
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun for_each_possible_cpu(cpu) {
44*4882a593Smuzhiyun struct device *cpu_dev;
45*4882a593Smuzhiyun struct clk *clk;
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun cpu_dev = get_cpu_device(cpu);
48*4882a593Smuzhiyun if (!cpu_dev) {
49*4882a593Smuzhiyun pr_warn("Failed to get cpu%d device\n", cpu);
50*4882a593Smuzhiyun continue;
51*4882a593Smuzhiyun }
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun clk = clk_get(cpu_dev, 0);
54*4882a593Smuzhiyun if (IS_ERR(clk)) {
55*4882a593Smuzhiyun pr_warn("Cannot get clock for CPU %d\n", cpu);
56*4882a593Smuzhiyun } else {
57*4882a593Smuzhiyun if (clk_is_match(clk, cur_clk))
58*4882a593Smuzhiyun cpumask_set_cpu(cpu, cpumask);
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun clk_put(clk);
61*4882a593Smuzhiyun }
62*4882a593Smuzhiyun }
63*4882a593Smuzhiyun }
64*4882a593Smuzhiyun
armada_8k_add_opp(struct clk * clk,struct device * cpu_dev,struct freq_table * freq_tables,int opps_index)65*4882a593Smuzhiyun static int __init armada_8k_add_opp(struct clk *clk, struct device *cpu_dev,
66*4882a593Smuzhiyun struct freq_table *freq_tables,
67*4882a593Smuzhiyun int opps_index)
68*4882a593Smuzhiyun {
69*4882a593Smuzhiyun unsigned int cur_frequency;
70*4882a593Smuzhiyun unsigned int freq;
71*4882a593Smuzhiyun int i, ret;
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun /* Get nominal (current) CPU frequency. */
74*4882a593Smuzhiyun cur_frequency = clk_get_rate(clk);
75*4882a593Smuzhiyun if (!cur_frequency) {
76*4882a593Smuzhiyun dev_err(cpu_dev, "Failed to get clock rate for this CPU\n");
77*4882a593Smuzhiyun return -EINVAL;
78*4882a593Smuzhiyun }
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun freq_tables[opps_index].cpu_dev = cpu_dev;
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun for (i = 0; i < ARRAY_SIZE(opps_div); i++) {
83*4882a593Smuzhiyun freq = cur_frequency / opps_div[i];
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun ret = dev_pm_opp_add(cpu_dev, freq, 0);
86*4882a593Smuzhiyun if (ret)
87*4882a593Smuzhiyun return ret;
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun freq_tables[opps_index].freq[i] = freq;
90*4882a593Smuzhiyun }
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun return 0;
93*4882a593Smuzhiyun }
94*4882a593Smuzhiyun
armada_8k_cpufreq_free_table(struct freq_table * freq_tables)95*4882a593Smuzhiyun static void armada_8k_cpufreq_free_table(struct freq_table *freq_tables)
96*4882a593Smuzhiyun {
97*4882a593Smuzhiyun int opps_index, nb_cpus = num_possible_cpus();
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun for (opps_index = 0 ; opps_index <= nb_cpus; opps_index++) {
100*4882a593Smuzhiyun int i;
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun /* If cpu_dev is NULL then we reached the end of the array */
103*4882a593Smuzhiyun if (!freq_tables[opps_index].cpu_dev)
104*4882a593Smuzhiyun break;
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun for (i = 0; i < ARRAY_SIZE(opps_div); i++) {
107*4882a593Smuzhiyun /*
108*4882a593Smuzhiyun * A 0Hz frequency is not valid, this meant
109*4882a593Smuzhiyun * that it was not yet initialized so there is
110*4882a593Smuzhiyun * no more opp to free
111*4882a593Smuzhiyun */
112*4882a593Smuzhiyun if (freq_tables[opps_index].freq[i] == 0)
113*4882a593Smuzhiyun break;
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun dev_pm_opp_remove(freq_tables[opps_index].cpu_dev,
116*4882a593Smuzhiyun freq_tables[opps_index].freq[i]);
117*4882a593Smuzhiyun }
118*4882a593Smuzhiyun }
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun kfree(freq_tables);
121*4882a593Smuzhiyun }
122*4882a593Smuzhiyun
armada_8k_cpufreq_init(void)123*4882a593Smuzhiyun static int __init armada_8k_cpufreq_init(void)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun int ret = 0, opps_index = 0, cpu, nb_cpus;
126*4882a593Smuzhiyun struct freq_table *freq_tables;
127*4882a593Smuzhiyun struct device_node *node;
128*4882a593Smuzhiyun struct cpumask cpus;
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun node = of_find_compatible_node(NULL, NULL, "marvell,ap806-cpu-clock");
131*4882a593Smuzhiyun if (!node || !of_device_is_available(node)) {
132*4882a593Smuzhiyun of_node_put(node);
133*4882a593Smuzhiyun return -ENODEV;
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun of_node_put(node);
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun nb_cpus = num_possible_cpus();
138*4882a593Smuzhiyun freq_tables = kcalloc(nb_cpus, sizeof(*freq_tables), GFP_KERNEL);
139*4882a593Smuzhiyun if (!freq_tables)
140*4882a593Smuzhiyun return -ENOMEM;
141*4882a593Smuzhiyun cpumask_copy(&cpus, cpu_possible_mask);
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun /*
144*4882a593Smuzhiyun * For each CPU, this loop registers the operating points
145*4882a593Smuzhiyun * supported (which are the nominal CPU frequency and full integer
146*4882a593Smuzhiyun * divisions of it).
147*4882a593Smuzhiyun */
148*4882a593Smuzhiyun for_each_cpu(cpu, &cpus) {
149*4882a593Smuzhiyun struct cpumask shared_cpus;
150*4882a593Smuzhiyun struct device *cpu_dev;
151*4882a593Smuzhiyun struct clk *clk;
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun cpu_dev = get_cpu_device(cpu);
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun if (!cpu_dev) {
156*4882a593Smuzhiyun pr_err("Cannot get CPU %d\n", cpu);
157*4882a593Smuzhiyun continue;
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun clk = clk_get(cpu_dev, 0);
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun if (IS_ERR(clk)) {
163*4882a593Smuzhiyun pr_err("Cannot get clock for CPU %d\n", cpu);
164*4882a593Smuzhiyun ret = PTR_ERR(clk);
165*4882a593Smuzhiyun goto remove_opp;
166*4882a593Smuzhiyun }
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun ret = armada_8k_add_opp(clk, cpu_dev, freq_tables, opps_index);
169*4882a593Smuzhiyun if (ret) {
170*4882a593Smuzhiyun clk_put(clk);
171*4882a593Smuzhiyun goto remove_opp;
172*4882a593Smuzhiyun }
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun opps_index++;
175*4882a593Smuzhiyun cpumask_clear(&shared_cpus);
176*4882a593Smuzhiyun armada_8k_get_sharing_cpus(clk, &shared_cpus);
177*4882a593Smuzhiyun dev_pm_opp_set_sharing_cpus(cpu_dev, &shared_cpus);
178*4882a593Smuzhiyun cpumask_andnot(&cpus, &cpus, &shared_cpus);
179*4882a593Smuzhiyun clk_put(clk);
180*4882a593Smuzhiyun }
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun armada_8k_pdev = platform_device_register_simple("cpufreq-dt", -1,
183*4882a593Smuzhiyun NULL, 0);
184*4882a593Smuzhiyun ret = PTR_ERR_OR_ZERO(armada_8k_pdev);
185*4882a593Smuzhiyun if (ret)
186*4882a593Smuzhiyun goto remove_opp;
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun platform_set_drvdata(armada_8k_pdev, freq_tables);
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun return 0;
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun remove_opp:
193*4882a593Smuzhiyun armada_8k_cpufreq_free_table(freq_tables);
194*4882a593Smuzhiyun return ret;
195*4882a593Smuzhiyun }
196*4882a593Smuzhiyun module_init(armada_8k_cpufreq_init);
197*4882a593Smuzhiyun
armada_8k_cpufreq_exit(void)198*4882a593Smuzhiyun static void __exit armada_8k_cpufreq_exit(void)
199*4882a593Smuzhiyun {
200*4882a593Smuzhiyun struct freq_table *freq_tables = platform_get_drvdata(armada_8k_pdev);
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun platform_device_unregister(armada_8k_pdev);
203*4882a593Smuzhiyun armada_8k_cpufreq_free_table(freq_tables);
204*4882a593Smuzhiyun }
205*4882a593Smuzhiyun module_exit(armada_8k_cpufreq_exit);
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun static const struct of_device_id __maybe_unused armada_8k_cpufreq_of_match[] = {
208*4882a593Smuzhiyun { .compatible = "marvell,ap806-cpu-clock" },
209*4882a593Smuzhiyun { },
210*4882a593Smuzhiyun };
211*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, armada_8k_cpufreq_of_match);
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun MODULE_AUTHOR("Gregory Clement <gregory.clement@bootlin.com>");
214*4882a593Smuzhiyun MODULE_DESCRIPTION("Armada 8K cpufreq driver");
215*4882a593Smuzhiyun MODULE_LICENSE("GPL");
216