| /* |
| * CPU frequency scaling for Broadcom BMIPS SoCs |
| * |
| * Copyright (c) 2017 Broadcom |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation version 2. |
| * |
| * This program is distributed "as is" WITHOUT ANY WARRANTY of any |
| * kind, whether express or implied; without even the implied warranty |
| * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/cpufreq.h> |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/slab.h> |
| |
| /* for mips_hpt_frequency */ |
| #include <asm/time.h> |
| |
| #define BMIPS_CPUFREQ_PREFIX "bmips" |
| #define BMIPS_CPUFREQ_NAME BMIPS_CPUFREQ_PREFIX "-cpufreq" |
| |
| #define TRANSITION_LATENCY (25 * 1000) /* 25 us */ |
| |
| #define BMIPS5_CLK_DIV_SET_SHIFT 0x7 |
| #define BMIPS5_CLK_DIV_SHIFT 0x4 |
| #define BMIPS5_CLK_DIV_MASK 0xf |
| |
| enum bmips_type { |
| BMIPS5000, |
| BMIPS5200, |
| }; |
| |
| struct cpufreq_compat { |
| const char *compatible; |
| unsigned int bmips_type; |
| unsigned int clk_mult; |
| unsigned int max_freqs; |
| }; |
| |
| #define BMIPS(c, t, m, f) { \ |
| .compatible = c, \ |
| .bmips_type = (t), \ |
| .clk_mult = (m), \ |
| .max_freqs = (f), \ |
| } |
| |
| static struct cpufreq_compat bmips_cpufreq_compat[] = { |
| BMIPS("brcm,bmips5000", BMIPS5000, 8, 4), |
| BMIPS("brcm,bmips5200", BMIPS5200, 8, 4), |
| { } |
| }; |
| |
| static struct cpufreq_compat *priv; |
| |
| static int htp_freq_to_cpu_freq(unsigned int clk_mult) |
| { |
| return mips_hpt_frequency * clk_mult / 1000; |
| } |
| |
| static struct cpufreq_frequency_table * |
| bmips_cpufreq_get_freq_table(const struct cpufreq_policy *policy) |
| { |
| struct cpufreq_frequency_table *table; |
| unsigned long cpu_freq; |
| int i; |
| |
| cpu_freq = htp_freq_to_cpu_freq(priv->clk_mult); |
| |
| table = kmalloc_array(priv->max_freqs + 1, sizeof(*table), GFP_KERNEL); |
| if (!table) |
| return ERR_PTR(-ENOMEM); |
| |
| for (i = 0; i < priv->max_freqs; i++) { |
| table[i].frequency = cpu_freq / (1 << i); |
| table[i].driver_data = i; |
| } |
| table[i].frequency = CPUFREQ_TABLE_END; |
| |
| return table; |
| } |
| |
| static unsigned int bmips_cpufreq_get(unsigned int cpu) |
| { |
| unsigned int div; |
| uint32_t mode; |
| |
| switch (priv->bmips_type) { |
| case BMIPS5200: |
| case BMIPS5000: |
| mode = read_c0_brcm_mode(); |
| div = ((mode >> BMIPS5_CLK_DIV_SHIFT) & BMIPS5_CLK_DIV_MASK); |
| break; |
| default: |
| div = 0; |
| } |
| |
| return htp_freq_to_cpu_freq(priv->clk_mult) / (1 << div); |
| } |
| |
| static int bmips_cpufreq_target_index(struct cpufreq_policy *policy, |
| unsigned int index) |
| { |
| unsigned int div = policy->freq_table[index].driver_data; |
| |
| switch (priv->bmips_type) { |
| case BMIPS5200: |
| case BMIPS5000: |
| change_c0_brcm_mode(BMIPS5_CLK_DIV_MASK << BMIPS5_CLK_DIV_SHIFT, |
| (1 << BMIPS5_CLK_DIV_SET_SHIFT) | |
| (div << BMIPS5_CLK_DIV_SHIFT)); |
| break; |
| default: |
| return -ENOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static int bmips_cpufreq_exit(struct cpufreq_policy *policy) |
| { |
| kfree(policy->freq_table); |
| |
| return 0; |
| } |
| |
| static int bmips_cpufreq_init(struct cpufreq_policy *policy) |
| { |
| struct cpufreq_frequency_table *freq_table; |
| int ret; |
| |
| freq_table = bmips_cpufreq_get_freq_table(policy); |
| if (IS_ERR(freq_table)) { |
| ret = PTR_ERR(freq_table); |
| pr_err("%s: couldn't determine frequency table (%d).\n", |
| BMIPS_CPUFREQ_NAME, ret); |
| return ret; |
| } |
| |
| ret = cpufreq_generic_init(policy, freq_table, TRANSITION_LATENCY); |
| if (ret) |
| bmips_cpufreq_exit(policy); |
| else |
| pr_info("%s: registered\n", BMIPS_CPUFREQ_NAME); |
| |
| return ret; |
| } |
| |
| static struct cpufreq_driver bmips_cpufreq_driver = { |
| .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK, |
| .verify = cpufreq_generic_frequency_table_verify, |
| .target_index = bmips_cpufreq_target_index, |
| .get = bmips_cpufreq_get, |
| .init = bmips_cpufreq_init, |
| .exit = bmips_cpufreq_exit, |
| .attr = cpufreq_generic_attr, |
| .name = BMIPS_CPUFREQ_PREFIX, |
| }; |
| |
| static int __init bmips_cpufreq_probe(void) |
| { |
| struct cpufreq_compat *cc; |
| struct device_node *np; |
| |
| for (cc = bmips_cpufreq_compat; cc->compatible; cc++) { |
| np = of_find_compatible_node(NULL, "cpu", cc->compatible); |
| if (np) { |
| of_node_put(np); |
| priv = cc; |
| break; |
| } |
| } |
| |
| /* We hit the guard element of the array. No compatible CPU found. */ |
| if (!cc->compatible) |
| return -ENODEV; |
| |
| return cpufreq_register_driver(&bmips_cpufreq_driver); |
| } |
| device_initcall(bmips_cpufreq_probe); |
| |
| MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>"); |
| MODULE_DESCRIPTION("CPUfreq driver for Broadcom BMIPS SoCs"); |
| MODULE_LICENSE("GPL"); |