18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * elanfreq: cpufreq driver for the AMD ELAN family 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Parts of this code are (c) Sven Geggus <sven@geggus.net> 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * All Rights Reserved. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * 2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/init.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/delay.h> 218c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h> 248c2ecf20Sopenharmony_ci#include <asm/msr.h> 258c2ecf20Sopenharmony_ci#include <linux/timex.h> 268c2ecf20Sopenharmony_ci#include <linux/io.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define REG_CSCIR 0x22 /* Chip Setup and Control Index Register */ 298c2ecf20Sopenharmony_ci#define REG_CSCDR 0x23 /* Chip Setup and Control Data Register */ 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/* Module parameter */ 328c2ecf20Sopenharmony_cistatic int max_freq; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct s_elan_multiplier { 358c2ecf20Sopenharmony_ci int clock; /* frequency in kHz */ 368c2ecf20Sopenharmony_ci int val40h; /* PMU Force Mode register */ 378c2ecf20Sopenharmony_ci int val80h; /* CPU Clock Speed Register */ 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* 418c2ecf20Sopenharmony_ci * It is important that the frequencies 428c2ecf20Sopenharmony_ci * are listed in ascending order here! 438c2ecf20Sopenharmony_ci */ 448c2ecf20Sopenharmony_cistatic struct s_elan_multiplier elan_multiplier[] = { 458c2ecf20Sopenharmony_ci {1000, 0x02, 0x18}, 468c2ecf20Sopenharmony_ci {2000, 0x02, 0x10}, 478c2ecf20Sopenharmony_ci {4000, 0x02, 0x08}, 488c2ecf20Sopenharmony_ci {8000, 0x00, 0x00}, 498c2ecf20Sopenharmony_ci {16000, 0x00, 0x02}, 508c2ecf20Sopenharmony_ci {33000, 0x00, 0x04}, 518c2ecf20Sopenharmony_ci {66000, 0x01, 0x04}, 528c2ecf20Sopenharmony_ci {99000, 0x01, 0x05} 538c2ecf20Sopenharmony_ci}; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic struct cpufreq_frequency_table elanfreq_table[] = { 568c2ecf20Sopenharmony_ci {0, 0, 1000}, 578c2ecf20Sopenharmony_ci {0, 1, 2000}, 588c2ecf20Sopenharmony_ci {0, 2, 4000}, 598c2ecf20Sopenharmony_ci {0, 3, 8000}, 608c2ecf20Sopenharmony_ci {0, 4, 16000}, 618c2ecf20Sopenharmony_ci {0, 5, 33000}, 628c2ecf20Sopenharmony_ci {0, 6, 66000}, 638c2ecf20Sopenharmony_ci {0, 7, 99000}, 648c2ecf20Sopenharmony_ci {0, 0, CPUFREQ_TABLE_END}, 658c2ecf20Sopenharmony_ci}; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/** 698c2ecf20Sopenharmony_ci * elanfreq_get_cpu_frequency: determine current cpu speed 708c2ecf20Sopenharmony_ci * 718c2ecf20Sopenharmony_ci * Finds out at which frequency the CPU of the Elan SOC runs 728c2ecf20Sopenharmony_ci * at the moment. Frequencies from 1 to 33 MHz are generated 738c2ecf20Sopenharmony_ci * the normal way, 66 and 99 MHz are called "Hyperspeed Mode" 748c2ecf20Sopenharmony_ci * and have the rest of the chip running with 33 MHz. 758c2ecf20Sopenharmony_ci */ 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic unsigned int elanfreq_get_cpu_frequency(unsigned int cpu) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci u8 clockspeed_reg; /* Clock Speed Register */ 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci local_irq_disable(); 828c2ecf20Sopenharmony_ci outb_p(0x80, REG_CSCIR); 838c2ecf20Sopenharmony_ci clockspeed_reg = inb_p(REG_CSCDR); 848c2ecf20Sopenharmony_ci local_irq_enable(); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if ((clockspeed_reg & 0xE0) == 0xE0) 878c2ecf20Sopenharmony_ci return 0; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* Are we in CPU clock multiplied mode (66/99 MHz)? */ 908c2ecf20Sopenharmony_ci if ((clockspeed_reg & 0xE0) == 0xC0) { 918c2ecf20Sopenharmony_ci if ((clockspeed_reg & 0x01) == 0) 928c2ecf20Sopenharmony_ci return 66000; 938c2ecf20Sopenharmony_ci else 948c2ecf20Sopenharmony_ci return 99000; 958c2ecf20Sopenharmony_ci } 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci /* 33 MHz is not 32 MHz... */ 988c2ecf20Sopenharmony_ci if ((clockspeed_reg & 0xE0) == 0xA0) 998c2ecf20Sopenharmony_ci return 33000; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return (1<<((clockspeed_reg & 0xE0) >> 5)) * 1000; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic int elanfreq_target(struct cpufreq_policy *policy, 1068c2ecf20Sopenharmony_ci unsigned int state) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci /* 1098c2ecf20Sopenharmony_ci * Access to the Elan's internal registers is indexed via 1108c2ecf20Sopenharmony_ci * 0x22: Chip Setup & Control Register Index Register (CSCI) 1118c2ecf20Sopenharmony_ci * 0x23: Chip Setup & Control Register Data Register (CSCD) 1128c2ecf20Sopenharmony_ci * 1138c2ecf20Sopenharmony_ci */ 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci /* 1168c2ecf20Sopenharmony_ci * 0x40 is the Power Management Unit's Force Mode Register. 1178c2ecf20Sopenharmony_ci * Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency) 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci local_irq_disable(); 1218c2ecf20Sopenharmony_ci outb_p(0x40, REG_CSCIR); /* Disable hyperspeed mode */ 1228c2ecf20Sopenharmony_ci outb_p(0x00, REG_CSCDR); 1238c2ecf20Sopenharmony_ci local_irq_enable(); /* wait till internal pipelines and */ 1248c2ecf20Sopenharmony_ci udelay(1000); /* buffers have cleaned up */ 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci local_irq_disable(); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci /* now, set the CPU clock speed register (0x80) */ 1298c2ecf20Sopenharmony_ci outb_p(0x80, REG_CSCIR); 1308c2ecf20Sopenharmony_ci outb_p(elan_multiplier[state].val80h, REG_CSCDR); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci /* now, the hyperspeed bit in PMU Force Mode Register (0x40) */ 1338c2ecf20Sopenharmony_ci outb_p(0x40, REG_CSCIR); 1348c2ecf20Sopenharmony_ci outb_p(elan_multiplier[state].val40h, REG_CSCDR); 1358c2ecf20Sopenharmony_ci udelay(10000); 1368c2ecf20Sopenharmony_ci local_irq_enable(); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci/* 1418c2ecf20Sopenharmony_ci * Module init and exit code 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int elanfreq_cpu_init(struct cpufreq_policy *policy) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct cpuinfo_x86 *c = &cpu_data(0); 1478c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *pos; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci /* capability check */ 1508c2ecf20Sopenharmony_ci if ((c->x86_vendor != X86_VENDOR_AMD) || 1518c2ecf20Sopenharmony_ci (c->x86 != 4) || (c->x86_model != 10)) 1528c2ecf20Sopenharmony_ci return -ENODEV; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* max freq */ 1558c2ecf20Sopenharmony_ci if (!max_freq) 1568c2ecf20Sopenharmony_ci max_freq = elanfreq_get_cpu_frequency(0); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* table init */ 1598c2ecf20Sopenharmony_ci cpufreq_for_each_entry(pos, elanfreq_table) 1608c2ecf20Sopenharmony_ci if (pos->frequency > max_freq) 1618c2ecf20Sopenharmony_ci pos->frequency = CPUFREQ_ENTRY_INVALID; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci policy->freq_table = elanfreq_table; 1648c2ecf20Sopenharmony_ci return 0; 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci#ifndef MODULE 1698c2ecf20Sopenharmony_ci/** 1708c2ecf20Sopenharmony_ci * elanfreq_setup - elanfreq command line parameter parsing 1718c2ecf20Sopenharmony_ci * 1728c2ecf20Sopenharmony_ci * elanfreq command line parameter. Use: 1738c2ecf20Sopenharmony_ci * elanfreq=66000 1748c2ecf20Sopenharmony_ci * to set the maximum CPU frequency to 66 MHz. Note that in 1758c2ecf20Sopenharmony_ci * case you do not give this boot parameter, the maximum 1768c2ecf20Sopenharmony_ci * frequency will fall back to _current_ CPU frequency which 1778c2ecf20Sopenharmony_ci * might be lower. If you build this as a module, use the 1788c2ecf20Sopenharmony_ci * max_freq module parameter instead. 1798c2ecf20Sopenharmony_ci */ 1808c2ecf20Sopenharmony_cistatic int __init elanfreq_setup(char *str) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci max_freq = simple_strtoul(str, &str, 0); 1838c2ecf20Sopenharmony_ci pr_warn("You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please!\n"); 1848c2ecf20Sopenharmony_ci return 1; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci__setup("elanfreq=", elanfreq_setup); 1878c2ecf20Sopenharmony_ci#endif 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic struct cpufreq_driver elanfreq_driver = { 1918c2ecf20Sopenharmony_ci .get = elanfreq_get_cpu_frequency, 1928c2ecf20Sopenharmony_ci .flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING, 1938c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 1948c2ecf20Sopenharmony_ci .target_index = elanfreq_target, 1958c2ecf20Sopenharmony_ci .init = elanfreq_cpu_init, 1968c2ecf20Sopenharmony_ci .name = "elanfreq", 1978c2ecf20Sopenharmony_ci .attr = cpufreq_generic_attr, 1988c2ecf20Sopenharmony_ci}; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic const struct x86_cpu_id elan_id[] = { 2018c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 10, NULL), 2028c2ecf20Sopenharmony_ci {} 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(x86cpu, elan_id); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic int __init elanfreq_init(void) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci if (!x86_match_cpu(elan_id)) 2098c2ecf20Sopenharmony_ci return -ENODEV; 2108c2ecf20Sopenharmony_ci return cpufreq_register_driver(&elanfreq_driver); 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cistatic void __exit elanfreq_exit(void) 2158c2ecf20Sopenharmony_ci{ 2168c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&elanfreq_driver); 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cimodule_param(max_freq, int, 0444); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2238c2ecf20Sopenharmony_ciMODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, " 2248c2ecf20Sopenharmony_ci "Sven Geggus <sven@geggus.net>"); 2258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs"); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cimodule_init(elanfreq_init); 2288c2ecf20Sopenharmony_cimodule_exit(elanfreq_exit); 229