elanfreq.c (5643B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * elanfreq: cpufreq driver for the AMD ELAN family 4 * 5 * (c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de> 6 * 7 * Parts of this code are (c) Sven Geggus <sven@geggus.net> 8 * 9 * All Rights Reserved. 10 * 11 * 2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel 12 */ 13 14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 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 23#include <asm/cpu_device_id.h> 24#include <asm/msr.h> 25#include <linux/timex.h> 26#include <linux/io.h> 27 28#define REG_CSCIR 0x22 /* Chip Setup and Control Index Register */ 29#define REG_CSCDR 0x23 /* Chip Setup and Control Data Register */ 30 31/* Module parameter */ 32static int max_freq; 33 34struct s_elan_multiplier { 35 int clock; /* frequency in kHz */ 36 int val40h; /* PMU Force Mode register */ 37 int val80h; /* CPU Clock Speed Register */ 38}; 39 40/* 41 * It is important that the frequencies 42 * are listed in ascending order here! 43 */ 44static struct s_elan_multiplier elan_multiplier[] = { 45 {1000, 0x02, 0x18}, 46 {2000, 0x02, 0x10}, 47 {4000, 0x02, 0x08}, 48 {8000, 0x00, 0x00}, 49 {16000, 0x00, 0x02}, 50 {33000, 0x00, 0x04}, 51 {66000, 0x01, 0x04}, 52 {99000, 0x01, 0x05} 53}; 54 55static struct cpufreq_frequency_table elanfreq_table[] = { 56 {0, 0, 1000}, 57 {0, 1, 2000}, 58 {0, 2, 4000}, 59 {0, 3, 8000}, 60 {0, 4, 16000}, 61 {0, 5, 33000}, 62 {0, 6, 66000}, 63 {0, 7, 99000}, 64 {0, 0, CPUFREQ_TABLE_END}, 65}; 66 67 68/** 69 * elanfreq_get_cpu_frequency: determine current cpu speed 70 * 71 * Finds out at which frequency the CPU of the Elan SOC runs 72 * at the moment. Frequencies from 1 to 33 MHz are generated 73 * the normal way, 66 and 99 MHz are called "Hyperspeed Mode" 74 * and have the rest of the chip running with 33 MHz. 75 */ 76 77static unsigned int elanfreq_get_cpu_frequency(unsigned int cpu) 78{ 79 u8 clockspeed_reg; /* Clock Speed Register */ 80 81 local_irq_disable(); 82 outb_p(0x80, REG_CSCIR); 83 clockspeed_reg = inb_p(REG_CSCDR); 84 local_irq_enable(); 85 86 if ((clockspeed_reg & 0xE0) == 0xE0) 87 return 0; 88 89 /* Are we in CPU clock multiplied mode (66/99 MHz)? */ 90 if ((clockspeed_reg & 0xE0) == 0xC0) { 91 if ((clockspeed_reg & 0x01) == 0) 92 return 66000; 93 else 94 return 99000; 95 } 96 97 /* 33 MHz is not 32 MHz... */ 98 if ((clockspeed_reg & 0xE0) == 0xA0) 99 return 33000; 100 101 return (1<<((clockspeed_reg & 0xE0) >> 5)) * 1000; 102} 103 104 105static int elanfreq_target(struct cpufreq_policy *policy, 106 unsigned int state) 107{ 108 /* 109 * Access to the Elan's internal registers is indexed via 110 * 0x22: Chip Setup & Control Register Index Register (CSCI) 111 * 0x23: Chip Setup & Control Register Data Register (CSCD) 112 * 113 */ 114 115 /* 116 * 0x40 is the Power Management Unit's Force Mode Register. 117 * Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency) 118 */ 119 120 local_irq_disable(); 121 outb_p(0x40, REG_CSCIR); /* Disable hyperspeed mode */ 122 outb_p(0x00, REG_CSCDR); 123 local_irq_enable(); /* wait till internal pipelines and */ 124 udelay(1000); /* buffers have cleaned up */ 125 126 local_irq_disable(); 127 128 /* now, set the CPU clock speed register (0x80) */ 129 outb_p(0x80, REG_CSCIR); 130 outb_p(elan_multiplier[state].val80h, REG_CSCDR); 131 132 /* now, the hyperspeed bit in PMU Force Mode Register (0x40) */ 133 outb_p(0x40, REG_CSCIR); 134 outb_p(elan_multiplier[state].val40h, REG_CSCDR); 135 udelay(10000); 136 local_irq_enable(); 137 138 return 0; 139} 140/* 141 * Module init and exit code 142 */ 143 144static int elanfreq_cpu_init(struct cpufreq_policy *policy) 145{ 146 struct cpuinfo_x86 *c = &cpu_data(0); 147 struct cpufreq_frequency_table *pos; 148 149 /* capability check */ 150 if ((c->x86_vendor != X86_VENDOR_AMD) || 151 (c->x86 != 4) || (c->x86_model != 10)) 152 return -ENODEV; 153 154 /* max freq */ 155 if (!max_freq) 156 max_freq = elanfreq_get_cpu_frequency(0); 157 158 /* table init */ 159 cpufreq_for_each_entry(pos, elanfreq_table) 160 if (pos->frequency > max_freq) 161 pos->frequency = CPUFREQ_ENTRY_INVALID; 162 163 policy->freq_table = elanfreq_table; 164 return 0; 165} 166 167 168#ifndef MODULE 169/** 170 * elanfreq_setup - elanfreq command line parameter parsing 171 * 172 * elanfreq command line parameter. Use: 173 * elanfreq=66000 174 * to set the maximum CPU frequency to 66 MHz. Note that in 175 * case you do not give this boot parameter, the maximum 176 * frequency will fall back to _current_ CPU frequency which 177 * might be lower. If you build this as a module, use the 178 * max_freq module parameter instead. 179 */ 180static int __init elanfreq_setup(char *str) 181{ 182 max_freq = simple_strtoul(str, &str, 0); 183 pr_warn("You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please!\n"); 184 return 1; 185} 186__setup("elanfreq=", elanfreq_setup); 187#endif 188 189 190static struct cpufreq_driver elanfreq_driver = { 191 .get = elanfreq_get_cpu_frequency, 192 .flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING, 193 .verify = cpufreq_generic_frequency_table_verify, 194 .target_index = elanfreq_target, 195 .init = elanfreq_cpu_init, 196 .name = "elanfreq", 197 .attr = cpufreq_generic_attr, 198}; 199 200static const struct x86_cpu_id elan_id[] = { 201 X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 10, NULL), 202 {} 203}; 204MODULE_DEVICE_TABLE(x86cpu, elan_id); 205 206static int __init elanfreq_init(void) 207{ 208 if (!x86_match_cpu(elan_id)) 209 return -ENODEV; 210 return cpufreq_register_driver(&elanfreq_driver); 211} 212 213 214static void __exit elanfreq_exit(void) 215{ 216 cpufreq_unregister_driver(&elanfreq_driver); 217} 218 219 220module_param(max_freq, int, 0444); 221 222MODULE_LICENSE("GPL"); 223MODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, " 224 "Sven Geggus <sven@geggus.net>"); 225MODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs"); 226 227module_init(elanfreq_init); 228module_exit(elanfreq_exit);