mirror of https://gitee.com/openkylin/linux.git
sh: cpufreq: clock framework support.
This gets the SH cpufreq working again. We follow the changes in the AVR32 implementation for wrapping in to the clock framework. CPUs that wish to use this are required to define rate rounding primitives in order to satisfy clk_round_rate(). This works well enough for the common case, though we should look at unifying this driver across all of the platforms that implement clock framework support in one capacity or another. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
This commit is contained in:
parent
fe04d7798f
commit
cb5ec75b8b
|
@ -475,7 +475,7 @@ source "drivers/cpufreq/Kconfig"
|
||||||
|
|
||||||
config SH_CPU_FREQ
|
config SH_CPU_FREQ
|
||||||
tristate "SuperH CPU Frequency driver"
|
tristate "SuperH CPU Frequency driver"
|
||||||
depends on CPU_FREQ && CPU_SH4 && BROKEN
|
depends on CPU_FREQ
|
||||||
select CPU_FREQ_TABLE
|
select CPU_FREQ_TABLE
|
||||||
help
|
help
|
||||||
This adds the cpufreq driver for SuperH. At present, only
|
This adds the cpufreq driver for SuperH. At present, only
|
||||||
|
|
|
@ -3,89 +3,51 @@
|
||||||
*
|
*
|
||||||
* cpufreq driver for the SuperH processors.
|
* cpufreq driver for the SuperH processors.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2002, 2003, 2004, 2005 Paul Mundt
|
* Copyright (C) 2002 - 2007 Paul Mundt
|
||||||
* Copyright (C) 2002 M. R. Brown
|
* Copyright (C) 2002 M. R. Brown
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
* Clock framework bits from arch/avr32/mach-at32ap/cpufreq.c
|
||||||
* under the terms of the GNU General Public License as published by the
|
*
|
||||||
* Free Software Foundation; either version 2 of the License, or (at your
|
* Copyright (C) 2004-2007 Atmel Corporation
|
||||||
* option) any later version.
|
*
|
||||||
|
* 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.
|
||||||
*/
|
*/
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/cpufreq.h>
|
#include <linux/cpufreq.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/err.h>
|
||||||
#include <linux/cpumask.h>
|
#include <linux/cpumask.h>
|
||||||
#include <linux/smp.h>
|
#include <linux/smp.h>
|
||||||
#include <linux/sched.h> /* set_cpus_allowed() */
|
#include <linux/sched.h> /* set_cpus_allowed() */
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/clk.h>
|
||||||
#include <asm/processor.h>
|
#include <asm/processor.h>
|
||||||
#include <asm/watchdog.h>
|
#include <asm/watchdog.h>
|
||||||
#include <asm/freq.h>
|
#include <asm/freq.h>
|
||||||
#include <asm/io.h>
|
#include <asm/clock.h>
|
||||||
|
|
||||||
/*
|
static struct clk *cpuclk;
|
||||||
* For SuperH, each policy change requires that we change the IFC, BFC, and
|
|
||||||
* PFC at the same time. Here we define sane values that won't trash the
|
|
||||||
* system.
|
|
||||||
*
|
|
||||||
* Note the max set is computed at runtime, we use the divisors that we booted
|
|
||||||
* with to setup our maximum operating frequencies.
|
|
||||||
*/
|
|
||||||
struct clock_set {
|
|
||||||
unsigned int ifc;
|
|
||||||
unsigned int bfc;
|
|
||||||
unsigned int pfc;
|
|
||||||
} clock_sets[] = {
|
|
||||||
#if defined(CONFIG_CPU_SH3) || defined(CONFIG_CPU_SH2)
|
|
||||||
{ 0, 0, 0 }, /* not implemented yet */
|
|
||||||
#elif defined(CONFIG_CPU_SH4)
|
|
||||||
{ 4, 8, 8 }, /* min - IFC: 1/4, BFC: 1/8, PFC: 1/8 */
|
|
||||||
{ 1, 2, 2 }, /* max - IFC: 1, BFC: 1/2, PFC: 1/2 */
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MIN_CLOCK_SET 0
|
static unsigned int sh_cpufreq_get(unsigned int cpu)
|
||||||
#define MAX_CLOCK_SET (ARRAY_SIZE(clock_sets) - 1)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For the time being, we only support two frequencies, which in turn are
|
|
||||||
* aimed at the POWERSAVE and PERFORMANCE policies, which in turn are derived
|
|
||||||
* directly from the respective min/max clock sets. Technically we could
|
|
||||||
* support a wider range of frequencies, but these vary far too much for each
|
|
||||||
* CPU subtype (and we'd have to construct a frequency table for each subtype).
|
|
||||||
*
|
|
||||||
* Maybe something to implement in the future..
|
|
||||||
*/
|
|
||||||
#define SH_FREQ_MAX 0
|
|
||||||
#define SH_FREQ_MIN 1
|
|
||||||
|
|
||||||
static struct cpufreq_frequency_table sh_freqs[] = {
|
|
||||||
{ SH_FREQ_MAX, 0 },
|
|
||||||
{ SH_FREQ_MIN, 0 },
|
|
||||||
{ 0, CPUFREQ_TABLE_END },
|
|
||||||
};
|
|
||||||
|
|
||||||
static void sh_cpufreq_update_clocks(unsigned int set)
|
|
||||||
{
|
{
|
||||||
current_cpu_data.cpu_clock = current_cpu_data.master_clock / clock_sets[set].ifc;
|
return (clk_get_rate(cpuclk) + 500) / 1000;
|
||||||
current_cpu_data.bus_clock = current_cpu_data.master_clock / clock_sets[set].bfc;
|
|
||||||
current_cpu_data.module_clock = current_cpu_data.master_clock / clock_sets[set].pfc;
|
|
||||||
current_cpu_data.loops_per_jiffy = loops_per_jiffy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* XXX: This needs to be split out per CPU and CPU subtype. */
|
|
||||||
/*
|
/*
|
||||||
* Here we notify other drivers of the proposed change and the final change.
|
* Here we notify other drivers of the proposed change and the final change.
|
||||||
*/
|
*/
|
||||||
static int sh_cpufreq_setstate(unsigned int cpu, unsigned int set)
|
static int sh_cpufreq_target(struct cpufreq_policy *policy,
|
||||||
|
unsigned int target_freq,
|
||||||
|
unsigned int relation)
|
||||||
{
|
{
|
||||||
unsigned short frqcr = ctrl_inw(FRQCR);
|
unsigned int cpu = policy->cpu;
|
||||||
cpumask_t cpus_allowed;
|
cpumask_t cpus_allowed;
|
||||||
struct cpufreq_freqs freqs;
|
struct cpufreq_freqs freqs;
|
||||||
|
long freq;
|
||||||
|
|
||||||
if (!cpu_online(cpu))
|
if (!cpu_online(cpu))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
@ -95,125 +57,109 @@ static int sh_cpufreq_setstate(unsigned int cpu, unsigned int set)
|
||||||
|
|
||||||
BUG_ON(smp_processor_id() != cpu);
|
BUG_ON(smp_processor_id() != cpu);
|
||||||
|
|
||||||
freqs.cpu = cpu;
|
/* Convert target_freq from kHz to Hz */
|
||||||
freqs.old = current_cpu_data.cpu_clock / 1000;
|
freq = clk_round_rate(cpuclk, target_freq * 1000);
|
||||||
freqs.new = (current_cpu_data.master_clock / clock_sets[set].ifc) / 1000;
|
|
||||||
|
if (freq < (policy->min * 1000) || freq > (policy->max * 1000))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
pr_debug("cpufreq: requested frequency %u Hz\n", target_freq * 1000);
|
||||||
|
|
||||||
|
freqs.cpu = cpu;
|
||||||
|
freqs.old = sh_cpufreq_get(cpu);
|
||||||
|
freqs.new = (freq + 500) / 1000;
|
||||||
|
freqs.flags = 0;
|
||||||
|
|
||||||
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
|
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
|
||||||
#if defined(CONFIG_CPU_SH3)
|
|
||||||
frqcr |= (newstate & 0x4000) << 14;
|
|
||||||
frqcr |= (newstate & 0x000c) << 2;
|
|
||||||
#elif defined(CONFIG_CPU_SH4)
|
|
||||||
/*
|
|
||||||
* FRQCR.PLL2EN is 1, we need to allow the PLL to stabilize by
|
|
||||||
* initializing the WDT.
|
|
||||||
*/
|
|
||||||
if (frqcr & (1 << 9)) {
|
|
||||||
__u8 csr;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the overflow period to the highest available,
|
|
||||||
* in this case a 1/4096 division ratio yields a 5.25ms
|
|
||||||
* overflow period. See asm-sh/watchdog.h for more
|
|
||||||
* information and a range of other divisors.
|
|
||||||
*/
|
|
||||||
csr = sh_wdt_read_csr();
|
|
||||||
csr |= WTCSR_CKS_4096;
|
|
||||||
sh_wdt_write_csr(csr);
|
|
||||||
|
|
||||||
sh_wdt_write_cnt(0);
|
|
||||||
}
|
|
||||||
frqcr &= 0x0e00; /* Clear ifc, bfc, pfc */
|
|
||||||
frqcr |= get_ifc_value(clock_sets[set].ifc) << 6;
|
|
||||||
frqcr |= get_bfc_value(clock_sets[set].bfc) << 3;
|
|
||||||
frqcr |= get_pfc_value(clock_sets[set].pfc);
|
|
||||||
#endif
|
|
||||||
ctrl_outw(frqcr, FRQCR);
|
|
||||||
sh_cpufreq_update_clocks(set);
|
|
||||||
|
|
||||||
set_cpus_allowed(current, cpus_allowed);
|
set_cpus_allowed(current, cpus_allowed);
|
||||||
|
clk_set_rate(cpuclk, freq);
|
||||||
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
|
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
|
||||||
|
|
||||||
|
pr_debug("cpufreq: set frequency %lu Hz\n", freq);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy)
|
static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy)
|
||||||
{
|
{
|
||||||
unsigned int min_freq, max_freq;
|
printk(KERN_INFO "cpufreq: SuperH CPU frequency driver.\n");
|
||||||
unsigned int ifc, bfc, pfc;
|
|
||||||
|
|
||||||
if (!cpu_online(policy->cpu))
|
if (!cpu_online(policy->cpu))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
/* Update our maximum clock set */
|
cpuclk = clk_get(NULL, "cpu_clk");
|
||||||
get_current_frequency_divisors(&ifc, &bfc, &pfc);
|
if (IS_ERR(cpuclk)) {
|
||||||
clock_sets[MAX_CLOCK_SET].ifc = ifc;
|
printk(KERN_ERR "cpufreq: couldn't get CPU clk\n");
|
||||||
clock_sets[MAX_CLOCK_SET].bfc = bfc;
|
return PTR_ERR(cpuclk);
|
||||||
clock_sets[MAX_CLOCK_SET].pfc = pfc;
|
}
|
||||||
|
|
||||||
/* Convert from Hz to kHz */
|
|
||||||
max_freq = current_cpu_data.cpu_clock / 1000;
|
|
||||||
min_freq = (current_cpu_data.master_clock / clock_sets[MIN_CLOCK_SET].ifc) / 1000;
|
|
||||||
|
|
||||||
sh_freqs[SH_FREQ_MAX].frequency = max_freq;
|
|
||||||
sh_freqs[SH_FREQ_MIN].frequency = min_freq;
|
|
||||||
|
|
||||||
/* cpuinfo and default policy values */
|
/* cpuinfo and default policy values */
|
||||||
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
|
policy->cpuinfo.min_freq = (clk_round_rate(cpuclk, 1) + 500) / 1000;
|
||||||
|
policy->cpuinfo.max_freq = (clk_round_rate(cpuclk, ~0UL) + 500) / 1000;
|
||||||
policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
|
policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
|
||||||
policy->cur = max_freq;
|
|
||||||
|
|
||||||
return cpufreq_frequency_table_cpuinfo(policy, &sh_freqs[0]);
|
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
|
||||||
|
policy->cur = sh_cpufreq_get(policy->cpu);
|
||||||
|
policy->min = policy->cpuinfo.min_freq;
|
||||||
|
policy->max = policy->cpuinfo.max_freq;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Catch the cases where the clock framework hasn't been wired up
|
||||||
|
* properly to support scaling.
|
||||||
|
*/
|
||||||
|
if (unlikely(policy->min == policy->max)) {
|
||||||
|
printk(KERN_ERR "cpufreq: clock framework rate rounding "
|
||||||
|
"not supported on this CPU.\n");
|
||||||
|
|
||||||
|
clk_put(cpuclk);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
printk(KERN_INFO "cpufreq: Frequencies - Minimum %u.%03u MHz, "
|
||||||
|
"Maximum %u.%03u MHz.\n",
|
||||||
|
policy->min / 1000, policy->min % 1000,
|
||||||
|
policy->max / 1000, policy->max % 1000);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sh_cpufreq_verify(struct cpufreq_policy *policy)
|
static int sh_cpufreq_verify(struct cpufreq_policy *policy)
|
||||||
{
|
{
|
||||||
return cpufreq_frequency_table_verify(policy, &sh_freqs[0]);
|
cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
|
||||||
|
policy->cpuinfo.max_freq);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sh_cpufreq_target(struct cpufreq_policy *policy,
|
static int sh_cpufreq_exit(struct cpufreq_policy *policy)
|
||||||
unsigned int target_freq,
|
|
||||||
unsigned int relation)
|
|
||||||
{
|
{
|
||||||
unsigned int set, idx = 0;
|
clk_put(cpuclk);
|
||||||
|
|
||||||
if (cpufreq_frequency_table_target(policy, &sh_freqs[0], target_freq, relation, &idx))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
set = (idx == SH_FREQ_MIN) ? MIN_CLOCK_SET : MAX_CLOCK_SET;
|
|
||||||
|
|
||||||
sh_cpufreq_setstate(policy->cpu, set);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct cpufreq_driver sh_cpufreq_driver = {
|
static struct cpufreq_driver sh_cpufreq_driver = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.name = "SH cpufreq",
|
.name = "sh",
|
||||||
.init = sh_cpufreq_cpu_init,
|
.init = sh_cpufreq_cpu_init,
|
||||||
.verify = sh_cpufreq_verify,
|
.verify = sh_cpufreq_verify,
|
||||||
.target = sh_cpufreq_target,
|
.target = sh_cpufreq_target,
|
||||||
|
.get = sh_cpufreq_get,
|
||||||
|
.exit = sh_cpufreq_exit,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int __init sh_cpufreq_init(void)
|
static int __init sh_cpufreq_module_init(void)
|
||||||
{
|
{
|
||||||
if (!current_cpu_data.cpu_clock)
|
return cpufreq_register_driver(&sh_cpufreq_driver);
|
||||||
return -EINVAL;
|
|
||||||
if (cpufreq_register_driver(&sh_cpufreq_driver))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit sh_cpufreq_exit(void)
|
static void __exit sh_cpufreq_module_exit(void)
|
||||||
{
|
{
|
||||||
cpufreq_unregister_driver(&sh_cpufreq_driver);
|
cpufreq_unregister_driver(&sh_cpufreq_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(sh_cpufreq_init);
|
module_init(sh_cpufreq_module_init);
|
||||||
module_exit(sh_cpufreq_exit);
|
module_exit(sh_cpufreq_module_exit);
|
||||||
|
|
||||||
MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
|
MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
|
||||||
MODULE_DESCRIPTION("cpufreq driver for SuperH");
|
MODULE_DESCRIPTION("cpufreq driver for SuperH");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue