229 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * elanfreq: cpufreq driver for the AMD ELAN family4 *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 Schwebel12 */13 14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt15 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 frequencies42 * 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 speed70 *71 * Finds out at which frequency the CPU of the Elan SOC runs72 * at the moment. Frequencies from 1 to 33 MHz are generated73 * 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 else94 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 via110 * 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 code142 */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 MODULE169/**170 * elanfreq_setup - elanfreq command line parameter parsing171 *172 * elanfreq command line parameter. Use:173 * elanfreq=66000174 * to set the maximum CPU frequency to 66 MHz. Note that in175 * case you do not give this boot parameter, the maximum176 * frequency will fall back to _current_ CPU frequency which177 * might be lower. If you build this as a module, use the178 * 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#endif188 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);229