1/* 2 * elanfreq: cpufreq driver for the AMD ELAN family 3 * 4 * (c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de> 5 * 6 * Parts of this code are (c) Sven Geggus <sven@geggus.net> 7 * 8 * All Rights Reserved. 9 * 10 * This program is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU General Public License 12 * as published by the Free Software Foundation; either version 13 * 2 of the License, or (at your option) any later version. 14 * 15 * 2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel 16 * 17 */ 18 19#include <linux/kernel.h> 20#include <linux/module.h> 21#include <linux/init.h> 22 23#include <linux/delay.h> 24#include <linux/cpufreq.h> 25 26#include <asm/msr.h> 27#include <linux/timex.h> 28#include <linux/io.h> 29 30#define REG_CSCIR 0x22 /* Chip Setup and Control Index Register */ 31#define REG_CSCDR 0x23 /* Chip Setup and Control Data Register */ 32 33/* Module parameter */ 34static int max_freq; 35 36struct s_elan_multiplier { 37 int clock; /* frequency in kHz */ 38 int val40h; /* PMU Force Mode register */ 39 int val80h; /* CPU Clock Speed Register */ 40}; 41 42/* 43 * It is important that the frequencies 44 * are listed in ascending order here! 45 */ 46static struct s_elan_multiplier elan_multiplier[] = { 47 {1000, 0x02, 0x18}, 48 {2000, 0x02, 0x10}, 49 {4000, 0x02, 0x08}, 50 {8000, 0x00, 0x00}, 51 {16000, 0x00, 0x02}, 52 {33000, 0x00, 0x04}, 53 {66000, 0x01, 0x04}, 54 {99000, 0x01, 0x05} 55}; 56 57static struct cpufreq_frequency_table elanfreq_table[] = { 58 {0, 1000}, 59 {1, 2000}, 60 {2, 4000}, 61 {3, 8000}, 62 {4, 16000}, 63 {5, 33000}, 64 {6, 66000}, 65 {7, 99000}, 66 {0, CPUFREQ_TABLE_END}, 67}; 68 69 70/** 71 * elanfreq_get_cpu_frequency: determine current cpu speed 72 * 73 * Finds out at which frequency the CPU of the Elan SOC runs 74 * at the moment. Frequencies from 1 to 33 MHz are generated 75 * the normal way, 66 and 99 MHz are called "Hyperspeed Mode" 76 * and have the rest of the chip running with 33 MHz. 77 */ 78 79static unsigned int elanfreq_get_cpu_frequency(unsigned int cpu) 80{ 81 u8 clockspeed_reg; /* Clock Speed Register */ 82 83 local_irq_disable(); 84 outb_p(0x80, REG_CSCIR); 85 clockspeed_reg = inb_p(REG_CSCDR); 86 local_irq_enable(); 87 88 if ((clockspeed_reg & 0xE0) == 0xE0) 89 return 0; 90 91 /* Are we in CPU clock multiplied mode (66/99 MHz)? */ 92 if ((clockspeed_reg & 0xE0) == 0xC0) { 93 if ((clockspeed_reg & 0x01) == 0) 94 return 66000; 95 else 96 return 99000; 97 } 98 99 /* 33 MHz is not 32 MHz... */ 100 if ((clockspeed_reg & 0xE0) == 0xA0) 101 return 33000; 102 103 return (1<<((clockspeed_reg & 0xE0) >> 5)) * 1000; 104} 105 106 107/** 108 * elanfreq_set_cpu_frequency: Change the CPU core frequency 109 * @cpu: cpu number 110 * @freq: frequency in kHz 111 * 112 * This function takes a frequency value and changes the CPU frequency 113 * according to this. Note that the frequency has to be checked by 114 * elanfreq_validatespeed() for correctness! 115 * 116 * There is no return value. 117 */ 118 119static void elanfreq_set_cpu_state(unsigned int state) 120{ 121 struct cpufreq_freqs freqs; 122 123 freqs.old = elanfreq_get_cpu_frequency(0); 124 freqs.new = elan_multiplier[state].clock; 125 freqs.cpu = 0; /* elanfreq.c is UP only driver */ 126 127 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); 128 129 printk(KERN_INFO "elanfreq: attempting to set frequency to %i kHz\n", 130 elan_multiplier[state].clock); 131 132 133 /* 134 * Access to the Elan's internal registers is indexed via 135 * 0x22: Chip Setup & Control Register Index Register (CSCI) 136 * 0x23: Chip Setup & Control Register Data Register (CSCD) 137 * 138 */ 139 140 /* 141 * 0x40 is the Power Management Unit's Force Mode Register. 142 * Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency) 143 */ 144 145 local_irq_disable(); 146 outb_p(0x40, REG_CSCIR); /* Disable hyperspeed mode */ 147 outb_p(0x00, REG_CSCDR); 148 local_irq_enable(); /* wait till internal pipelines and */ 149 udelay(1000); /* buffers have cleaned up */ 150 151 local_irq_disable(); 152 153 /* now, set the CPU clock speed register (0x80) */ 154 outb_p(0x80, REG_CSCIR); 155 outb_p(elan_multiplier[state].val80h, REG_CSCDR); 156 157 /* now, the hyperspeed bit in PMU Force Mode Register (0x40) */ 158 outb_p(0x40, REG_CSCIR); 159 outb_p(elan_multiplier[state].val40h, REG_CSCDR); 160 udelay(10000); 161 local_irq_enable(); 162 163 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); 164}; 165 166 167/** 168 * elanfreq_validatespeed: test if frequency range is valid 169 * @policy: the policy to validate 170 * 171 * This function checks if a given frequency range in kHz is valid 172 * for the hardware supported by the driver. 173 */ 174 175static int elanfreq_verify(struct cpufreq_policy *policy) 176{ 177 return cpufreq_frequency_table_verify(policy, &elanfreq_table[0]); 178} 179 180static int elanfreq_target(struct cpufreq_policy *policy, 181 unsigned int target_freq, 182 unsigned int relation) 183{ 184 unsigned int newstate = 0; 185 186 if (cpufreq_frequency_table_target(policy, &elanfreq_table[0], 187 target_freq, relation, &newstate)) 188 return -EINVAL; 189 190 elanfreq_set_cpu_state(newstate); 191 192 return 0; 193} 194 195 196/* 197 * Module init and exit code 198 */ 199 200static int elanfreq_cpu_init(struct cpufreq_policy *policy) 201{ 202 struct cpuinfo_x86 *c = &cpu_data(0); 203 unsigned int i; 204 int result; 205 206 /* capability check */ 207 if ((c->x86_vendor != X86_VENDOR_AMD) || 208 (c->x86 != 4) || (c->x86_model != 10)) 209 return -ENODEV; 210 211 /* max freq */ 212 if (!max_freq) 213 max_freq = elanfreq_get_cpu_frequency(0); 214 215 /* table init */ 216 for (i = 0; (elanfreq_table[i].frequency != CPUFREQ_TABLE_END); i++) { 217 if (elanfreq_table[i].frequency > max_freq) 218 elanfreq_table[i].frequency = CPUFREQ_ENTRY_INVALID; 219 } 220 221 /* cpuinfo and default policy values */ 222 policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; 223 policy->cur = elanfreq_get_cpu_frequency(0); 224 225 result = cpufreq_frequency_table_cpuinfo(policy, elanfreq_table); 226 if (result) 227 return result; 228 229 cpufreq_frequency_table_get_attr(elanfreq_table, policy->cpu); 230 return 0; 231} 232 233 234static int elanfreq_cpu_exit(struct cpufreq_policy *policy) 235{ 236 cpufreq_frequency_table_put_attr(policy->cpu); 237 return 0; 238} 239 240 241#ifndef MODULE 242/** 243 * elanfreq_setup - elanfreq command line parameter parsing 244 * 245 * elanfreq command line parameter. Use: 246 * elanfreq=66000 247 * to set the maximum CPU frequency to 66 MHz. Note that in 248 * case you do not give this boot parameter, the maximum 249 * frequency will fall back to _current_ CPU frequency which 250 * might be lower. If you build this as a module, use the 251 * max_freq module parameter instead. 252 */ 253static int __init elanfreq_setup(char *str) 254{ 255 max_freq = simple_strtoul(str, &str, 0); 256 printk(KERN_WARNING "You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please!\n"); 257 return 1; 258} 259__setup("elanfreq=", elanfreq_setup); 260#endif 261 262 263static struct freq_attr *elanfreq_attr[] = { 264 &cpufreq_freq_attr_scaling_available_freqs, 265 NULL, 266}; 267 268 269static struct cpufreq_driver elanfreq_driver = { 270 .get = elanfreq_get_cpu_frequency, 271 .verify = elanfreq_verify, 272 .target = elanfreq_target, 273 .init = elanfreq_cpu_init, 274 .exit = elanfreq_cpu_exit, 275 .name = "elanfreq", 276 .owner = THIS_MODULE, 277 .attr = elanfreq_attr, 278}; 279 280 281static int __init elanfreq_init(void) 282{ 283 struct cpuinfo_x86 *c = &cpu_data(0); 284 285 /* Test if we have the right hardware */ 286 if ((c->x86_vendor != X86_VENDOR_AMD) || 287 (c->x86 != 4) || (c->x86_model != 10)) { 288 printk(KERN_INFO "elanfreq: error: no Elan processor found!\n"); 289 return -ENODEV; 290 } 291 return cpufreq_register_driver(&elanfreq_driver); 292} 293 294 295static void __exit elanfreq_exit(void) 296{ 297 cpufreq_unregister_driver(&elanfreq_driver); 298} 299 300 301module_param(max_freq, int, 0444); 302 303MODULE_LICENSE("GPL"); 304MODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, " 305 "Sven Geggus <sven@geggus.net>"); 306MODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs"); 307 308module_init(elanfreq_init); 309module_exit(elanfreq_exit); 310