xref: /linux/drivers/cpufreq/sc520_freq.c (revision f2ee442115c9b6219083c019939a9cc0c9abb2f8)
1 /*
2  *	sc520_freq.c: cpufreq driver for the AMD Elan sc520
3  *
4  *	Copyright (C) 2005 Sean Young <sean@mess.org>
5  *
6  *	This program is free software; you can redistribute it and/or
7  *	modify it under the terms of the GNU General Public License
8  *	as published by the Free Software Foundation; either version
9  *	2 of the License, or (at your option) any later version.
10  *
11  *	Based on elanfreq.c
12  *
13  *	2005-03-30: - initial revision
14  */
15 
16 #include <linux/kernel.h>
17 #include <linux/module.h>
18 #include <linux/init.h>
19 
20 #include <linux/delay.h>
21 #include <linux/cpufreq.h>
22 #include <linux/timex.h>
23 #include <linux/io.h>
24 
25 #include <asm/msr.h>
26 
27 #define MMCR_BASE	0xfffef000	/* The default base address */
28 #define OFFS_CPUCTL	0x2   /* CPU Control Register */
29 
30 static __u8 __iomem *cpuctl;
31 
32 #define PFX "sc520_freq: "
33 
34 static struct cpufreq_frequency_table sc520_freq_table[] = {
35 	{0x01,	100000},
36 	{0x02,	133000},
37 	{0,	CPUFREQ_TABLE_END},
38 };
39 
40 static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu)
41 {
42 	u8 clockspeed_reg = *cpuctl;
43 
44 	switch (clockspeed_reg & 0x03) {
45 	default:
46 		printk(KERN_ERR PFX "error: cpuctl register has unexpected "
47 				"value %02x\n", clockspeed_reg);
48 	case 0x01:
49 		return 100000;
50 	case 0x02:
51 		return 133000;
52 	}
53 }
54 
55 static void sc520_freq_set_cpu_state(unsigned int state)
56 {
57 
58 	struct cpufreq_freqs	freqs;
59 	u8 clockspeed_reg;
60 
61 	freqs.old = sc520_freq_get_cpu_frequency(0);
62 	freqs.new = sc520_freq_table[state].frequency;
63 	freqs.cpu = 0; /* AMD Elan is UP */
64 
65 	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
66 
67 	pr_debug("attempting to set frequency to %i kHz\n",
68 			sc520_freq_table[state].frequency);
69 
70 	local_irq_disable();
71 
72 	clockspeed_reg = *cpuctl & ~0x03;
73 	*cpuctl = clockspeed_reg | sc520_freq_table[state].index;
74 
75 	local_irq_enable();
76 
77 	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
78 };
79 
80 static int sc520_freq_verify(struct cpufreq_policy *policy)
81 {
82 	return cpufreq_frequency_table_verify(policy, &sc520_freq_table[0]);
83 }
84 
85 static int sc520_freq_target(struct cpufreq_policy *policy,
86 			    unsigned int target_freq,
87 			    unsigned int relation)
88 {
89 	unsigned int newstate = 0;
90 
91 	if (cpufreq_frequency_table_target(policy, sc520_freq_table,
92 				target_freq, relation, &newstate))
93 		return -EINVAL;
94 
95 	sc520_freq_set_cpu_state(newstate);
96 
97 	return 0;
98 }
99 
100 
101 /*
102  *	Module init and exit code
103  */
104 
105 static int sc520_freq_cpu_init(struct cpufreq_policy *policy)
106 {
107 	struct cpuinfo_x86 *c = &cpu_data(0);
108 	int result;
109 
110 	/* capability check */
111 	if (c->x86_vendor != X86_VENDOR_AMD ||
112 	    c->x86 != 4 || c->x86_model != 9)
113 		return -ENODEV;
114 
115 	/* cpuinfo and default policy values */
116 	policy->cpuinfo.transition_latency = 1000000; /* 1ms */
117 	policy->cur = sc520_freq_get_cpu_frequency(0);
118 
119 	result = cpufreq_frequency_table_cpuinfo(policy, sc520_freq_table);
120 	if (result)
121 		return result;
122 
123 	cpufreq_frequency_table_get_attr(sc520_freq_table, policy->cpu);
124 
125 	return 0;
126 }
127 
128 
129 static int sc520_freq_cpu_exit(struct cpufreq_policy *policy)
130 {
131 	cpufreq_frequency_table_put_attr(policy->cpu);
132 	return 0;
133 }
134 
135 
136 static struct freq_attr *sc520_freq_attr[] = {
137 	&cpufreq_freq_attr_scaling_available_freqs,
138 	NULL,
139 };
140 
141 
142 static struct cpufreq_driver sc520_freq_driver = {
143 	.get	= sc520_freq_get_cpu_frequency,
144 	.verify	= sc520_freq_verify,
145 	.target	= sc520_freq_target,
146 	.init	= sc520_freq_cpu_init,
147 	.exit	= sc520_freq_cpu_exit,
148 	.name	= "sc520_freq",
149 	.owner	= THIS_MODULE,
150 	.attr	= sc520_freq_attr,
151 };
152 
153 
154 static int __init sc520_freq_init(void)
155 {
156 	struct cpuinfo_x86 *c = &cpu_data(0);
157 	int err;
158 
159 	/* Test if we have the right hardware */
160 	if (c->x86_vendor != X86_VENDOR_AMD ||
161 	    c->x86 != 4 || c->x86_model != 9) {
162 		pr_debug("no Elan SC520 processor found!\n");
163 		return -ENODEV;
164 	}
165 	cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1);
166 	if (!cpuctl) {
167 		printk(KERN_ERR "sc520_freq: error: failed to remap memory\n");
168 		return -ENOMEM;
169 	}
170 
171 	err = cpufreq_register_driver(&sc520_freq_driver);
172 	if (err)
173 		iounmap(cpuctl);
174 
175 	return err;
176 }
177 
178 
179 static void __exit sc520_freq_exit(void)
180 {
181 	cpufreq_unregister_driver(&sc520_freq_driver);
182 	iounmap(cpuctl);
183 }
184 
185 
186 MODULE_LICENSE("GPL");
187 MODULE_AUTHOR("Sean Young <sean@mess.org>");
188 MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU");
189 
190 module_init(sc520_freq_init);
191 module_exit(sc520_freq_exit);
192 
193