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 25 #define MMCR_BASE 0xfffef000 /* The default base address */ 26 #define OFFS_CPUCTL 0x2 /* CPU Control Register */ 27 28 static __u8 __iomem *cpuctl; 29 30 static struct cpufreq_frequency_table sc520_freq_table[] = { 31 {0, 0x01, 100000}, 32 {0, 0x02, 133000}, 33 {0, 0, CPUFREQ_TABLE_END}, 34 }; 35 36 static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu) 37 { 38 u8 clockspeed_reg = *cpuctl; 39 40 switch (clockspeed_reg & 0x03) { 41 default: 42 pr_err("error: cpuctl register has unexpected value %02x\n", 43 clockspeed_reg); 44 fallthrough; 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 }; 95 96 static const struct x86_cpu_id sc520_ids[] = { 97 X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 9, NULL), 98 {} 99 }; 100 MODULE_DEVICE_TABLE(x86cpu, sc520_ids); 101 102 static int __init sc520_freq_init(void) 103 { 104 int err; 105 106 if (!x86_match_cpu(sc520_ids)) 107 return -ENODEV; 108 109 cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1); 110 if (!cpuctl) { 111 pr_err("sc520_freq: error: failed to remap memory\n"); 112 return -ENOMEM; 113 } 114 115 err = cpufreq_register_driver(&sc520_freq_driver); 116 if (err) 117 iounmap(cpuctl); 118 119 return err; 120 } 121 122 123 static void __exit sc520_freq_exit(void) 124 { 125 cpufreq_unregister_driver(&sc520_freq_driver); 126 iounmap(cpuctl); 127 } 128 129 130 MODULE_LICENSE("GPL"); 131 MODULE_AUTHOR("Sean Young <sean@mess.org>"); 132 MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU"); 133 134 module_init(sc520_freq_init); 135 module_exit(sc520_freq_exit); 136 137