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 29 static __u8 __iomem *cpuctl; 30 31 static struct cpufreq_frequency_table sc520_freq_table[] = { 32 {0, 0x01, 100000}, 33 {0, 0x02, 133000}, 34 {0, 0, CPUFREQ_TABLE_END}, 35 }; 36 37 static 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 53 static 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 72 static 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 89 static 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 98 static const struct x86_cpu_id sc520_ids[] = { 99 X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 9, NULL), 100 {} 101 }; 102 MODULE_DEVICE_TABLE(x86cpu, sc520_ids); 103 104 static 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 125 static void __exit sc520_freq_exit(void) 126 { 127 cpufreq_unregister_driver(&sc520_freq_driver); 128 iounmap(cpuctl); 129 } 130 131 132 MODULE_LICENSE("GPL"); 133 MODULE_AUTHOR("Sean Young <sean@mess.org>"); 134 MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU"); 135 136 module_init(sc520_freq_init); 137 module_exit(sc520_freq_exit); 138 139