sc520_freq.c (2841B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * sc520_freq.c: cpufreq driver for the AMD Elan sc520 4 * 5 * Copyright (C) 2005 Sean Young <sean@mess.org> 6 * 7 * Based on elanfreq.c 8 * 9 * 2005-03-30: - initial revision 10 */ 11 12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 14#include <linux/kernel.h> 15#include <linux/module.h> 16#include <linux/init.h> 17 18#include <linux/delay.h> 19#include <linux/cpufreq.h> 20#include <linux/timex.h> 21#include <linux/io.h> 22 23#include <asm/cpu_device_id.h> 24#include <asm/msr.h> 25 26#define MMCR_BASE 0xfffef000 /* The default base address */ 27#define OFFS_CPUCTL 0x2 /* CPU Control Register */ 28 29static __u8 __iomem *cpuctl; 30 31static struct cpufreq_frequency_table sc520_freq_table[] = { 32 {0, 0x01, 100000}, 33 {0, 0x02, 133000}, 34 {0, 0, CPUFREQ_TABLE_END}, 35}; 36 37static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu) 38{ 39 u8 clockspeed_reg = *cpuctl; 40 41 switch (clockspeed_reg & 0x03) { 42 default: 43 pr_err("error: cpuctl register has unexpected value %02x\n", 44 clockspeed_reg); 45 fallthrough; 46 case 0x01: 47 return 100000; 48 case 0x02: 49 return 133000; 50 } 51} 52 53static int sc520_freq_target(struct cpufreq_policy *policy, unsigned int state) 54{ 55 56 u8 clockspeed_reg; 57 58 local_irq_disable(); 59 60 clockspeed_reg = *cpuctl & ~0x03; 61 *cpuctl = clockspeed_reg | sc520_freq_table[state].driver_data; 62 63 local_irq_enable(); 64 65 return 0; 66} 67 68/* 69 * Module init and exit code 70 */ 71 72static int sc520_freq_cpu_init(struct cpufreq_policy *policy) 73{ 74 struct cpuinfo_x86 *c = &cpu_data(0); 75 76 /* capability check */ 77 if (c->x86_vendor != X86_VENDOR_AMD || 78 c->x86 != 4 || c->x86_model != 9) 79 return -ENODEV; 80 81 /* cpuinfo and default policy values */ 82 policy->cpuinfo.transition_latency = 1000000; /* 1ms */ 83 policy->freq_table = sc520_freq_table; 84 85 return 0; 86} 87 88 89static struct cpufreq_driver sc520_freq_driver = { 90 .get = sc520_freq_get_cpu_frequency, 91 .verify = cpufreq_generic_frequency_table_verify, 92 .target_index = sc520_freq_target, 93 .init = sc520_freq_cpu_init, 94 .name = "sc520_freq", 95 .attr = cpufreq_generic_attr, 96}; 97 98static const struct x86_cpu_id sc520_ids[] = { 99 X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 9, NULL), 100 {} 101}; 102MODULE_DEVICE_TABLE(x86cpu, sc520_ids); 103 104static int __init sc520_freq_init(void) 105{ 106 int err; 107 108 if (!x86_match_cpu(sc520_ids)) 109 return -ENODEV; 110 111 cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1); 112 if (!cpuctl) { 113 pr_err("sc520_freq: error: failed to remap memory\n"); 114 return -ENOMEM; 115 } 116 117 err = cpufreq_register_driver(&sc520_freq_driver); 118 if (err) 119 iounmap(cpuctl); 120 121 return err; 122} 123 124 125static void __exit sc520_freq_exit(void) 126{ 127 cpufreq_unregister_driver(&sc520_freq_driver); 128 iounmap(cpuctl); 129} 130 131 132MODULE_LICENSE("GPL"); 133MODULE_AUTHOR("Sean Young <sean@mess.org>"); 134MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU"); 135 136module_init(sc520_freq_init); 137module_exit(sc520_freq_exit); 138