diff options
Diffstat (limited to 'arch/mips/kernel/cpufreq')
-rw-r--r-- | arch/mips/kernel/cpufreq/Kconfig | 41 | ||||
-rw-r--r-- | arch/mips/kernel/cpufreq/Makefile | 5 | ||||
-rw-r--r-- | arch/mips/kernel/cpufreq/loongson2_clock.c | 166 | ||||
-rw-r--r-- | arch/mips/kernel/cpufreq/loongson2_cpufreq.c | 227 |
4 files changed, 439 insertions, 0 deletions
diff --git a/arch/mips/kernel/cpufreq/Kconfig b/arch/mips/kernel/cpufreq/Kconfig new file mode 100644 index 000000000000..58c601eee6fd --- /dev/null +++ b/arch/mips/kernel/cpufreq/Kconfig @@ -0,0 +1,41 @@ +# +# CPU Frequency scaling +# + +config MIPS_EXTERNAL_TIMER + bool + +config MIPS_CPUFREQ + bool + default y + depends on CPU_SUPPORTS_CPUFREQ && MIPS_EXTERNAL_TIMER + +if MIPS_CPUFREQ + +menu "CPU Frequency scaling" + +source "drivers/cpufreq/Kconfig" + +if CPU_FREQ + +comment "CPUFreq processor drivers" + +config LOONGSON2_CPUFREQ + tristate "Loongson2 CPUFreq Driver" + select CPU_FREQ_TABLE + depends on MIPS_CPUFREQ + help + This option adds a CPUFreq driver for loongson processors which + support software configurable cpu frequency. + + Loongson2F and it's successors support this feature. + + For details, take a look at <file:Documentation/cpu-freq/>. + + If in doubt, say N. + +endif # CPU_FREQ + +endmenu + +endif # MIPS_CPUFREQ diff --git a/arch/mips/kernel/cpufreq/Makefile b/arch/mips/kernel/cpufreq/Makefile new file mode 100644 index 000000000000..c3479a432efe --- /dev/null +++ b/arch/mips/kernel/cpufreq/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Linux/MIPS cpufreq. +# + +obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o loongson2_clock.o diff --git a/arch/mips/kernel/cpufreq/loongson2_clock.c b/arch/mips/kernel/cpufreq/loongson2_clock.c new file mode 100644 index 000000000000..d7ca256e33ef --- /dev/null +++ b/arch/mips/kernel/cpufreq/loongson2_clock.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2006 - 2008 Lemote Inc. & Insititute of Computing Technology + * Author: Yanhua, yanh@lemote.com + * + * 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/cpufreq.h> +#include <linux/platform_device.h> + +#include <asm/clock.h> + +#include <loongson.h> + +static LIST_HEAD(clock_list); +static DEFINE_SPINLOCK(clock_lock); +static DEFINE_MUTEX(clock_list_sem); + +/* Minimum CLK support */ +enum { + DC_ZERO, DC_25PT = 2, DC_37PT, DC_50PT, DC_62PT, DC_75PT, + DC_87PT, DC_DISABLE, DC_RESV +}; + +struct cpufreq_frequency_table loongson2_clockmod_table[] = { + {DC_RESV, CPUFREQ_ENTRY_INVALID}, + {DC_ZERO, CPUFREQ_ENTRY_INVALID}, + {DC_25PT, 0}, + {DC_37PT, 0}, + {DC_50PT, 0}, + {DC_62PT, 0}, + {DC_75PT, 0}, + {DC_87PT, 0}, + {DC_DISABLE, 0}, + {DC_RESV, CPUFREQ_TABLE_END}, +}; +EXPORT_SYMBOL_GPL(loongson2_clockmod_table); + +static struct clk cpu_clk = { + .name = "cpu_clk", + .flags = CLK_ALWAYS_ENABLED | CLK_RATE_PROPAGATES, + .rate = 800000000, +}; + +struct clk *clk_get(struct device *dev, const char *id) +{ + return &cpu_clk; +} +EXPORT_SYMBOL(clk_get); + +static void propagate_rate(struct clk *clk) +{ + struct clk *clkp; + + list_for_each_entry(clkp, &clock_list, node) { + if (likely(clkp->parent != clk)) + continue; + if (likely(clkp->ops && clkp->ops->recalc)) + clkp->ops->recalc(clkp); + if (unlikely(clkp->flags & CLK_RATE_PROPAGATES)) + propagate_rate(clkp); + } +} + +int clk_enable(struct clk *clk) +{ + return 0; +} +EXPORT_SYMBOL(clk_enable); + +void clk_disable(struct clk *clk) +{ +} +EXPORT_SYMBOL(clk_disable); + +unsigned long clk_get_rate(struct clk *clk) +{ + return (unsigned long)clk->rate; +} +EXPORT_SYMBOL(clk_get_rate); + +void clk_put(struct clk *clk) +{ +} +EXPORT_SYMBOL(clk_put); + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + return clk_set_rate_ex(clk, rate, 0); +} +EXPORT_SYMBOL_GPL(clk_set_rate); + +int clk_set_rate_ex(struct clk *clk, unsigned long rate, int algo_id) +{ + int ret = 0; + int regval; + int i; + + if (likely(clk->ops && clk->ops->set_rate)) { + unsigned long flags; + + spin_lock_irqsave(&clock_lock, flags); + ret = clk->ops->set_rate(clk, rate, algo_id); + spin_unlock_irqrestore(&clock_lock, flags); + } + + if (unlikely(clk->flags & CLK_RATE_PROPAGATES)) + propagate_rate(clk); + + for (i = 0; loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END; + i++) { + if (loongson2_clockmod_table[i].frequency == + CPUFREQ_ENTRY_INVALID) + continue; + if (rate == loongson2_clockmod_table[i].frequency) + break; + } + if (rate != loongson2_clockmod_table[i].frequency) + return -ENOTSUPP; + + clk->rate = rate; + + regval = LOONGSON_CHIPCFG0; + regval = (regval & ~0x7) | (loongson2_clockmod_table[i].index - 1); + LOONGSON_CHIPCFG0 = regval; + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_rate_ex); + +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + if (likely(clk->ops && clk->ops->round_rate)) { + unsigned long flags, rounded; + + spin_lock_irqsave(&clock_lock, flags); + rounded = clk->ops->round_rate(clk, rate); + spin_unlock_irqrestore(&clock_lock, flags); + + return rounded; + } + + return rate; +} +EXPORT_SYMBOL_GPL(clk_round_rate); + +/* + * This is the simple version of Loongson-2 wait, Maybe we need do this in + * interrupt disabled content + */ + +DEFINE_SPINLOCK(loongson2_wait_lock); +void loongson2_cpu_wait(void) +{ + u32 cpu_freq; + unsigned long flags; + + spin_lock_irqsave(&loongson2_wait_lock, flags); + cpu_freq = LOONGSON_CHIPCFG0; + LOONGSON_CHIPCFG0 &= ~0x7; /* Put CPU into wait mode */ + LOONGSON_CHIPCFG0 = cpu_freq; /* Restore CPU state */ + spin_unlock_irqrestore(&loongson2_wait_lock, flags); +} +EXPORT_SYMBOL_GPL(loongson2_cpu_wait); diff --git a/arch/mips/kernel/cpufreq/loongson2_cpufreq.c b/arch/mips/kernel/cpufreq/loongson2_cpufreq.c new file mode 100644 index 000000000000..2f6a0b147ab8 --- /dev/null +++ b/arch/mips/kernel/cpufreq/loongson2_cpufreq.c @@ -0,0 +1,227 @@ +/* + * Cpufreq driver for the loongson-2 processors + * + * The 2E revision of loongson processor not support this feature. + * + * Copyright (C) 2006 - 2008 Lemote Inc. & Insititute of Computing Technology + * Author: Yanhua, yanh@lemote.com + * + * 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/cpufreq.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/sched.h> /* set_cpus_allowed() */ +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <asm/clock.h> + +#include <loongson.h> + +static uint nowait; + +static struct clk *cpuclk; + +static void (*saved_cpu_wait) (void); + +static int loongson2_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data); + +static struct notifier_block loongson2_cpufreq_notifier_block = { + .notifier_call = loongson2_cpu_freq_notifier +}; + +static int loongson2_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data) +{ + if (val == CPUFREQ_POSTCHANGE) + current_cpu_data.udelay_val = loops_per_jiffy; + + return 0; +} + +static unsigned int loongson2_cpufreq_get(unsigned int cpu) +{ + return clk_get_rate(cpuclk); +} + +/* + * Here we notify other drivers of the proposed change and the final change. + */ +static int loongson2_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int cpu = policy->cpu; + unsigned int newstate = 0; + cpumask_t cpus_allowed; + struct cpufreq_freqs freqs; + unsigned int freq; + + if (!cpu_online(cpu)) + return -ENODEV; + + cpus_allowed = current->cpus_allowed; + set_cpus_allowed(current, cpumask_of_cpu(cpu)); + + if (cpufreq_frequency_table_target + (policy, &loongson2_clockmod_table[0], target_freq, relation, + &newstate)) + return -EINVAL; + + freq = + ((cpu_clock_freq / 1000) * + loongson2_clockmod_table[newstate].index) / 8; + if (freq < policy->min || freq > policy->max) + return -EINVAL; + + pr_debug("cpufreq: requested frequency %u Hz\n", target_freq * 1000); + + freqs.cpu = cpu; + freqs.old = loongson2_cpufreq_get(cpu); + freqs.new = freq; + freqs.flags = 0; + + if (freqs.new == freqs.old) + return 0; + + /* notifiers */ + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + set_cpus_allowed(current, cpus_allowed); + + /* setting the cpu frequency */ + clk_set_rate(cpuclk, freq); + + /* notifiers */ + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + pr_debug("cpufreq: set frequency %u kHz\n", freq); + + return 0; +} + +static int loongson2_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + int i; + + if (!cpu_online(policy->cpu)) + return -ENODEV; + + cpuclk = clk_get(NULL, "cpu_clk"); + if (IS_ERR(cpuclk)) { + printk(KERN_ERR "cpufreq: couldn't get CPU clk\n"); + return PTR_ERR(cpuclk); + } + + cpuclk->rate = cpu_clock_freq / 1000; + if (!cpuclk->rate) + return -EINVAL; + + /* clock table init */ + for (i = 2; + (loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END); + i++) + loongson2_clockmod_table[i].frequency = (cpuclk->rate * i) / 8; + + policy->cur = loongson2_cpufreq_get(policy->cpu); + + cpufreq_frequency_table_get_attr(&loongson2_clockmod_table[0], + policy->cpu); + + return cpufreq_frequency_table_cpuinfo(policy, + &loongson2_clockmod_table[0]); +} + +static int loongson2_cpufreq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, + &loongson2_clockmod_table[0]); +} + +static int loongson2_cpufreq_exit(struct cpufreq_policy *policy) +{ + clk_put(cpuclk); + return 0; +} + +static struct freq_attr *loongson2_table_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver loongson2_cpufreq_driver = { + .owner = THIS_MODULE, + .name = "loongson2", + .init = loongson2_cpufreq_cpu_init, + .verify = loongson2_cpufreq_verify, + .target = loongson2_cpufreq_target, + .get = loongson2_cpufreq_get, + .exit = loongson2_cpufreq_exit, + .attr = loongson2_table_attr, +}; + +static struct platform_device_id platform_device_ids[] = { + { + .name = "loongson2_cpufreq", + }, + {} +}; + +MODULE_DEVICE_TABLE(platform, platform_device_ids); + +static struct platform_driver platform_driver = { + .driver = { + .name = "loongson2_cpufreq", + .owner = THIS_MODULE, + }, + .id_table = platform_device_ids, +}; + +static int __init cpufreq_init(void) +{ + int ret; + + /* Register platform stuff */ + ret = platform_driver_register(&platform_driver); + if (ret) + return ret; + + pr_info("cpufreq: Loongson-2F CPU frequency driver.\n"); + + cpufreq_register_notifier(&loongson2_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + ret = cpufreq_register_driver(&loongson2_cpufreq_driver); + + if (!ret && !nowait) { + saved_cpu_wait = cpu_wait; + cpu_wait = loongson2_cpu_wait; + } + + return ret; +} + +static void __exit cpufreq_exit(void) +{ + if (!nowait && saved_cpu_wait) + cpu_wait = saved_cpu_wait; + cpufreq_unregister_driver(&loongson2_cpufreq_driver); + cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + platform_driver_unregister(&platform_driver); +} + +module_init(cpufreq_init); +module_exit(cpufreq_exit); + +module_param(nowait, uint, 0644); +MODULE_PARM_DESC(nowait, "Disable Loongson-2F specific wait"); + +MODULE_AUTHOR("Yanhua <yanh@lemote.com>"); +MODULE_DESCRIPTION("cpufreq driver for Loongson2F"); +MODULE_LICENSE("GPL"); |