mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
4.4 KiB
175 lines
4.4 KiB
/* |
|
* cpufreq driver for the SuperH processors. |
|
* |
|
* Copyright (C) 2002 - 2012 Paul Mundt |
|
* Copyright (C) 2002 M. R. Brown |
|
* |
|
* Clock framework bits from arch/avr32/mach-at32ap/cpufreq.c |
|
* |
|
* Copyright (C) 2004-2007 Atmel Corporation |
|
* |
|
* This file is subject to the terms and conditions of the GNU General Public |
|
* License. See the file "COPYING" in the main directory of this archive |
|
* for more details. |
|
*/ |
|
#define pr_fmt(fmt) "cpufreq: " fmt |
|
|
|
#include <linux/types.h> |
|
#include <linux/cpufreq.h> |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/init.h> |
|
#include <linux/err.h> |
|
#include <linux/cpumask.h> |
|
#include <linux/cpu.h> |
|
#include <linux/smp.h> |
|
#include <linux/clk.h> |
|
#include <linux/percpu.h> |
|
#include <linux/sh_clk.h> |
|
|
|
static DEFINE_PER_CPU(struct clk, sh_cpuclk); |
|
|
|
struct cpufreq_target { |
|
struct cpufreq_policy *policy; |
|
unsigned int freq; |
|
}; |
|
|
|
static unsigned int sh_cpufreq_get(unsigned int cpu) |
|
{ |
|
return (clk_get_rate(&per_cpu(sh_cpuclk, cpu)) + 500) / 1000; |
|
} |
|
|
|
static long __sh_cpufreq_target(void *arg) |
|
{ |
|
struct cpufreq_target *target = arg; |
|
struct cpufreq_policy *policy = target->policy; |
|
int cpu = policy->cpu; |
|
struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); |
|
struct cpufreq_freqs freqs; |
|
struct device *dev; |
|
long freq; |
|
|
|
if (smp_processor_id() != cpu) |
|
return -ENODEV; |
|
|
|
dev = get_cpu_device(cpu); |
|
|
|
/* Convert target_freq from kHz to Hz */ |
|
freq = clk_round_rate(cpuclk, target->freq * 1000); |
|
|
|
if (freq < (policy->min * 1000) || freq > (policy->max * 1000)) |
|
return -EINVAL; |
|
|
|
dev_dbg(dev, "requested frequency %u Hz\n", target->freq * 1000); |
|
|
|
freqs.old = sh_cpufreq_get(cpu); |
|
freqs.new = (freq + 500) / 1000; |
|
freqs.flags = 0; |
|
|
|
cpufreq_freq_transition_begin(target->policy, &freqs); |
|
clk_set_rate(cpuclk, freq); |
|
cpufreq_freq_transition_end(target->policy, &freqs, 0); |
|
|
|
dev_dbg(dev, "set frequency %lu Hz\n", freq); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Here we notify other drivers of the proposed change and the final change. |
|
*/ |
|
static int sh_cpufreq_target(struct cpufreq_policy *policy, |
|
unsigned int target_freq, |
|
unsigned int relation) |
|
{ |
|
struct cpufreq_target data = { .policy = policy, .freq = target_freq }; |
|
|
|
return work_on_cpu(policy->cpu, __sh_cpufreq_target, &data); |
|
} |
|
|
|
static int sh_cpufreq_verify(struct cpufreq_policy_data *policy) |
|
{ |
|
struct clk *cpuclk = &per_cpu(sh_cpuclk, policy->cpu); |
|
struct cpufreq_frequency_table *freq_table; |
|
|
|
freq_table = cpuclk->nr_freqs ? cpuclk->freq_table : NULL; |
|
if (freq_table) |
|
return cpufreq_frequency_table_verify(policy, freq_table); |
|
|
|
cpufreq_verify_within_cpu_limits(policy); |
|
|
|
policy->min = (clk_round_rate(cpuclk, 1) + 500) / 1000; |
|
policy->max = (clk_round_rate(cpuclk, ~0UL) + 500) / 1000; |
|
|
|
cpufreq_verify_within_cpu_limits(policy); |
|
return 0; |
|
} |
|
|
|
static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy) |
|
{ |
|
unsigned int cpu = policy->cpu; |
|
struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); |
|
struct cpufreq_frequency_table *freq_table; |
|
struct device *dev; |
|
|
|
dev = get_cpu_device(cpu); |
|
|
|
cpuclk = clk_get(dev, "cpu_clk"); |
|
if (IS_ERR(cpuclk)) { |
|
dev_err(dev, "couldn't get CPU clk\n"); |
|
return PTR_ERR(cpuclk); |
|
} |
|
|
|
freq_table = cpuclk->nr_freqs ? cpuclk->freq_table : NULL; |
|
if (freq_table) { |
|
policy->freq_table = freq_table; |
|
} else { |
|
dev_notice(dev, "no frequency table found, falling back " |
|
"to rate rounding.\n"); |
|
|
|
policy->min = policy->cpuinfo.min_freq = |
|
(clk_round_rate(cpuclk, 1) + 500) / 1000; |
|
policy->max = policy->cpuinfo.max_freq = |
|
(clk_round_rate(cpuclk, ~0UL) + 500) / 1000; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int sh_cpufreq_cpu_exit(struct cpufreq_policy *policy) |
|
{ |
|
unsigned int cpu = policy->cpu; |
|
struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); |
|
|
|
clk_put(cpuclk); |
|
|
|
return 0; |
|
} |
|
|
|
static struct cpufreq_driver sh_cpufreq_driver = { |
|
.name = "sh", |
|
.flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING, |
|
.get = sh_cpufreq_get, |
|
.target = sh_cpufreq_target, |
|
.verify = sh_cpufreq_verify, |
|
.init = sh_cpufreq_cpu_init, |
|
.exit = sh_cpufreq_cpu_exit, |
|
.attr = cpufreq_generic_attr, |
|
}; |
|
|
|
static int __init sh_cpufreq_module_init(void) |
|
{ |
|
pr_notice("SuperH CPU frequency driver.\n"); |
|
return cpufreq_register_driver(&sh_cpufreq_driver); |
|
} |
|
|
|
static void __exit sh_cpufreq_module_exit(void) |
|
{ |
|
cpufreq_unregister_driver(&sh_cpufreq_driver); |
|
} |
|
|
|
module_init(sh_cpufreq_module_init); |
|
module_exit(sh_cpufreq_module_exit); |
|
|
|
MODULE_AUTHOR("Paul Mundt <[email protected]>"); |
|
MODULE_DESCRIPTION("cpufreq driver for SuperH"); |
|
MODULE_LICENSE("GPL");
|
|
|