162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * CPU frequency scaling for Broadcom BMIPS SoCs
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (c) 2017 Broadcom
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or
762306a36Sopenharmony_ci * modify it under the terms of the GNU General Public License as
862306a36Sopenharmony_ci * published by the Free Software Foundation version 2.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any
1162306a36Sopenharmony_ci * kind, whether express or implied; without even the implied warranty
1262306a36Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1362306a36Sopenharmony_ci * GNU General Public License for more details.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/cpufreq.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/of_address.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* for mips_hpt_frequency */
2262306a36Sopenharmony_ci#include <asm/time.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define BMIPS_CPUFREQ_PREFIX	"bmips"
2562306a36Sopenharmony_ci#define BMIPS_CPUFREQ_NAME	BMIPS_CPUFREQ_PREFIX "-cpufreq"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define TRANSITION_LATENCY	(25 * 1000)	/* 25 us */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define BMIPS5_CLK_DIV_SET_SHIFT	0x7
3062306a36Sopenharmony_ci#define BMIPS5_CLK_DIV_SHIFT		0x4
3162306a36Sopenharmony_ci#define BMIPS5_CLK_DIV_MASK		0xf
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cienum bmips_type {
3462306a36Sopenharmony_ci	BMIPS5000,
3562306a36Sopenharmony_ci	BMIPS5200,
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct cpufreq_compat {
3962306a36Sopenharmony_ci	const char *compatible;
4062306a36Sopenharmony_ci	unsigned int bmips_type;
4162306a36Sopenharmony_ci	unsigned int clk_mult;
4262306a36Sopenharmony_ci	unsigned int max_freqs;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define BMIPS(c, t, m, f) { \
4662306a36Sopenharmony_ci	.compatible = c, \
4762306a36Sopenharmony_ci	.bmips_type = (t), \
4862306a36Sopenharmony_ci	.clk_mult = (m), \
4962306a36Sopenharmony_ci	.max_freqs = (f), \
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic struct cpufreq_compat bmips_cpufreq_compat[] = {
5362306a36Sopenharmony_ci	BMIPS("brcm,bmips5000", BMIPS5000, 8, 4),
5462306a36Sopenharmony_ci	BMIPS("brcm,bmips5200", BMIPS5200, 8, 4),
5562306a36Sopenharmony_ci	{ }
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic struct cpufreq_compat *priv;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic int htp_freq_to_cpu_freq(unsigned int clk_mult)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	return mips_hpt_frequency * clk_mult / 1000;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic struct cpufreq_frequency_table *
6662306a36Sopenharmony_cibmips_cpufreq_get_freq_table(const struct cpufreq_policy *policy)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	struct cpufreq_frequency_table *table;
6962306a36Sopenharmony_ci	unsigned long cpu_freq;
7062306a36Sopenharmony_ci	int i;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	cpu_freq = htp_freq_to_cpu_freq(priv->clk_mult);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	table = kmalloc_array(priv->max_freqs + 1, sizeof(*table), GFP_KERNEL);
7562306a36Sopenharmony_ci	if (!table)
7662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	for (i = 0; i < priv->max_freqs; i++) {
7962306a36Sopenharmony_ci		table[i].frequency = cpu_freq / (1 << i);
8062306a36Sopenharmony_ci		table[i].driver_data = i;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci	table[i].frequency = CPUFREQ_TABLE_END;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return table;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic unsigned int bmips_cpufreq_get(unsigned int cpu)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	unsigned int div;
9062306a36Sopenharmony_ci	uint32_t mode;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	switch (priv->bmips_type) {
9362306a36Sopenharmony_ci	case BMIPS5200:
9462306a36Sopenharmony_ci	case BMIPS5000:
9562306a36Sopenharmony_ci		mode = read_c0_brcm_mode();
9662306a36Sopenharmony_ci		div = ((mode >> BMIPS5_CLK_DIV_SHIFT) & BMIPS5_CLK_DIV_MASK);
9762306a36Sopenharmony_ci		break;
9862306a36Sopenharmony_ci	default:
9962306a36Sopenharmony_ci		div = 0;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	return htp_freq_to_cpu_freq(priv->clk_mult) / (1 << div);
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic int bmips_cpufreq_target_index(struct cpufreq_policy *policy,
10662306a36Sopenharmony_ci				      unsigned int index)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	unsigned int div = policy->freq_table[index].driver_data;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	switch (priv->bmips_type) {
11162306a36Sopenharmony_ci	case BMIPS5200:
11262306a36Sopenharmony_ci	case BMIPS5000:
11362306a36Sopenharmony_ci		change_c0_brcm_mode(BMIPS5_CLK_DIV_MASK << BMIPS5_CLK_DIV_SHIFT,
11462306a36Sopenharmony_ci				    (1 << BMIPS5_CLK_DIV_SET_SHIFT) |
11562306a36Sopenharmony_ci				    (div << BMIPS5_CLK_DIV_SHIFT));
11662306a36Sopenharmony_ci		break;
11762306a36Sopenharmony_ci	default:
11862306a36Sopenharmony_ci		return -ENOTSUPP;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int bmips_cpufreq_exit(struct cpufreq_policy *policy)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	kfree(policy->freq_table);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return 0;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic int bmips_cpufreq_init(struct cpufreq_policy *policy)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct cpufreq_frequency_table *freq_table;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	freq_table = bmips_cpufreq_get_freq_table(policy);
13662306a36Sopenharmony_ci	if (IS_ERR(freq_table)) {
13762306a36Sopenharmony_ci		pr_err("%s: couldn't determine frequency table (%ld).\n",
13862306a36Sopenharmony_ci			BMIPS_CPUFREQ_NAME, PTR_ERR(freq_table));
13962306a36Sopenharmony_ci		return PTR_ERR(freq_table);
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	cpufreq_generic_init(policy, freq_table, TRANSITION_LATENCY);
14362306a36Sopenharmony_ci	pr_info("%s: registered\n", BMIPS_CPUFREQ_NAME);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic struct cpufreq_driver bmips_cpufreq_driver = {
14962306a36Sopenharmony_ci	.flags		= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
15062306a36Sopenharmony_ci	.verify		= cpufreq_generic_frequency_table_verify,
15162306a36Sopenharmony_ci	.target_index	= bmips_cpufreq_target_index,
15262306a36Sopenharmony_ci	.get		= bmips_cpufreq_get,
15362306a36Sopenharmony_ci	.init		= bmips_cpufreq_init,
15462306a36Sopenharmony_ci	.exit		= bmips_cpufreq_exit,
15562306a36Sopenharmony_ci	.attr		= cpufreq_generic_attr,
15662306a36Sopenharmony_ci	.name		= BMIPS_CPUFREQ_PREFIX,
15762306a36Sopenharmony_ci};
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic int __init bmips_cpufreq_driver_init(void)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	struct cpufreq_compat *cc;
16262306a36Sopenharmony_ci	struct device_node *np;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	for (cc = bmips_cpufreq_compat; cc->compatible; cc++) {
16562306a36Sopenharmony_ci		np = of_find_compatible_node(NULL, "cpu", cc->compatible);
16662306a36Sopenharmony_ci		if (np) {
16762306a36Sopenharmony_ci			of_node_put(np);
16862306a36Sopenharmony_ci			priv = cc;
16962306a36Sopenharmony_ci			break;
17062306a36Sopenharmony_ci		}
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* We hit the guard element of the array. No compatible CPU found. */
17462306a36Sopenharmony_ci	if (!cc->compatible)
17562306a36Sopenharmony_ci		return -ENODEV;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return cpufreq_register_driver(&bmips_cpufreq_driver);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_cimodule_init(bmips_cpufreq_driver_init);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic void __exit bmips_cpufreq_driver_exit(void)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	cpufreq_unregister_driver(&bmips_cpufreq_driver);
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_cimodule_exit(bmips_cpufreq_driver_exit);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ciMODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>");
18862306a36Sopenharmony_ciMODULE_DESCRIPTION("CPUfreq driver for Broadcom BMIPS SoCs");
18962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
190