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