| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <linux/cpufreq.h> |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/slab.h> |
| |
| |
| #include <asm/time.h> |
| |
| #define BMIPS_CPUFREQ_PREFIX "bmips" |
| #define BMIPS_CPUFREQ_NAME BMIPS_CPUFREQ_PREFIX "-cpufreq" |
| |
| #define TRANSITION_LATENCY (25 * 1000) |
| |
| #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; |
| |
| <------>freq_table = bmips_cpufreq_get_freq_table(policy); |
| <------>if (IS_ERR(freq_table)) { |
| <------><------>pr_err("%s: couldn't determine frequency table (%ld).\n", |
| <------><------><------>BMIPS_CPUFREQ_NAME, PTR_ERR(freq_table)); |
| <------><------>return PTR_ERR(freq_table); |
| <------>} |
| |
| <------>cpufreq_generic_init(policy, freq_table, TRANSITION_LATENCY); |
| <------>pr_info("%s: registered\n", BMIPS_CPUFREQ_NAME); |
| |
| <------>return 0; |
| } |
| |
| 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; |
| <------><------>} |
| <------>} |
| |
| <------> |
| <------>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"); |
| |