1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * processor_thermal_device.c |
4 | * Copyright (c) 2014, Intel Corporation. |
5 | */ |
6 | #include <linux/acpi.h> |
7 | #include <linux/intel_tcc.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/pci.h> |
11 | #include <linux/thermal.h> |
12 | #include "int340x_thermal_zone.h" |
13 | #include "processor_thermal_device.h" |
14 | #include "../intel_soc_dts_iosf.h" |
15 | |
16 | #define DRV_NAME "proc_thermal" |
17 | |
18 | #define POWER_LIMIT_SHOW(index, suffix) \ |
19 | static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \ |
20 | struct device_attribute *attr, \ |
21 | char *buf) \ |
22 | { \ |
23 | struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); \ |
24 | \ |
25 | return sprintf(buf, "%lu\n",\ |
26 | (unsigned long)proc_dev->power_limits[index].suffix * 1000); \ |
27 | } |
28 | |
29 | static ssize_t power_floor_status_show(struct device *dev, |
30 | struct device_attribute *attr, |
31 | char *buf) |
32 | { |
33 | struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); |
34 | int ret; |
35 | |
36 | ret = proc_thermal_read_power_floor_status(proc_priv: proc_dev); |
37 | |
38 | return sysfs_emit(buf, fmt: "%d\n" , ret); |
39 | } |
40 | |
41 | static ssize_t power_floor_enable_show(struct device *dev, |
42 | struct device_attribute *attr, |
43 | char *buf) |
44 | { |
45 | struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); |
46 | bool ret; |
47 | |
48 | ret = proc_thermal_power_floor_get_state(proc_priv: proc_dev); |
49 | |
50 | return sysfs_emit(buf, fmt: "%d\n" , ret); |
51 | } |
52 | |
53 | static ssize_t power_floor_enable_store(struct device *dev, |
54 | struct device_attribute *attr, |
55 | const char *buf, size_t count) |
56 | { |
57 | struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); |
58 | u8 state; |
59 | int ret; |
60 | |
61 | if (kstrtou8(s: buf, base: 0, res: &state)) |
62 | return -EINVAL; |
63 | |
64 | ret = proc_thermal_power_floor_set_state(proc_priv: proc_dev, enable: !!state); |
65 | if (ret) |
66 | return ret; |
67 | |
68 | return count; |
69 | } |
70 | |
71 | POWER_LIMIT_SHOW(0, min_uw) |
72 | POWER_LIMIT_SHOW(0, max_uw) |
73 | POWER_LIMIT_SHOW(0, step_uw) |
74 | POWER_LIMIT_SHOW(0, tmin_us) |
75 | POWER_LIMIT_SHOW(0, tmax_us) |
76 | |
77 | POWER_LIMIT_SHOW(1, min_uw) |
78 | POWER_LIMIT_SHOW(1, max_uw) |
79 | POWER_LIMIT_SHOW(1, step_uw) |
80 | POWER_LIMIT_SHOW(1, tmin_us) |
81 | POWER_LIMIT_SHOW(1, tmax_us) |
82 | |
83 | static DEVICE_ATTR_RO(power_limit_0_min_uw); |
84 | static DEVICE_ATTR_RO(power_limit_0_max_uw); |
85 | static DEVICE_ATTR_RO(power_limit_0_step_uw); |
86 | static DEVICE_ATTR_RO(power_limit_0_tmin_us); |
87 | static DEVICE_ATTR_RO(power_limit_0_tmax_us); |
88 | |
89 | static DEVICE_ATTR_RO(power_limit_1_min_uw); |
90 | static DEVICE_ATTR_RO(power_limit_1_max_uw); |
91 | static DEVICE_ATTR_RO(power_limit_1_step_uw); |
92 | static DEVICE_ATTR_RO(power_limit_1_tmin_us); |
93 | static DEVICE_ATTR_RO(power_limit_1_tmax_us); |
94 | |
95 | static DEVICE_ATTR_RO(power_floor_status); |
96 | static DEVICE_ATTR_RW(power_floor_enable); |
97 | |
98 | static struct attribute *power_limit_attrs[] = { |
99 | &dev_attr_power_limit_0_min_uw.attr, |
100 | &dev_attr_power_limit_1_min_uw.attr, |
101 | &dev_attr_power_limit_0_max_uw.attr, |
102 | &dev_attr_power_limit_1_max_uw.attr, |
103 | &dev_attr_power_limit_0_step_uw.attr, |
104 | &dev_attr_power_limit_1_step_uw.attr, |
105 | &dev_attr_power_limit_0_tmin_us.attr, |
106 | &dev_attr_power_limit_1_tmin_us.attr, |
107 | &dev_attr_power_limit_0_tmax_us.attr, |
108 | &dev_attr_power_limit_1_tmax_us.attr, |
109 | &dev_attr_power_floor_status.attr, |
110 | &dev_attr_power_floor_enable.attr, |
111 | NULL |
112 | }; |
113 | |
114 | static umode_t power_limit_attr_visible(struct kobject *kobj, struct attribute *attr, int unused) |
115 | { |
116 | struct device *dev = kobj_to_dev(kobj); |
117 | struct proc_thermal_device *proc_dev; |
118 | |
119 | if (attr != &dev_attr_power_floor_status.attr && attr != &dev_attr_power_floor_enable.attr) |
120 | return attr->mode; |
121 | |
122 | proc_dev = dev_get_drvdata(dev); |
123 | if (!proc_dev || !(proc_dev->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR)) |
124 | return 0; |
125 | |
126 | return attr->mode; |
127 | } |
128 | |
129 | static const struct attribute_group power_limit_attribute_group = { |
130 | .attrs = power_limit_attrs, |
131 | .name = "power_limits" , |
132 | .is_visible = power_limit_attr_visible, |
133 | }; |
134 | |
135 | static ssize_t tcc_offset_degree_celsius_show(struct device *dev, |
136 | struct device_attribute *attr, |
137 | char *buf) |
138 | { |
139 | int offset; |
140 | |
141 | offset = intel_tcc_get_offset(cpu: -1); |
142 | if (offset < 0) |
143 | return offset; |
144 | |
145 | return sprintf(buf, fmt: "%d\n" , offset); |
146 | } |
147 | |
148 | static ssize_t tcc_offset_degree_celsius_store(struct device *dev, |
149 | struct device_attribute *attr, const char *buf, |
150 | size_t count) |
151 | { |
152 | unsigned int tcc; |
153 | u64 val; |
154 | int err; |
155 | |
156 | err = rdmsrl_safe(MSR_PLATFORM_INFO, p: &val); |
157 | if (err) |
158 | return err; |
159 | |
160 | if (!(val & BIT(30))) |
161 | return -EACCES; |
162 | |
163 | if (kstrtouint(s: buf, base: 0, res: &tcc)) |
164 | return -EINVAL; |
165 | |
166 | err = intel_tcc_set_offset(cpu: -1, offset: tcc); |
167 | if (err) |
168 | return err; |
169 | |
170 | return count; |
171 | } |
172 | |
173 | static DEVICE_ATTR_RW(tcc_offset_degree_celsius); |
174 | |
175 | static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone, |
176 | int *temp) |
177 | { |
178 | int cpu; |
179 | int curr_temp; |
180 | |
181 | *temp = 0; |
182 | |
183 | for_each_online_cpu(cpu) { |
184 | curr_temp = intel_tcc_get_temp(cpu, pkg: false); |
185 | if (curr_temp < 0) |
186 | return curr_temp; |
187 | if (!*temp || curr_temp > *temp) |
188 | *temp = curr_temp; |
189 | } |
190 | |
191 | *temp *= 1000; |
192 | |
193 | return 0; |
194 | } |
195 | |
196 | static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv) |
197 | { |
198 | int i; |
199 | acpi_status status; |
200 | struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; |
201 | union acpi_object *elements, *ppcc; |
202 | union acpi_object *p; |
203 | int ret = 0; |
204 | |
205 | status = acpi_evaluate_object(object: proc_priv->adev->handle, pathname: "PPCC" , |
206 | NULL, return_object_buffer: &buf); |
207 | if (ACPI_FAILURE(status)) |
208 | return -ENODEV; |
209 | |
210 | p = buf.pointer; |
211 | if (!p || (p->type != ACPI_TYPE_PACKAGE)) { |
212 | dev_err(proc_priv->dev, "Invalid PPCC data\n" ); |
213 | ret = -EFAULT; |
214 | goto free_buffer; |
215 | } |
216 | |
217 | if (!p->package.count) { |
218 | dev_err(proc_priv->dev, "Invalid PPCC package size\n" ); |
219 | ret = -EFAULT; |
220 | goto free_buffer; |
221 | } |
222 | |
223 | for (i = 0; i < min((int)p->package.count - 1, 2); ++i) { |
224 | elements = &(p->package.elements[i+1]); |
225 | if (elements->type != ACPI_TYPE_PACKAGE || |
226 | elements->package.count != 6) { |
227 | ret = -EFAULT; |
228 | goto free_buffer; |
229 | } |
230 | ppcc = elements->package.elements; |
231 | proc_priv->power_limits[i].index = ppcc[0].integer.value; |
232 | proc_priv->power_limits[i].min_uw = ppcc[1].integer.value; |
233 | proc_priv->power_limits[i].max_uw = ppcc[2].integer.value; |
234 | proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value; |
235 | proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value; |
236 | proc_priv->power_limits[i].step_uw = ppcc[5].integer.value; |
237 | } |
238 | |
239 | free_buffer: |
240 | kfree(objp: buf.pointer); |
241 | |
242 | return ret; |
243 | } |
244 | |
245 | #define PROC_POWER_CAPABILITY_CHANGED 0x83 |
246 | static void proc_thermal_notify(acpi_handle handle, u32 event, void *data) |
247 | { |
248 | struct proc_thermal_device *proc_priv = data; |
249 | |
250 | if (!proc_priv) |
251 | return; |
252 | |
253 | switch (event) { |
254 | case PROC_POWER_CAPABILITY_CHANGED: |
255 | proc_thermal_read_ppcc(proc_priv); |
256 | int340x_thermal_zone_device_update(tzone: proc_priv->int340x_zone, |
257 | event: THERMAL_DEVICE_POWER_CAPABILITY_CHANGED); |
258 | break; |
259 | default: |
260 | dev_dbg(proc_priv->dev, "Unsupported event [0x%x]\n" , event); |
261 | break; |
262 | } |
263 | } |
264 | |
265 | int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv) |
266 | { |
267 | struct acpi_device *adev; |
268 | acpi_status status; |
269 | unsigned long long tmp; |
270 | int (*get_temp) (struct thermal_zone_device *, int *) = NULL; |
271 | int ret; |
272 | |
273 | adev = ACPI_COMPANION(dev); |
274 | if (!adev) |
275 | return -ENODEV; |
276 | |
277 | proc_priv->dev = dev; |
278 | proc_priv->adev = adev; |
279 | |
280 | ret = proc_thermal_read_ppcc(proc_priv); |
281 | if (ret) |
282 | return ret; |
283 | |
284 | status = acpi_evaluate_integer(handle: adev->handle, pathname: "_TMP" , NULL, data: &tmp); |
285 | if (ACPI_FAILURE(status)) { |
286 | /* there is no _TMP method, add local method */ |
287 | if (intel_tcc_get_tjmax(cpu: -1) > 0) |
288 | get_temp = proc_thermal_get_zone_temp; |
289 | } |
290 | |
291 | proc_priv->int340x_zone = int340x_thermal_zone_add(adev, get_temp); |
292 | if (IS_ERR(ptr: proc_priv->int340x_zone)) { |
293 | return PTR_ERR(ptr: proc_priv->int340x_zone); |
294 | } else |
295 | ret = 0; |
296 | |
297 | ret = acpi_install_notify_handler(device: adev->handle, ACPI_DEVICE_NOTIFY, |
298 | handler: proc_thermal_notify, |
299 | context: (void *)proc_priv); |
300 | if (ret) |
301 | goto remove_zone; |
302 | |
303 | ret = sysfs_create_file(kobj: &dev->kobj, attr: &dev_attr_tcc_offset_degree_celsius.attr); |
304 | if (ret) |
305 | goto remove_notify; |
306 | |
307 | ret = sysfs_create_group(kobj: &dev->kobj, grp: &power_limit_attribute_group); |
308 | if (ret) { |
309 | sysfs_remove_file(kobj: &dev->kobj, attr: &dev_attr_tcc_offset_degree_celsius.attr); |
310 | goto remove_notify; |
311 | } |
312 | |
313 | return 0; |
314 | |
315 | remove_notify: |
316 | acpi_remove_notify_handler(device: adev->handle, |
317 | ACPI_DEVICE_NOTIFY, handler: proc_thermal_notify); |
318 | remove_zone: |
319 | int340x_thermal_zone_remove(proc_priv->int340x_zone); |
320 | |
321 | return ret; |
322 | } |
323 | EXPORT_SYMBOL_GPL(proc_thermal_add); |
324 | |
325 | void proc_thermal_remove(struct proc_thermal_device *proc_priv) |
326 | { |
327 | acpi_remove_notify_handler(device: proc_priv->adev->handle, |
328 | ACPI_DEVICE_NOTIFY, handler: proc_thermal_notify); |
329 | int340x_thermal_zone_remove(proc_priv->int340x_zone); |
330 | sysfs_remove_file(kobj: &proc_priv->dev->kobj, attr: &dev_attr_tcc_offset_degree_celsius.attr); |
331 | sysfs_remove_group(kobj: &proc_priv->dev->kobj, |
332 | grp: &power_limit_attribute_group); |
333 | } |
334 | EXPORT_SYMBOL_GPL(proc_thermal_remove); |
335 | |
336 | static int tcc_offset_save = -1; |
337 | |
338 | int proc_thermal_suspend(struct device *dev) |
339 | { |
340 | tcc_offset_save = intel_tcc_get_offset(cpu: -1); |
341 | if (tcc_offset_save < 0) |
342 | dev_warn(dev, "failed to save offset (%d)\n" , tcc_offset_save); |
343 | |
344 | return 0; |
345 | } |
346 | EXPORT_SYMBOL_GPL(proc_thermal_suspend); |
347 | |
348 | int proc_thermal_resume(struct device *dev) |
349 | { |
350 | struct proc_thermal_device *proc_dev; |
351 | |
352 | proc_dev = dev_get_drvdata(dev); |
353 | proc_thermal_read_ppcc(proc_priv: proc_dev); |
354 | |
355 | /* Do not update if saving failed */ |
356 | if (tcc_offset_save >= 0) |
357 | intel_tcc_set_offset(cpu: -1, offset: tcc_offset_save); |
358 | |
359 | return 0; |
360 | } |
361 | EXPORT_SYMBOL_GPL(proc_thermal_resume); |
362 | |
363 | #define MCHBAR 0 |
364 | |
365 | static int proc_thermal_set_mmio_base(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) |
366 | { |
367 | int ret; |
368 | |
369 | ret = pcim_iomap_regions(pdev, mask: 1 << MCHBAR, DRV_NAME); |
370 | if (ret) { |
371 | dev_err(&pdev->dev, "cannot reserve PCI memory region\n" ); |
372 | return -ENOMEM; |
373 | } |
374 | |
375 | proc_priv->mmio_base = pcim_iomap_table(pdev)[MCHBAR]; |
376 | |
377 | return 0; |
378 | } |
379 | |
380 | int proc_thermal_mmio_add(struct pci_dev *pdev, |
381 | struct proc_thermal_device *proc_priv, |
382 | kernel_ulong_t feature_mask) |
383 | { |
384 | int ret; |
385 | |
386 | proc_priv->mmio_feature_mask = feature_mask; |
387 | |
388 | if (feature_mask) { |
389 | ret = proc_thermal_set_mmio_base(pdev, proc_priv); |
390 | if (ret) |
391 | return ret; |
392 | } |
393 | |
394 | if (feature_mask & PROC_THERMAL_FEATURE_RAPL) { |
395 | ret = proc_thermal_rapl_add(pdev, proc_priv); |
396 | if (ret) { |
397 | dev_err(&pdev->dev, "failed to add RAPL MMIO interface\n" ); |
398 | return ret; |
399 | } |
400 | } |
401 | |
402 | if (feature_mask & PROC_THERMAL_FEATURE_FIVR || |
403 | feature_mask & PROC_THERMAL_FEATURE_DVFS || |
404 | feature_mask & PROC_THERMAL_FEATURE_DLVR) { |
405 | ret = proc_thermal_rfim_add(pdev, proc_priv); |
406 | if (ret) { |
407 | dev_err(&pdev->dev, "failed to add RFIM interface\n" ); |
408 | goto err_rem_rapl; |
409 | } |
410 | } |
411 | |
412 | if (feature_mask & PROC_THERMAL_FEATURE_WT_REQ) { |
413 | ret = proc_thermal_wt_req_add(pdev, proc_priv); |
414 | if (ret) { |
415 | dev_err(&pdev->dev, "failed to add MBOX interface\n" ); |
416 | goto err_rem_rfim; |
417 | } |
418 | } else if (feature_mask & PROC_THERMAL_FEATURE_WT_HINT) { |
419 | ret = proc_thermal_wt_hint_add(pdev, proc_priv); |
420 | if (ret) { |
421 | dev_err(&pdev->dev, "failed to add WT Hint\n" ); |
422 | goto err_rem_rfim; |
423 | } |
424 | } |
425 | |
426 | return 0; |
427 | |
428 | err_rem_rfim: |
429 | proc_thermal_rfim_remove(pdev); |
430 | err_rem_rapl: |
431 | proc_thermal_rapl_remove(); |
432 | |
433 | return ret; |
434 | } |
435 | EXPORT_SYMBOL_GPL(proc_thermal_mmio_add); |
436 | |
437 | void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *proc_priv) |
438 | { |
439 | if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_RAPL) |
440 | proc_thermal_rapl_remove(); |
441 | |
442 | if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR || |
443 | proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) |
444 | proc_thermal_rfim_remove(pdev); |
445 | |
446 | if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR) |
447 | proc_thermal_power_floor_set_state(proc_priv, enable: false); |
448 | |
449 | if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_REQ) |
450 | proc_thermal_wt_req_remove(pdev); |
451 | else if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_HINT) |
452 | proc_thermal_wt_hint_remove(pdev); |
453 | } |
454 | EXPORT_SYMBOL_GPL(proc_thermal_mmio_remove); |
455 | |
456 | MODULE_IMPORT_NS(INTEL_TCC); |
457 | MODULE_IMPORT_NS(INT340X_THERMAL); |
458 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>" ); |
459 | MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver" ); |
460 | MODULE_LICENSE("GPL v2" ); |
461 | |