xref: /linux/drivers/cpufreq/bmips-cpufreq.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
1cdb56cbfSMarkus Mayer /*
2cdb56cbfSMarkus Mayer  * CPU frequency scaling for Broadcom BMIPS SoCs
3cdb56cbfSMarkus Mayer  *
4cdb56cbfSMarkus Mayer  * Copyright (c) 2017 Broadcom
5cdb56cbfSMarkus Mayer  *
6cdb56cbfSMarkus Mayer  * This program is free software; you can redistribute it and/or
7cdb56cbfSMarkus Mayer  * modify it under the terms of the GNU General Public License as
8cdb56cbfSMarkus Mayer  * published by the Free Software Foundation version 2.
9cdb56cbfSMarkus Mayer  *
10cdb56cbfSMarkus Mayer  * This program is distributed "as is" WITHOUT ANY WARRANTY of any
11cdb56cbfSMarkus Mayer  * kind, whether express or implied; without even the implied warranty
12cdb56cbfSMarkus Mayer  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13cdb56cbfSMarkus Mayer  * GNU General Public License for more details.
14cdb56cbfSMarkus Mayer  */
15cdb56cbfSMarkus Mayer 
16cdb56cbfSMarkus Mayer #include <linux/cpufreq.h>
17cdb56cbfSMarkus Mayer #include <linux/module.h>
18cdb56cbfSMarkus Mayer #include <linux/of_address.h>
19cdb56cbfSMarkus Mayer #include <linux/slab.h>
20cdb56cbfSMarkus Mayer 
21cdb56cbfSMarkus Mayer /* for mips_hpt_frequency */
22cdb56cbfSMarkus Mayer #include <asm/time.h>
23cdb56cbfSMarkus Mayer 
24cdb56cbfSMarkus Mayer #define BMIPS_CPUFREQ_PREFIX	"bmips"
25cdb56cbfSMarkus Mayer #define BMIPS_CPUFREQ_NAME	BMIPS_CPUFREQ_PREFIX "-cpufreq"
26cdb56cbfSMarkus Mayer 
27cdb56cbfSMarkus Mayer #define TRANSITION_LATENCY	(25 * 1000)	/* 25 us */
28cdb56cbfSMarkus Mayer 
29cdb56cbfSMarkus Mayer #define BMIPS5_CLK_DIV_SET_SHIFT	0x7
30cdb56cbfSMarkus Mayer #define BMIPS5_CLK_DIV_SHIFT		0x4
31cdb56cbfSMarkus Mayer #define BMIPS5_CLK_DIV_MASK		0xf
32cdb56cbfSMarkus Mayer 
33cdb56cbfSMarkus Mayer enum bmips_type {
34cdb56cbfSMarkus Mayer 	BMIPS5000,
35cdb56cbfSMarkus Mayer 	BMIPS5200,
36cdb56cbfSMarkus Mayer };
37cdb56cbfSMarkus Mayer 
38cdb56cbfSMarkus Mayer struct cpufreq_compat {
39cdb56cbfSMarkus Mayer 	const char *compatible;
40cdb56cbfSMarkus Mayer 	unsigned int bmips_type;
41cdb56cbfSMarkus Mayer 	unsigned int clk_mult;
42cdb56cbfSMarkus Mayer 	unsigned int max_freqs;
43cdb56cbfSMarkus Mayer };
44cdb56cbfSMarkus Mayer 
45cdb56cbfSMarkus Mayer #define BMIPS(c, t, m, f) { \
46cdb56cbfSMarkus Mayer 	.compatible = c, \
47cdb56cbfSMarkus Mayer 	.bmips_type = (t), \
48cdb56cbfSMarkus Mayer 	.clk_mult = (m), \
49cdb56cbfSMarkus Mayer 	.max_freqs = (f), \
50cdb56cbfSMarkus Mayer }
51cdb56cbfSMarkus Mayer 
52cdb56cbfSMarkus Mayer static struct cpufreq_compat bmips_cpufreq_compat[] = {
53cdb56cbfSMarkus Mayer 	BMIPS("brcm,bmips5000", BMIPS5000, 8, 4),
54cdb56cbfSMarkus Mayer 	BMIPS("brcm,bmips5200", BMIPS5200, 8, 4),
55cdb56cbfSMarkus Mayer 	{ }
56cdb56cbfSMarkus Mayer };
57cdb56cbfSMarkus Mayer 
58cdb56cbfSMarkus Mayer static struct cpufreq_compat *priv;
59cdb56cbfSMarkus Mayer 
htp_freq_to_cpu_freq(unsigned int clk_mult)60cdb56cbfSMarkus Mayer static int htp_freq_to_cpu_freq(unsigned int clk_mult)
61cdb56cbfSMarkus Mayer {
62cdb56cbfSMarkus Mayer 	return mips_hpt_frequency * clk_mult / 1000;
63cdb56cbfSMarkus Mayer }
64cdb56cbfSMarkus Mayer 
65cdb56cbfSMarkus Mayer static struct cpufreq_frequency_table *
bmips_cpufreq_get_freq_table(const struct cpufreq_policy * policy)66cdb56cbfSMarkus Mayer bmips_cpufreq_get_freq_table(const struct cpufreq_policy *policy)
67cdb56cbfSMarkus Mayer {
68cdb56cbfSMarkus Mayer 	struct cpufreq_frequency_table *table;
69cdb56cbfSMarkus Mayer 	unsigned long cpu_freq;
70cdb56cbfSMarkus Mayer 	int i;
71cdb56cbfSMarkus Mayer 
72cdb56cbfSMarkus Mayer 	cpu_freq = htp_freq_to_cpu_freq(priv->clk_mult);
73cdb56cbfSMarkus Mayer 
746da2ec56SKees Cook 	table = kmalloc_array(priv->max_freqs + 1, sizeof(*table), GFP_KERNEL);
75cdb56cbfSMarkus Mayer 	if (!table)
76cdb56cbfSMarkus Mayer 		return ERR_PTR(-ENOMEM);
77cdb56cbfSMarkus Mayer 
78cdb56cbfSMarkus Mayer 	for (i = 0; i < priv->max_freqs; i++) {
79cdb56cbfSMarkus Mayer 		table[i].frequency = cpu_freq / (1 << i);
80cdb56cbfSMarkus Mayer 		table[i].driver_data = i;
81cdb56cbfSMarkus Mayer 	}
82cdb56cbfSMarkus Mayer 	table[i].frequency = CPUFREQ_TABLE_END;
83cdb56cbfSMarkus Mayer 
84cdb56cbfSMarkus Mayer 	return table;
85cdb56cbfSMarkus Mayer }
86cdb56cbfSMarkus Mayer 
bmips_cpufreq_get(unsigned int cpu)87cdb56cbfSMarkus Mayer static unsigned int bmips_cpufreq_get(unsigned int cpu)
88cdb56cbfSMarkus Mayer {
89cdb56cbfSMarkus Mayer 	unsigned int div;
90cdb56cbfSMarkus Mayer 	uint32_t mode;
91cdb56cbfSMarkus Mayer 
92cdb56cbfSMarkus Mayer 	switch (priv->bmips_type) {
93cdb56cbfSMarkus Mayer 	case BMIPS5200:
94cdb56cbfSMarkus Mayer 	case BMIPS5000:
95cdb56cbfSMarkus Mayer 		mode = read_c0_brcm_mode();
96cdb56cbfSMarkus Mayer 		div = ((mode >> BMIPS5_CLK_DIV_SHIFT) & BMIPS5_CLK_DIV_MASK);
97cdb56cbfSMarkus Mayer 		break;
98cdb56cbfSMarkus Mayer 	default:
99cdb56cbfSMarkus Mayer 		div = 0;
100cdb56cbfSMarkus Mayer 	}
101cdb56cbfSMarkus Mayer 
102cdb56cbfSMarkus Mayer 	return htp_freq_to_cpu_freq(priv->clk_mult) / (1 << div);
103cdb56cbfSMarkus Mayer }
104cdb56cbfSMarkus Mayer 
bmips_cpufreq_target_index(struct cpufreq_policy * policy,unsigned int index)105cdb56cbfSMarkus Mayer static int bmips_cpufreq_target_index(struct cpufreq_policy *policy,
106cdb56cbfSMarkus Mayer 				      unsigned int index)
107cdb56cbfSMarkus Mayer {
108cdb56cbfSMarkus Mayer 	unsigned int div = policy->freq_table[index].driver_data;
109cdb56cbfSMarkus Mayer 
110cdb56cbfSMarkus Mayer 	switch (priv->bmips_type) {
111cdb56cbfSMarkus Mayer 	case BMIPS5200:
112cdb56cbfSMarkus Mayer 	case BMIPS5000:
113cdb56cbfSMarkus Mayer 		change_c0_brcm_mode(BMIPS5_CLK_DIV_MASK << BMIPS5_CLK_DIV_SHIFT,
114cdb56cbfSMarkus Mayer 				    (1 << BMIPS5_CLK_DIV_SET_SHIFT) |
115cdb56cbfSMarkus Mayer 				    (div << BMIPS5_CLK_DIV_SHIFT));
116cdb56cbfSMarkus Mayer 		break;
117cdb56cbfSMarkus Mayer 	default:
118cdb56cbfSMarkus Mayer 		return -ENOTSUPP;
119cdb56cbfSMarkus Mayer 	}
120cdb56cbfSMarkus Mayer 
121cdb56cbfSMarkus Mayer 	return 0;
122cdb56cbfSMarkus Mayer }
123cdb56cbfSMarkus Mayer 
bmips_cpufreq_exit(struct cpufreq_policy * policy)124*b4b1ddc9SLizhe static void bmips_cpufreq_exit(struct cpufreq_policy *policy)
125cdb56cbfSMarkus Mayer {
126cdb56cbfSMarkus Mayer 	kfree(policy->freq_table);
127cdb56cbfSMarkus Mayer }
128cdb56cbfSMarkus Mayer 
bmips_cpufreq_init(struct cpufreq_policy * policy)129cdb56cbfSMarkus Mayer static int bmips_cpufreq_init(struct cpufreq_policy *policy)
130cdb56cbfSMarkus Mayer {
131cdb56cbfSMarkus Mayer 	struct cpufreq_frequency_table *freq_table;
132cdb56cbfSMarkus Mayer 
133cdb56cbfSMarkus Mayer 	freq_table = bmips_cpufreq_get_freq_table(policy);
134cdb56cbfSMarkus Mayer 	if (IS_ERR(freq_table)) {
135c4dcc8a1SViresh Kumar 		pr_err("%s: couldn't determine frequency table (%ld).\n",
136c4dcc8a1SViresh Kumar 			BMIPS_CPUFREQ_NAME, PTR_ERR(freq_table));
137c4dcc8a1SViresh Kumar 		return PTR_ERR(freq_table);
138cdb56cbfSMarkus Mayer 	}
139cdb56cbfSMarkus Mayer 
140c4dcc8a1SViresh Kumar 	cpufreq_generic_init(policy, freq_table, TRANSITION_LATENCY);
141cdb56cbfSMarkus Mayer 	pr_info("%s: registered\n", BMIPS_CPUFREQ_NAME);
142cdb56cbfSMarkus Mayer 
143c4dcc8a1SViresh Kumar 	return 0;
144cdb56cbfSMarkus Mayer }
145cdb56cbfSMarkus Mayer 
146cdb56cbfSMarkus Mayer static struct cpufreq_driver bmips_cpufreq_driver = {
147cdb56cbfSMarkus Mayer 	.flags		= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
148cdb56cbfSMarkus Mayer 	.verify		= cpufreq_generic_frequency_table_verify,
149cdb56cbfSMarkus Mayer 	.target_index	= bmips_cpufreq_target_index,
150cdb56cbfSMarkus Mayer 	.get		= bmips_cpufreq_get,
151cdb56cbfSMarkus Mayer 	.init		= bmips_cpufreq_init,
152cdb56cbfSMarkus Mayer 	.exit		= bmips_cpufreq_exit,
153cdb56cbfSMarkus Mayer 	.attr		= cpufreq_generic_attr,
154cdb56cbfSMarkus Mayer 	.name		= BMIPS_CPUFREQ_PREFIX,
155cdb56cbfSMarkus Mayer };
156cdb56cbfSMarkus Mayer 
bmips_cpufreq_driver_init(void)1573359d527SZhang Jianhua static int __init bmips_cpufreq_driver_init(void)
158cdb56cbfSMarkus Mayer {
159cdb56cbfSMarkus Mayer 	struct cpufreq_compat *cc;
160cdb56cbfSMarkus Mayer 	struct device_node *np;
161cdb56cbfSMarkus Mayer 
162cdb56cbfSMarkus Mayer 	for (cc = bmips_cpufreq_compat; cc->compatible; cc++) {
163cdb56cbfSMarkus Mayer 		np = of_find_compatible_node(NULL, "cpu", cc->compatible);
164cdb56cbfSMarkus Mayer 		if (np) {
165cdb56cbfSMarkus Mayer 			of_node_put(np);
166cdb56cbfSMarkus Mayer 			priv = cc;
167cdb56cbfSMarkus Mayer 			break;
168cdb56cbfSMarkus Mayer 		}
169cdb56cbfSMarkus Mayer 	}
170cdb56cbfSMarkus Mayer 
171cdb56cbfSMarkus Mayer 	/* We hit the guard element of the array. No compatible CPU found. */
172cdb56cbfSMarkus Mayer 	if (!cc->compatible)
173cdb56cbfSMarkus Mayer 		return -ENODEV;
174cdb56cbfSMarkus Mayer 
175cdb56cbfSMarkus Mayer 	return cpufreq_register_driver(&bmips_cpufreq_driver);
176cdb56cbfSMarkus Mayer }
1773359d527SZhang Jianhua module_init(bmips_cpufreq_driver_init);
1783359d527SZhang Jianhua 
bmips_cpufreq_driver_exit(void)1793359d527SZhang Jianhua static void __exit bmips_cpufreq_driver_exit(void)
1803359d527SZhang Jianhua {
1813359d527SZhang Jianhua 	cpufreq_unregister_driver(&bmips_cpufreq_driver);
1823359d527SZhang Jianhua }
1833359d527SZhang Jianhua module_exit(bmips_cpufreq_driver_exit);
184cdb56cbfSMarkus Mayer 
185cdb56cbfSMarkus Mayer MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>");
186cdb56cbfSMarkus Mayer MODULE_DESCRIPTION("CPUfreq driver for Broadcom BMIPS SoCs");
187cdb56cbfSMarkus Mayer MODULE_LICENSE("GPL");
188