diff options
Diffstat (limited to 'drivers/cpufreq/sc520_freq.c')
| -rw-r--r-- | drivers/cpufreq/sc520_freq.c | 192 | 
1 files changed, 192 insertions, 0 deletions
diff --git a/drivers/cpufreq/sc520_freq.c b/drivers/cpufreq/sc520_freq.c new file mode 100644 index 00000000000..1e205e6b172 --- /dev/null +++ b/drivers/cpufreq/sc520_freq.c @@ -0,0 +1,192 @@ +/* + *	sc520_freq.c: cpufreq driver for the AMD Elan sc520 + * + *	Copyright (C) 2005 Sean Young <sean@mess.org> + * + *	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. + * + *	Based on elanfreq.c + * + *	2005-03-30: - initial revision + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/delay.h> +#include <linux/cpufreq.h> +#include <linux/timex.h> +#include <linux/io.h> + +#include <asm/msr.h> + +#define MMCR_BASE	0xfffef000	/* The default base address */ +#define OFFS_CPUCTL	0x2   /* CPU Control Register */ + +static __u8 __iomem *cpuctl; + +#define PFX "sc520_freq: " + +static struct cpufreq_frequency_table sc520_freq_table[] = { +	{0x01,	100000}, +	{0x02,	133000}, +	{0,	CPUFREQ_TABLE_END}, +}; + +static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu) +{ +	u8 clockspeed_reg = *cpuctl; + +	switch (clockspeed_reg & 0x03) { +	default: +		printk(KERN_ERR PFX "error: cpuctl register has unexpected " +				"value %02x\n", clockspeed_reg); +	case 0x01: +		return 100000; +	case 0x02: +		return 133000; +	} +} + +static void sc520_freq_set_cpu_state(unsigned int state) +{ + +	struct cpufreq_freqs	freqs; +	u8 clockspeed_reg; + +	freqs.old = sc520_freq_get_cpu_frequency(0); +	freqs.new = sc520_freq_table[state].frequency; +	freqs.cpu = 0; /* AMD Elan is UP */ + +	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + +	pr_debug("attempting to set frequency to %i kHz\n", +			sc520_freq_table[state].frequency); + +	local_irq_disable(); + +	clockspeed_reg = *cpuctl & ~0x03; +	*cpuctl = clockspeed_reg | sc520_freq_table[state].index; + +	local_irq_enable(); + +	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); +}; + +static int sc520_freq_verify(struct cpufreq_policy *policy) +{ +	return cpufreq_frequency_table_verify(policy, &sc520_freq_table[0]); +} + +static int sc520_freq_target(struct cpufreq_policy *policy, +			    unsigned int target_freq, +			    unsigned int relation) +{ +	unsigned int newstate = 0; + +	if (cpufreq_frequency_table_target(policy, sc520_freq_table, +				target_freq, relation, &newstate)) +		return -EINVAL; + +	sc520_freq_set_cpu_state(newstate); + +	return 0; +} + + +/* + *	Module init and exit code + */ + +static int sc520_freq_cpu_init(struct cpufreq_policy *policy) +{ +	struct cpuinfo_x86 *c = &cpu_data(0); +	int result; + +	/* capability check */ +	if (c->x86_vendor != X86_VENDOR_AMD || +	    c->x86 != 4 || c->x86_model != 9) +		return -ENODEV; + +	/* cpuinfo and default policy values */ +	policy->cpuinfo.transition_latency = 1000000; /* 1ms */ +	policy->cur = sc520_freq_get_cpu_frequency(0); + +	result = cpufreq_frequency_table_cpuinfo(policy, sc520_freq_table); +	if (result) +		return result; + +	cpufreq_frequency_table_get_attr(sc520_freq_table, policy->cpu); + +	return 0; +} + + +static int sc520_freq_cpu_exit(struct cpufreq_policy *policy) +{ +	cpufreq_frequency_table_put_attr(policy->cpu); +	return 0; +} + + +static struct freq_attr *sc520_freq_attr[] = { +	&cpufreq_freq_attr_scaling_available_freqs, +	NULL, +}; + + +static struct cpufreq_driver sc520_freq_driver = { +	.get	= sc520_freq_get_cpu_frequency, +	.verify	= sc520_freq_verify, +	.target	= sc520_freq_target, +	.init	= sc520_freq_cpu_init, +	.exit	= sc520_freq_cpu_exit, +	.name	= "sc520_freq", +	.owner	= THIS_MODULE, +	.attr	= sc520_freq_attr, +}; + + +static int __init sc520_freq_init(void) +{ +	struct cpuinfo_x86 *c = &cpu_data(0); +	int err; + +	/* Test if we have the right hardware */ +	if (c->x86_vendor != X86_VENDOR_AMD || +	    c->x86 != 4 || c->x86_model != 9) { +		pr_debug("no Elan SC520 processor found!\n"); +		return -ENODEV; +	} +	cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1); +	if (!cpuctl) { +		printk(KERN_ERR "sc520_freq: error: failed to remap memory\n"); +		return -ENOMEM; +	} + +	err = cpufreq_register_driver(&sc520_freq_driver); +	if (err) +		iounmap(cpuctl); + +	return err; +} + + +static void __exit sc520_freq_exit(void) +{ +	cpufreq_unregister_driver(&sc520_freq_driver); +	iounmap(cpuctl); +} + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sean Young <sean@mess.org>"); +MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU"); + +module_init(sc520_freq_init); +module_exit(sc520_freq_exit); +  |