Add the MIPS CPUfreq driver. This driver currently supports CPUfreq on BMIPS5xxx-based SoCs. Signed-off-by: Markus Mayer <mmayer@broadcom.com> Acked-by: Florian Fainelli <f.fainelli@gmail.com> Acked-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>tirimbino
parent
a8d709b065
commit
cdb56cbfd7
@ -0,0 +1,188 @@ |
||||
/*
|
||||
* 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((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"); |
Loading…
Reference in new issue