aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2007-07-20 13:38:19 +0900
committerPaul Mundt <lethal@linux-sh.org>2007-07-20 13:38:19 +0900
commitcb5ec75b8b0410bba4ae612d13a2f26e938bc49c (patch)
treedacfe59494055f6cfb862e49a033b6e03203c6d6
parentfe04d7798f611bca333df27d44484bdd62eef53f (diff)
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>
-rw-r--r--arch/sh/Kconfig2
-rw-r--r--arch/sh/kernel/cpufreq.c214
2 files changed, 81 insertions, 135 deletions
diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig
index 2a9682e1306..ddfd358a67c 100644
--- a/arch/sh/Kconfig
+++ b/arch/sh/Kconfig
@@ -475,7 +475,7 @@ source "drivers/cpufreq/Kconfig"
config SH_CPU_FREQ
tristate "SuperH CPU Frequency driver"
- depends on CPU_FREQ && CPU_SH4 && BROKEN
+ depends on CPU_FREQ
select CPU_FREQ_TABLE
help
This adds the cpufreq driver for SuperH. At present, only
diff --git a/arch/sh/kernel/cpufreq.c b/arch/sh/kernel/cpufreq.c
index 47abf6e49df..675118d6aea 100644
--- a/arch/sh/kernel/cpufreq.c
+++ b/arch/sh/kernel/cpufreq.c
@@ -3,89 +3,51 @@
*
* 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
*
- * 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; either version 2 of the License, or (at your
- * option) any later version.
+ * 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.
*/
#include <linux/types.h>
#include <linux/cpufreq.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/slab.h>
#include <linux/init.h>
-#include <linux/delay.h>
+#include <linux/err.h>
#include <linux/cpumask.h>
#include <linux/smp.h>
#include <linux/sched.h> /* set_cpus_allowed() */
-
+#include <linux/io.h>
+#include <linux/clk.h>
#include <asm/processor.h>
#include <asm/watchdog.h>
#include <asm/freq.h>
-#include <asm/io.h>
-
-/*
- * 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
-#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
+#include <asm/clock.h>
-static struct cpufreq_frequency_table sh_freqs[] = {
- { SH_FREQ_MAX, 0 },
- { SH_FREQ_MIN, 0 },
- { 0, CPUFREQ_TABLE_END },
-};
+static struct clk *cpuclk;
-static void sh_cpufreq_update_clocks(unsigned int set)
+static unsigned int sh_cpufreq_get(unsigned int cpu)
{
- current_cpu_data.cpu_clock = current_cpu_data.master_clock / clock_sets[set].ifc;
- 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;
+ return (clk_get_rate(cpuclk) + 500) / 1000;
}
-/* 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.
*/
-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;
struct cpufreq_freqs freqs;
+ long freq;
if (!cpu_online(cpu))
return -ENODEV;
@@ -95,125 +57,109 @@ static int sh_cpufreq_setstate(unsigned int cpu, unsigned int set)
BUG_ON(smp_processor_id() != cpu);
- freqs.cpu = cpu;
- freqs.old = current_cpu_data.cpu_clock / 1000;
- freqs.new = (current_cpu_data.master_clock / clock_sets[set].ifc) / 1000;
+ /* Convert target_freq from kHz to Hz */
+ freq = clk_round_rate(cpuclk, target_freq * 1000);
- 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);
+ 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);
set_cpus_allowed(current, cpus_allowed);
+ clk_set_rate(cpuclk, freq);
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+ pr_debug("cpufreq: set frequency %lu Hz\n", freq);
+
return 0;
}
static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
- unsigned int min_freq, max_freq;
- unsigned int ifc, bfc, pfc;
+ printk(KERN_INFO "cpufreq: SuperH CPU frequency driver.\n");
if (!cpu_online(policy->cpu))
return -ENODEV;
- /* Update our maximum clock set */
- get_current_frequency_divisors(&ifc, &bfc, &pfc);
- clock_sets[MAX_CLOCK_SET].ifc = ifc;
- clock_sets[MAX_CLOCK_SET].bfc = bfc;
- 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;
+ cpuclk = clk_get(NULL, "cpu_clk");
+ if (IS_ERR(cpuclk)) {
+ printk(KERN_ERR "cpufreq: couldn't get CPU clk\n");
+ return PTR_ERR(cpuclk);
+ }
/* 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->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;
-static int sh_cpufreq_verify(struct cpufreq_policy *policy)
-{
- return cpufreq_frequency_table_verify(policy, &sh_freqs[0]);
-}
-static int sh_cpufreq_target(struct cpufreq_policy *policy,
- unsigned int target_freq,
- unsigned int relation)
-{
- unsigned int set, idx = 0;
+ /*
+ * 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");
- if (cpufreq_frequency_table_target(policy, &sh_freqs[0], target_freq, relation, &idx))
+ clk_put(cpuclk);
return -EINVAL;
+ }
- set = (idx == SH_FREQ_MIN) ? MIN_CLOCK_SET : MAX_CLOCK_SET;
+ 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);
- sh_cpufreq_setstate(policy->cpu, set);
+ return 0;
+}
+static int sh_cpufreq_verify(struct cpufreq_policy *policy)
+{
+ cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
+ policy->cpuinfo.max_freq);
+ return 0;
+}
+
+static int sh_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ clk_put(cpuclk);
return 0;
}
static struct cpufreq_driver sh_cpufreq_driver = {
.owner = THIS_MODULE,
- .name = "SH cpufreq",
+ .name = "sh",
.init = sh_cpufreq_cpu_init,
.verify = sh_cpufreq_verify,
.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 -EINVAL;
- if (cpufreq_register_driver(&sh_cpufreq_driver))
- return -EINVAL;
-
- return 0;
+ return cpufreq_register_driver(&sh_cpufreq_driver);
}
-static void __exit sh_cpufreq_exit(void)
+static void __exit sh_cpufreq_module_exit(void)
{
cpufreq_unregister_driver(&sh_cpufreq_driver);
}
-module_init(sh_cpufreq_init);
-module_exit(sh_cpufreq_exit);
+module_init(sh_cpufreq_module_init);
+module_exit(sh_cpufreq_module_exit);
MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
MODULE_DESCRIPTION("cpufreq driver for SuperH");
MODULE_LICENSE("GPL");
-