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