1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * via-cputemp.c - Driver for VIA CPU core temperature monitoring |
4 | * Copyright (C) 2009 VIA Technologies, Inc. |
5 | * |
6 | * based on existing coretemp.c, which is |
7 | * |
8 | * Copyright (C) 2007 Rudolf Marek <r.marek@assembler.cz> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/init.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/hwmon.h> |
17 | #include <linux/hwmon-vid.h> |
18 | #include <linux/sysfs.h> |
19 | #include <linux/hwmon-sysfs.h> |
20 | #include <linux/err.h> |
21 | #include <linux/mutex.h> |
22 | #include <linux/list.h> |
23 | #include <linux/platform_device.h> |
24 | #include <linux/cpu.h> |
25 | #include <asm/msr.h> |
26 | #include <asm/processor.h> |
27 | #include <asm/cpu_device_id.h> |
28 | |
29 | #define DRVNAME "via_cputemp" |
30 | |
31 | enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME }; |
32 | |
33 | /* |
34 | * Functions declaration |
35 | */ |
36 | |
37 | struct via_cputemp_data { |
38 | struct device *hwmon_dev; |
39 | const char *name; |
40 | u8 vrm; |
41 | u32 id; |
42 | u32 msr_temp; |
43 | u32 msr_vid; |
44 | }; |
45 | |
46 | /* |
47 | * Sysfs stuff |
48 | */ |
49 | |
50 | static ssize_t name_show(struct device *dev, struct device_attribute *devattr, |
51 | char *buf) |
52 | { |
53 | int ret; |
54 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
55 | struct via_cputemp_data *data = dev_get_drvdata(dev); |
56 | |
57 | if (attr->index == SHOW_NAME) |
58 | ret = sprintf(buf, fmt: "%s\n" , data->name); |
59 | else /* show label */ |
60 | ret = sprintf(buf, fmt: "Core %d\n" , data->id); |
61 | return ret; |
62 | } |
63 | |
64 | static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, |
65 | char *buf) |
66 | { |
67 | struct via_cputemp_data *data = dev_get_drvdata(dev); |
68 | u32 eax, edx; |
69 | int err; |
70 | |
71 | err = rdmsr_safe_on_cpu(cpu: data->id, msr_no: data->msr_temp, l: &eax, h: &edx); |
72 | if (err) |
73 | return -EAGAIN; |
74 | |
75 | return sprintf(buf, fmt: "%lu\n" , ((unsigned long)eax & 0xffffff) * 1000); |
76 | } |
77 | |
78 | static ssize_t cpu0_vid_show(struct device *dev, |
79 | struct device_attribute *devattr, char *buf) |
80 | { |
81 | struct via_cputemp_data *data = dev_get_drvdata(dev); |
82 | u32 eax, edx; |
83 | int err; |
84 | |
85 | err = rdmsr_safe_on_cpu(cpu: data->id, msr_no: data->msr_vid, l: &eax, h: &edx); |
86 | if (err) |
87 | return -EAGAIN; |
88 | |
89 | return sprintf(buf, fmt: "%d\n" , vid_from_reg(val: ~edx & 0x7f, vrm: data->vrm)); |
90 | } |
91 | |
92 | static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, SHOW_TEMP); |
93 | static SENSOR_DEVICE_ATTR_RO(temp1_label, name, SHOW_LABEL); |
94 | static SENSOR_DEVICE_ATTR_RO(name, name, SHOW_NAME); |
95 | |
96 | static struct attribute *via_cputemp_attributes[] = { |
97 | &sensor_dev_attr_name.dev_attr.attr, |
98 | &sensor_dev_attr_temp1_label.dev_attr.attr, |
99 | &sensor_dev_attr_temp1_input.dev_attr.attr, |
100 | NULL |
101 | }; |
102 | |
103 | static const struct attribute_group via_cputemp_group = { |
104 | .attrs = via_cputemp_attributes, |
105 | }; |
106 | |
107 | /* Optional attributes */ |
108 | static DEVICE_ATTR_RO(cpu0_vid); |
109 | |
110 | static int via_cputemp_probe(struct platform_device *pdev) |
111 | { |
112 | struct via_cputemp_data *data; |
113 | struct cpuinfo_x86 *c = &cpu_data(pdev->id); |
114 | int err; |
115 | u32 eax, edx; |
116 | |
117 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct via_cputemp_data), |
118 | GFP_KERNEL); |
119 | if (!data) |
120 | return -ENOMEM; |
121 | |
122 | data->id = pdev->id; |
123 | data->name = "via_cputemp" ; |
124 | |
125 | if (c->x86 == 7) { |
126 | data->msr_temp = 0x1423; |
127 | } else { |
128 | switch (c->x86_model) { |
129 | case 0xA: |
130 | /* C7 A */ |
131 | case 0xD: |
132 | /* C7 D */ |
133 | data->msr_temp = 0x1169; |
134 | data->msr_vid = 0x198; |
135 | break; |
136 | case 0xF: |
137 | /* Nano */ |
138 | data->msr_temp = 0x1423; |
139 | break; |
140 | default: |
141 | return -ENODEV; |
142 | } |
143 | } |
144 | |
145 | /* test if we can access the TEMPERATURE MSR */ |
146 | err = rdmsr_safe_on_cpu(cpu: data->id, msr_no: data->msr_temp, l: &eax, h: &edx); |
147 | if (err) { |
148 | dev_err(&pdev->dev, |
149 | "Unable to access TEMPERATURE MSR, giving up\n" ); |
150 | return err; |
151 | } |
152 | |
153 | platform_set_drvdata(pdev, data); |
154 | |
155 | err = sysfs_create_group(kobj: &pdev->dev.kobj, grp: &via_cputemp_group); |
156 | if (err) |
157 | return err; |
158 | |
159 | if (data->msr_vid) |
160 | data->vrm = vid_which_vrm(); |
161 | |
162 | if (data->vrm) { |
163 | err = device_create_file(device: &pdev->dev, entry: &dev_attr_cpu0_vid); |
164 | if (err) |
165 | goto exit_remove; |
166 | } |
167 | |
168 | data->hwmon_dev = hwmon_device_register(dev: &pdev->dev); |
169 | if (IS_ERR(ptr: data->hwmon_dev)) { |
170 | err = PTR_ERR(ptr: data->hwmon_dev); |
171 | dev_err(&pdev->dev, "Class registration failed (%d)\n" , |
172 | err); |
173 | goto exit_remove; |
174 | } |
175 | |
176 | return 0; |
177 | |
178 | exit_remove: |
179 | if (data->vrm) |
180 | device_remove_file(dev: &pdev->dev, attr: &dev_attr_cpu0_vid); |
181 | sysfs_remove_group(kobj: &pdev->dev.kobj, grp: &via_cputemp_group); |
182 | return err; |
183 | } |
184 | |
185 | static void via_cputemp_remove(struct platform_device *pdev) |
186 | { |
187 | struct via_cputemp_data *data = platform_get_drvdata(pdev); |
188 | |
189 | hwmon_device_unregister(dev: data->hwmon_dev); |
190 | if (data->vrm) |
191 | device_remove_file(dev: &pdev->dev, attr: &dev_attr_cpu0_vid); |
192 | sysfs_remove_group(kobj: &pdev->dev.kobj, grp: &via_cputemp_group); |
193 | } |
194 | |
195 | static struct platform_driver via_cputemp_driver = { |
196 | .driver = { |
197 | .name = DRVNAME, |
198 | }, |
199 | .probe = via_cputemp_probe, |
200 | .remove_new = via_cputemp_remove, |
201 | }; |
202 | |
203 | struct pdev_entry { |
204 | struct list_head list; |
205 | struct platform_device *pdev; |
206 | unsigned int cpu; |
207 | }; |
208 | |
209 | static LIST_HEAD(pdev_list); |
210 | static DEFINE_MUTEX(pdev_list_mutex); |
211 | |
212 | static int via_cputemp_online(unsigned int cpu) |
213 | { |
214 | int err; |
215 | struct platform_device *pdev; |
216 | struct pdev_entry *pdev_entry; |
217 | |
218 | pdev = platform_device_alloc(DRVNAME, id: cpu); |
219 | if (!pdev) { |
220 | err = -ENOMEM; |
221 | pr_err("Device allocation failed\n" ); |
222 | goto exit; |
223 | } |
224 | |
225 | pdev_entry = kzalloc(size: sizeof(struct pdev_entry), GFP_KERNEL); |
226 | if (!pdev_entry) { |
227 | err = -ENOMEM; |
228 | goto exit_device_put; |
229 | } |
230 | |
231 | err = platform_device_add(pdev); |
232 | if (err) { |
233 | pr_err("Device addition failed (%d)\n" , err); |
234 | goto exit_device_free; |
235 | } |
236 | |
237 | pdev_entry->pdev = pdev; |
238 | pdev_entry->cpu = cpu; |
239 | mutex_lock(&pdev_list_mutex); |
240 | list_add_tail(new: &pdev_entry->list, head: &pdev_list); |
241 | mutex_unlock(lock: &pdev_list_mutex); |
242 | |
243 | return 0; |
244 | |
245 | exit_device_free: |
246 | kfree(objp: pdev_entry); |
247 | exit_device_put: |
248 | platform_device_put(pdev); |
249 | exit: |
250 | return err; |
251 | } |
252 | |
253 | static int via_cputemp_down_prep(unsigned int cpu) |
254 | { |
255 | struct pdev_entry *p; |
256 | |
257 | mutex_lock(&pdev_list_mutex); |
258 | list_for_each_entry(p, &pdev_list, list) { |
259 | if (p->cpu == cpu) { |
260 | platform_device_unregister(p->pdev); |
261 | list_del(entry: &p->list); |
262 | mutex_unlock(lock: &pdev_list_mutex); |
263 | kfree(objp: p); |
264 | return 0; |
265 | } |
266 | } |
267 | mutex_unlock(lock: &pdev_list_mutex); |
268 | return 0; |
269 | } |
270 | |
271 | static const struct x86_cpu_id __initconst cputemp_ids[] = { |
272 | X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_C7_A, NULL), |
273 | X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_C7_D, NULL), |
274 | X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_NANO, NULL), |
275 | X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 7, X86_MODEL_ANY, NULL), |
276 | {} |
277 | }; |
278 | MODULE_DEVICE_TABLE(x86cpu, cputemp_ids); |
279 | |
280 | static enum cpuhp_state via_temp_online; |
281 | |
282 | static int __init via_cputemp_init(void) |
283 | { |
284 | int err; |
285 | |
286 | if (!x86_match_cpu(match: cputemp_ids)) |
287 | return -ENODEV; |
288 | |
289 | err = platform_driver_register(&via_cputemp_driver); |
290 | if (err) |
291 | goto exit; |
292 | |
293 | err = cpuhp_setup_state(state: CPUHP_AP_ONLINE_DYN, name: "hwmon/via:online" , |
294 | startup: via_cputemp_online, teardown: via_cputemp_down_prep); |
295 | if (err < 0) |
296 | goto exit_driver_unreg; |
297 | via_temp_online = err; |
298 | |
299 | #ifndef CONFIG_HOTPLUG_CPU |
300 | if (list_empty(&pdev_list)) { |
301 | err = -ENODEV; |
302 | goto exit_hp_unreg; |
303 | } |
304 | #endif |
305 | return 0; |
306 | |
307 | #ifndef CONFIG_HOTPLUG_CPU |
308 | exit_hp_unreg: |
309 | cpuhp_remove_state_nocalls(via_temp_online); |
310 | #endif |
311 | exit_driver_unreg: |
312 | platform_driver_unregister(&via_cputemp_driver); |
313 | exit: |
314 | return err; |
315 | } |
316 | |
317 | static void __exit via_cputemp_exit(void) |
318 | { |
319 | cpuhp_remove_state(state: via_temp_online); |
320 | platform_driver_unregister(&via_cputemp_driver); |
321 | } |
322 | |
323 | MODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>" ); |
324 | MODULE_DESCRIPTION("VIA CPU temperature monitor" ); |
325 | MODULE_LICENSE("GPL" ); |
326 | |
327 | module_init(via_cputemp_init) |
328 | module_exit(via_cputemp_exit) |
329 | |