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(match: sc520_ids)) |
109 | return -ENODEV; |
110 | |
111 | cpuctl = ioremap(offset: (unsigned long)(MMCR_BASE + OFFS_CPUCTL), size: 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(driver_data: &sc520_freq_driver); |
118 | if (err) |
119 | iounmap(addr: cpuctl); |
120 | |
121 | return err; |
122 | } |
123 | |
124 | |
125 | static void __exit sc520_freq_exit(void) |
126 | { |
127 | cpufreq_unregister_driver(driver_data: &sc520_freq_driver); |
128 | iounmap(addr: 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 | |