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 case 0x01: 46 return 100000; 47 case 0x02: 48 return 133000; 49 } 50 } 51 52 static int sc520_freq_target(struct cpufreq_policy *policy, unsigned int state) 53 { 54 55 u8 clockspeed_reg; 56 57 local_irq_disable(); 58 59 clockspeed_reg = *cpuctl & ~0x03; 60 *cpuctl = clockspeed_reg | sc520_freq_table[state].driver_data; 61 62 local_irq_enable(); 63 64 return 0; 65 } 66 67 /* 68 * Module init and exit code 69 */ 70 71 static int sc520_freq_cpu_init(struct cpufreq_policy *policy) 72 { 73 struct cpuinfo_x86 *c = &cpu_data(0); 74 75 /* capability check */ 76 if (c->x86_vendor != X86_VENDOR_AMD || 77 c->x86 != 4 || c->x86_model != 9) 78 return -ENODEV; 79 80 /* cpuinfo and default policy values */ 81 policy->cpuinfo.transition_latency = 1000000; /* 1ms */ 82 policy->freq_table = sc520_freq_table; 83 84 return 0; 85 } 86 87 88 static struct cpufreq_driver sc520_freq_driver = { 89 .get = sc520_freq_get_cpu_frequency, 90 .verify = cpufreq_generic_frequency_table_verify, 91 .target_index = sc520_freq_target, 92 .init = sc520_freq_cpu_init, 93 .name = "sc520_freq", 94 .attr = cpufreq_generic_attr, 95 }; 96 97 static const struct x86_cpu_id sc520_ids[] = { 98 { X86_VENDOR_AMD, 4, 9 }, 99 {} 100 }; 101 MODULE_DEVICE_TABLE(x86cpu, sc520_ids); 102 103 static int __init sc520_freq_init(void) 104 { 105 int err; 106 107 if (!x86_match_cpu(sc520_ids)) 108 return -ENODEV; 109 110 cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1); 111 if (!cpuctl) { 112 pr_err("sc520_freq: error: failed to remap memory\n"); 113 return -ENOMEM; 114 } 115 116 err = cpufreq_register_driver(&sc520_freq_driver); 117 if (err) 118 iounmap(cpuctl); 119 120 return err; 121 } 122 123 124 static void __exit sc520_freq_exit(void) 125 { 126 cpufreq_unregister_driver(&sc520_freq_driver); 127 iounmap(cpuctl); 128 } 129 130 131 MODULE_LICENSE("GPL"); 132 MODULE_AUTHOR("Sean Young <sean@mess.org>"); 133 MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU"); 134 135 module_init(sc520_freq_init); 136 module_exit(sc520_freq_exit); 137 138