| lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Intel SpeedStep SMI driver. | 
|  | 3 | * | 
|  | 4 | * (C) 2003  Hiroshi Miura <miura@da-cha.org> | 
|  | 5 | * | 
|  | 6 | *  Licensed under the terms of the GNU GPL License version 2. | 
|  | 7 | * | 
|  | 8 | */ | 
|  | 9 |  | 
|  | 10 |  | 
|  | 11 | /********************************************************************* | 
|  | 12 | *                        SPEEDSTEP - DEFINITIONS                    * | 
|  | 13 | *********************************************************************/ | 
|  | 14 |  | 
|  | 15 | #include <linux/kernel.h> | 
|  | 16 | #include <linux/module.h> | 
|  | 17 | #include <linux/moduleparam.h> | 
|  | 18 | #include <linux/init.h> | 
|  | 19 | #include <linux/cpufreq.h> | 
|  | 20 | #include <linux/delay.h> | 
|  | 21 | #include <linux/io.h> | 
|  | 22 | #include <asm/ist.h> | 
|  | 23 | #include <asm/cpu_device_id.h> | 
|  | 24 |  | 
|  | 25 | #include "speedstep-lib.h" | 
|  | 26 |  | 
|  | 27 | /* speedstep system management interface port/command. | 
|  | 28 | * | 
|  | 29 | * These parameters are got from IST-SMI BIOS call. | 
|  | 30 | * If user gives it, these are used. | 
|  | 31 | * | 
|  | 32 | */ | 
|  | 33 | static int smi_port; | 
|  | 34 | static int smi_cmd; | 
|  | 35 | static unsigned int smi_sig; | 
|  | 36 |  | 
|  | 37 | /* info about the processor */ | 
|  | 38 | static enum speedstep_processor speedstep_processor; | 
|  | 39 |  | 
|  | 40 | /* | 
|  | 41 | * There are only two frequency states for each processor. Values | 
|  | 42 | * are in kHz for the time being. | 
|  | 43 | */ | 
|  | 44 | static struct cpufreq_frequency_table speedstep_freqs[] = { | 
|  | 45 | {SPEEDSTEP_HIGH,	0}, | 
|  | 46 | {SPEEDSTEP_LOW,		0}, | 
|  | 47 | {0,			CPUFREQ_TABLE_END}, | 
|  | 48 | }; | 
|  | 49 |  | 
|  | 50 | #define GET_SPEEDSTEP_OWNER 0 | 
|  | 51 | #define GET_SPEEDSTEP_STATE 1 | 
|  | 52 | #define SET_SPEEDSTEP_STATE 2 | 
|  | 53 | #define GET_SPEEDSTEP_FREQS 4 | 
|  | 54 |  | 
|  | 55 | /* how often shall the SMI call be tried if it failed, e.g. because | 
|  | 56 | * of DMA activity going on? */ | 
|  | 57 | #define SMI_TRIES 5 | 
|  | 58 |  | 
|  | 59 | /** | 
|  | 60 | * speedstep_smi_ownership | 
|  | 61 | */ | 
|  | 62 | static int speedstep_smi_ownership(void) | 
|  | 63 | { | 
|  | 64 | u32 command, result, magic, dummy; | 
|  | 65 | u32 function = GET_SPEEDSTEP_OWNER; | 
|  | 66 | unsigned char magic_data[] = "Copyright (c) 1999 Intel Corporation"; | 
|  | 67 |  | 
|  | 68 | command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff); | 
|  | 69 | magic = virt_to_phys(magic_data); | 
|  | 70 |  | 
|  | 71 | pr_debug("trying to obtain ownership with command %x at port %x\n", | 
|  | 72 | command, smi_port); | 
|  | 73 |  | 
|  | 74 | __asm__ __volatile__( | 
|  | 75 | "push %%ebp\n" | 
|  | 76 | "out %%al, (%%dx)\n" | 
|  | 77 | "pop %%ebp\n" | 
|  | 78 | : "=D" (result), | 
|  | 79 | "=a" (dummy), "=b" (dummy), "=c" (dummy), "=d" (dummy), | 
|  | 80 | "=S" (dummy) | 
|  | 81 | : "a" (command), "b" (function), "c" (0), "d" (smi_port), | 
|  | 82 | "D" (0), "S" (magic) | 
|  | 83 | : "memory" | 
|  | 84 | ); | 
|  | 85 |  | 
|  | 86 | pr_debug("result is %x\n", result); | 
|  | 87 |  | 
|  | 88 | return result; | 
|  | 89 | } | 
|  | 90 |  | 
|  | 91 | /** | 
|  | 92 | * speedstep_smi_get_freqs - get SpeedStep preferred & current freq. | 
|  | 93 | * @low: the low frequency value is placed here | 
|  | 94 | * @high: the high frequency value is placed here | 
|  | 95 | * | 
|  | 96 | * Only available on later SpeedStep-enabled systems, returns false results or | 
|  | 97 | * even hangs [cf. bugme.osdl.org # 1422] on earlier systems. Empirical testing | 
|  | 98 | * shows that the latter occurs if !(ist_info.event & 0xFFFF). | 
|  | 99 | */ | 
|  | 100 | static int speedstep_smi_get_freqs(unsigned int *low, unsigned int *high) | 
|  | 101 | { | 
|  | 102 | u32 command, result = 0, edi, high_mhz, low_mhz, dummy; | 
|  | 103 | u32 state = 0; | 
|  | 104 | u32 function = GET_SPEEDSTEP_FREQS; | 
|  | 105 |  | 
|  | 106 | if (!(ist_info.event & 0xFFFF)) { | 
|  | 107 | pr_debug("bug #1422 -- can't read freqs from BIOS\n"); | 
|  | 108 | return -ENODEV; | 
|  | 109 | } | 
|  | 110 |  | 
|  | 111 | command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff); | 
|  | 112 |  | 
|  | 113 | pr_debug("trying to determine frequencies with command %x at port %x\n", | 
|  | 114 | command, smi_port); | 
|  | 115 |  | 
|  | 116 | __asm__ __volatile__( | 
|  | 117 | "push %%ebp\n" | 
|  | 118 | "out %%al, (%%dx)\n" | 
|  | 119 | "pop %%ebp" | 
|  | 120 | : "=a" (result), | 
|  | 121 | "=b" (high_mhz), | 
|  | 122 | "=c" (low_mhz), | 
|  | 123 | "=d" (state), "=D" (edi), "=S" (dummy) | 
|  | 124 | : "a" (command), | 
|  | 125 | "b" (function), | 
|  | 126 | "c" (state), | 
|  | 127 | "d" (smi_port), "S" (0), "D" (0) | 
|  | 128 | ); | 
|  | 129 |  | 
|  | 130 | pr_debug("result %x, low_freq %u, high_freq %u\n", | 
|  | 131 | result, low_mhz, high_mhz); | 
|  | 132 |  | 
|  | 133 | /* abort if results are obviously incorrect... */ | 
|  | 134 | if ((high_mhz + low_mhz) < 600) | 
|  | 135 | return -EINVAL; | 
|  | 136 |  | 
|  | 137 | *high = high_mhz * 1000; | 
|  | 138 | *low  = low_mhz  * 1000; | 
|  | 139 |  | 
|  | 140 | return result; | 
|  | 141 | } | 
|  | 142 |  | 
|  | 143 | /** | 
|  | 144 | * speedstep_get_state - set the SpeedStep state | 
|  | 145 | * @state: processor frequency state (SPEEDSTEP_LOW or SPEEDSTEP_HIGH) | 
|  | 146 | * | 
|  | 147 | */ | 
|  | 148 | static int speedstep_get_state(void) | 
|  | 149 | { | 
|  | 150 | u32 function = GET_SPEEDSTEP_STATE; | 
|  | 151 | u32 result, state, edi, command, dummy; | 
|  | 152 |  | 
|  | 153 | command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff); | 
|  | 154 |  | 
|  | 155 | pr_debug("trying to determine current setting with command %x " | 
|  | 156 | "at port %x\n", command, smi_port); | 
|  | 157 |  | 
|  | 158 | __asm__ __volatile__( | 
|  | 159 | "push %%ebp\n" | 
|  | 160 | "out %%al, (%%dx)\n" | 
|  | 161 | "pop %%ebp\n" | 
|  | 162 | : "=a" (result), | 
|  | 163 | "=b" (state), "=D" (edi), | 
|  | 164 | "=c" (dummy), "=d" (dummy), "=S" (dummy) | 
|  | 165 | : "a" (command), "b" (function), "c" (0), | 
|  | 166 | "d" (smi_port), "S" (0), "D" (0) | 
|  | 167 | ); | 
|  | 168 |  | 
|  | 169 | pr_debug("state is %x, result is %x\n", state, result); | 
|  | 170 |  | 
|  | 171 | return state & 1; | 
|  | 172 | } | 
|  | 173 |  | 
|  | 174 |  | 
|  | 175 | /** | 
|  | 176 | * speedstep_set_state - set the SpeedStep state | 
|  | 177 | * @state: new processor frequency state (SPEEDSTEP_LOW or SPEEDSTEP_HIGH) | 
|  | 178 | * | 
|  | 179 | */ | 
|  | 180 | static void speedstep_set_state(unsigned int state) | 
|  | 181 | { | 
|  | 182 | unsigned int result = 0, command, new_state, dummy; | 
|  | 183 | unsigned long flags; | 
|  | 184 | unsigned int function = SET_SPEEDSTEP_STATE; | 
|  | 185 | unsigned int retry = 0; | 
|  | 186 |  | 
|  | 187 | if (state > 0x1) | 
|  | 188 | return; | 
|  | 189 |  | 
|  | 190 | /* Disable IRQs */ | 
|  | 191 | preempt_disable(); | 
|  | 192 | local_irq_save(flags); | 
|  | 193 |  | 
|  | 194 | command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff); | 
|  | 195 |  | 
|  | 196 | pr_debug("trying to set frequency to state %u " | 
|  | 197 | "with command %x at port %x\n", | 
|  | 198 | state, command, smi_port); | 
|  | 199 |  | 
|  | 200 | do { | 
|  | 201 | if (retry) { | 
|  | 202 | /* | 
|  | 203 | * We need to enable interrupts, otherwise the blockage | 
|  | 204 | * won't resolve. | 
|  | 205 | * | 
|  | 206 | * We disable preemption so that other processes don't | 
|  | 207 | * run. If other processes were running, they could | 
|  | 208 | * submit more DMA requests, making the blockage worse. | 
|  | 209 | */ | 
|  | 210 | pr_debug("retry %u, previous result %u, waiting...\n", | 
|  | 211 | retry, result); | 
|  | 212 | local_irq_enable(); | 
|  | 213 | mdelay(retry * 50); | 
|  | 214 | local_irq_disable(); | 
|  | 215 | } | 
|  | 216 | retry++; | 
|  | 217 | __asm__ __volatile__( | 
|  | 218 | "push %%ebp\n" | 
|  | 219 | "out %%al, (%%dx)\n" | 
|  | 220 | "pop %%ebp" | 
|  | 221 | : "=b" (new_state), "=D" (result), | 
|  | 222 | "=c" (dummy), "=a" (dummy), | 
|  | 223 | "=d" (dummy), "=S" (dummy) | 
|  | 224 | : "a" (command), "b" (function), "c" (state), | 
|  | 225 | "d" (smi_port), "S" (0), "D" (0) | 
|  | 226 | ); | 
|  | 227 | } while ((new_state != state) && (retry <= SMI_TRIES)); | 
|  | 228 |  | 
|  | 229 | /* enable IRQs */ | 
|  | 230 | local_irq_restore(flags); | 
|  | 231 | preempt_enable(); | 
|  | 232 |  | 
|  | 233 | if (new_state == state) | 
|  | 234 | pr_debug("change to %u MHz succeeded after %u tries " | 
|  | 235 | "with result %u\n", | 
|  | 236 | (speedstep_freqs[new_state].frequency / 1000), | 
|  | 237 | retry, result); | 
|  | 238 | else | 
|  | 239 | printk(KERN_ERR "cpufreq: change to state %u " | 
|  | 240 | "failed with new_state %u and result %u\n", | 
|  | 241 | state, new_state, result); | 
|  | 242 |  | 
|  | 243 | return; | 
|  | 244 | } | 
|  | 245 |  | 
|  | 246 |  | 
|  | 247 | /** | 
|  | 248 | * speedstep_target - set a new CPUFreq policy | 
|  | 249 | * @policy: new policy | 
|  | 250 | * @target_freq: new freq | 
|  | 251 | * @relation: | 
|  | 252 | * | 
|  | 253 | * Sets a new CPUFreq policy/freq. | 
|  | 254 | */ | 
|  | 255 | static int speedstep_target(struct cpufreq_policy *policy, | 
|  | 256 | unsigned int target_freq, unsigned int relation) | 
|  | 257 | { | 
|  | 258 | unsigned int newstate = 0; | 
|  | 259 | struct cpufreq_freqs freqs; | 
|  | 260 |  | 
|  | 261 | if (cpufreq_frequency_table_target(policy, &speedstep_freqs[0], | 
|  | 262 | target_freq, relation, &newstate)) | 
|  | 263 | return -EINVAL; | 
|  | 264 |  | 
|  | 265 | freqs.old = speedstep_freqs[speedstep_get_state()].frequency; | 
|  | 266 | freqs.new = speedstep_freqs[newstate].frequency; | 
|  | 267 | freqs.cpu = 0; /* speedstep.c is UP only driver */ | 
|  | 268 |  | 
|  | 269 | if (freqs.old == freqs.new) | 
|  | 270 | return 0; | 
|  | 271 |  | 
|  | 272 | cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); | 
|  | 273 | speedstep_set_state(newstate); | 
|  | 274 | cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); | 
|  | 275 |  | 
|  | 276 | return 0; | 
|  | 277 | } | 
|  | 278 |  | 
|  | 279 |  | 
|  | 280 | /** | 
|  | 281 | * speedstep_verify - verifies a new CPUFreq policy | 
|  | 282 | * @policy: new policy | 
|  | 283 | * | 
|  | 284 | * Limit must be within speedstep_low_freq and speedstep_high_freq, with | 
|  | 285 | * at least one border included. | 
|  | 286 | */ | 
|  | 287 | static int speedstep_verify(struct cpufreq_policy *policy) | 
|  | 288 | { | 
|  | 289 | return cpufreq_frequency_table_verify(policy, &speedstep_freqs[0]); | 
|  | 290 | } | 
|  | 291 |  | 
|  | 292 |  | 
|  | 293 | static int speedstep_cpu_init(struct cpufreq_policy *policy) | 
|  | 294 | { | 
|  | 295 | int result; | 
|  | 296 | unsigned int speed, state; | 
|  | 297 | unsigned int *low, *high; | 
|  | 298 |  | 
|  | 299 | /* capability check */ | 
|  | 300 | if (policy->cpu != 0) | 
|  | 301 | return -ENODEV; | 
|  | 302 |  | 
|  | 303 | result = speedstep_smi_ownership(); | 
|  | 304 | if (result) { | 
|  | 305 | pr_debug("fails in acquiring ownership of a SMI interface.\n"); | 
|  | 306 | return -EINVAL; | 
|  | 307 | } | 
|  | 308 |  | 
|  | 309 | /* detect low and high frequency */ | 
|  | 310 | low = &speedstep_freqs[SPEEDSTEP_LOW].frequency; | 
|  | 311 | high = &speedstep_freqs[SPEEDSTEP_HIGH].frequency; | 
|  | 312 |  | 
|  | 313 | result = speedstep_smi_get_freqs(low, high); | 
|  | 314 | if (result) { | 
|  | 315 | /* fall back to speedstep_lib.c dection mechanism: | 
|  | 316 | * try both states out */ | 
|  | 317 | pr_debug("could not detect low and high frequencies " | 
|  | 318 | "by SMI call.\n"); | 
|  | 319 | result = speedstep_get_freqs(speedstep_processor, | 
|  | 320 | low, high, | 
|  | 321 | NULL, | 
|  | 322 | &speedstep_set_state); | 
|  | 323 |  | 
|  | 324 | if (result) { | 
|  | 325 | pr_debug("could not detect two different speeds" | 
|  | 326 | " -- aborting.\n"); | 
|  | 327 | return result; | 
|  | 328 | } else | 
|  | 329 | pr_debug("workaround worked.\n"); | 
|  | 330 | } | 
|  | 331 |  | 
|  | 332 | /* get current speed setting */ | 
|  | 333 | state = speedstep_get_state(); | 
|  | 334 | speed = speedstep_freqs[state].frequency; | 
|  | 335 |  | 
|  | 336 | pr_debug("currently at %s speed setting - %i MHz\n", | 
|  | 337 | (speed == speedstep_freqs[SPEEDSTEP_LOW].frequency) | 
|  | 338 | ? "low" : "high", | 
|  | 339 | (speed / 1000)); | 
|  | 340 |  | 
|  | 341 | /* cpuinfo and default policy values */ | 
|  | 342 | policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; | 
|  | 343 | policy->cur = speed; | 
|  | 344 |  | 
|  | 345 | result = cpufreq_frequency_table_cpuinfo(policy, speedstep_freqs); | 
|  | 346 | if (result) | 
|  | 347 | return result; | 
|  | 348 |  | 
|  | 349 | cpufreq_frequency_table_get_attr(speedstep_freqs, policy->cpu); | 
|  | 350 |  | 
|  | 351 | return 0; | 
|  | 352 | } | 
|  | 353 |  | 
|  | 354 | static int speedstep_cpu_exit(struct cpufreq_policy *policy) | 
|  | 355 | { | 
|  | 356 | cpufreq_frequency_table_put_attr(policy->cpu); | 
|  | 357 | return 0; | 
|  | 358 | } | 
|  | 359 |  | 
|  | 360 | static unsigned int speedstep_get(unsigned int cpu) | 
|  | 361 | { | 
|  | 362 | if (cpu) | 
|  | 363 | return -ENODEV; | 
|  | 364 | return speedstep_get_frequency(speedstep_processor); | 
|  | 365 | } | 
|  | 366 |  | 
|  | 367 |  | 
|  | 368 | static int speedstep_resume(struct cpufreq_policy *policy) | 
|  | 369 | { | 
|  | 370 | int result = speedstep_smi_ownership(); | 
|  | 371 |  | 
|  | 372 | if (result) | 
|  | 373 | pr_debug("fails in re-acquiring ownership of a SMI interface.\n"); | 
|  | 374 |  | 
|  | 375 | return result; | 
|  | 376 | } | 
|  | 377 |  | 
|  | 378 | static struct freq_attr *speedstep_attr[] = { | 
|  | 379 | &cpufreq_freq_attr_scaling_available_freqs, | 
|  | 380 | NULL, | 
|  | 381 | }; | 
|  | 382 |  | 
|  | 383 | static struct cpufreq_driver speedstep_driver = { | 
|  | 384 | .name		= "speedstep-smi", | 
|  | 385 | .verify		= speedstep_verify, | 
|  | 386 | .target		= speedstep_target, | 
|  | 387 | .init		= speedstep_cpu_init, | 
|  | 388 | .exit		= speedstep_cpu_exit, | 
|  | 389 | .get		= speedstep_get, | 
|  | 390 | .resume		= speedstep_resume, | 
|  | 391 | .owner		= THIS_MODULE, | 
|  | 392 | .attr		= speedstep_attr, | 
|  | 393 | }; | 
|  | 394 |  | 
|  | 395 | static const struct x86_cpu_id ss_smi_ids[] = { | 
|  | 396 | { X86_VENDOR_INTEL, 6, 0xb, }, | 
|  | 397 | { X86_VENDOR_INTEL, 6, 0x8, }, | 
|  | 398 | { X86_VENDOR_INTEL, 15, 2 }, | 
|  | 399 | {} | 
|  | 400 | }; | 
|  | 401 | #if 0 | 
|  | 402 | /* Not auto loaded currently */ | 
|  | 403 | MODULE_DEVICE_TABLE(x86cpu, ss_smi_ids); | 
|  | 404 | #endif | 
|  | 405 |  | 
|  | 406 | /** | 
|  | 407 | * speedstep_init - initializes the SpeedStep CPUFreq driver | 
|  | 408 | * | 
|  | 409 | *   Initializes the SpeedStep support. Returns -ENODEV on unsupported | 
|  | 410 | * BIOS, -EINVAL on problems during initiatization, and zero on | 
|  | 411 | * success. | 
|  | 412 | */ | 
|  | 413 | static int __init speedstep_init(void) | 
|  | 414 | { | 
|  | 415 | if (!x86_match_cpu(ss_smi_ids)) | 
|  | 416 | return -ENODEV; | 
|  | 417 |  | 
|  | 418 | speedstep_processor = speedstep_detect_processor(); | 
|  | 419 |  | 
|  | 420 | switch (speedstep_processor) { | 
|  | 421 | case SPEEDSTEP_CPU_PIII_T: | 
|  | 422 | case SPEEDSTEP_CPU_PIII_C: | 
|  | 423 | case SPEEDSTEP_CPU_PIII_C_EARLY: | 
|  | 424 | break; | 
|  | 425 | default: | 
|  | 426 | speedstep_processor = 0; | 
|  | 427 | } | 
|  | 428 |  | 
|  | 429 | if (!speedstep_processor) { | 
|  | 430 | pr_debug("No supported Intel CPU detected.\n"); | 
|  | 431 | return -ENODEV; | 
|  | 432 | } | 
|  | 433 |  | 
|  | 434 | pr_debug("signature:0x%.8ulx, command:0x%.8ulx, " | 
|  | 435 | "event:0x%.8ulx, perf_level:0x%.8ulx.\n", | 
|  | 436 | ist_info.signature, ist_info.command, | 
|  | 437 | ist_info.event, ist_info.perf_level); | 
|  | 438 |  | 
|  | 439 | /* Error if no IST-SMI BIOS or no PARM | 
|  | 440 | sig= 'ISGE' aka 'Intel Speedstep Gate E' */ | 
|  | 441 | if ((ist_info.signature !=  0x47534943) && ( | 
|  | 442 | (smi_port == 0) || (smi_cmd == 0))) | 
|  | 443 | return -ENODEV; | 
|  | 444 |  | 
|  | 445 | if (smi_sig == 1) | 
|  | 446 | smi_sig = 0x47534943; | 
|  | 447 | else | 
|  | 448 | smi_sig = ist_info.signature; | 
|  | 449 |  | 
|  | 450 | /* setup smi_port from MODLULE_PARM or BIOS */ | 
|  | 451 | if ((smi_port > 0xff) || (smi_port < 0)) | 
|  | 452 | return -EINVAL; | 
|  | 453 | else if (smi_port == 0) | 
|  | 454 | smi_port = ist_info.command & 0xff; | 
|  | 455 |  | 
|  | 456 | if ((smi_cmd > 0xff) || (smi_cmd < 0)) | 
|  | 457 | return -EINVAL; | 
|  | 458 | else if (smi_cmd == 0) | 
|  | 459 | smi_cmd = (ist_info.command >> 16) & 0xff; | 
|  | 460 |  | 
|  | 461 | return cpufreq_register_driver(&speedstep_driver); | 
|  | 462 | } | 
|  | 463 |  | 
|  | 464 |  | 
|  | 465 | /** | 
|  | 466 | * speedstep_exit - unregisters SpeedStep support | 
|  | 467 | * | 
|  | 468 | *   Unregisters SpeedStep support. | 
|  | 469 | */ | 
|  | 470 | static void __exit speedstep_exit(void) | 
|  | 471 | { | 
|  | 472 | cpufreq_unregister_driver(&speedstep_driver); | 
|  | 473 | } | 
|  | 474 |  | 
|  | 475 | module_param(smi_port, int, 0444); | 
|  | 476 | module_param(smi_cmd,  int, 0444); | 
|  | 477 | module_param(smi_sig, uint, 0444); | 
|  | 478 |  | 
|  | 479 | MODULE_PARM_DESC(smi_port, "Override the BIOS-given IST port with this value " | 
|  | 480 | "-- Intel's default setting is 0xb2"); | 
|  | 481 | MODULE_PARM_DESC(smi_cmd, "Override the BIOS-given IST command with this value " | 
|  | 482 | "-- Intel's default setting is 0x82"); | 
|  | 483 | MODULE_PARM_DESC(smi_sig, "Set to 1 to fake the IST signature when using the " | 
|  | 484 | "SMI interface."); | 
|  | 485 |  | 
|  | 486 | MODULE_AUTHOR("Hiroshi Miura"); | 
|  | 487 | MODULE_DESCRIPTION("Speedstep driver for IST applet SMI interface."); | 
|  | 488 | MODULE_LICENSE("GPL"); | 
|  | 489 |  | 
|  | 490 | module_init(speedstep_init); | 
|  | 491 | module_exit(speedstep_exit); |