Commit | Line | Data |
---|---|---|
47c93e6b SP |
1 | /* |
2 | * processor_thermal_device.c | |
3 | * Copyright (c) 2014, Intel Corporation. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms and conditions of the GNU General Public License, | |
7 | * version 2, as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | */ | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/init.h> | |
18 | #include <linux/pci.h> | |
4d0dd6c1 | 19 | #include <linux/interrupt.h> |
47c93e6b SP |
20 | #include <linux/platform_device.h> |
21 | #include <linux/acpi.h> | |
1c55be02 SP |
22 | #include <linux/thermal.h> |
23 | #include "int340x_thermal_zone.h" | |
4d0dd6c1 | 24 | #include "../intel_soc_dts_iosf.h" |
47c93e6b SP |
25 | |
26 | /* Broadwell-U/HSB thermal reporting device */ | |
27 | #define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603 | |
28 | #define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03 | |
29 | ||
383b4d60 BB |
30 | /* Skylake thermal reporting device */ |
31 | #define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903 | |
32 | ||
47c93e6b SP |
33 | /* Braswell thermal reporting device */ |
34 | #define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC | |
35 | ||
20bbfaf7 AW |
36 | /* Broxton thermal reporting device */ |
37 | #define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C | |
38 | #define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C | |
39 | #define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C | |
40 | #define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C | |
41 | ||
47c93e6b SP |
42 | struct power_config { |
43 | u32 index; | |
44 | u32 min_uw; | |
45 | u32 max_uw; | |
46 | u32 tmin_us; | |
47 | u32 tmax_us; | |
48 | u32 step_uw; | |
49 | }; | |
50 | ||
51 | struct proc_thermal_device { | |
52 | struct device *dev; | |
53 | struct acpi_device *adev; | |
54 | struct power_config power_limits[2]; | |
1c55be02 | 55 | struct int34x_thermal_zone *int340x_zone; |
4d0dd6c1 | 56 | struct intel_soc_dts_sensors *soc_dts; |
47c93e6b SP |
57 | }; |
58 | ||
59 | enum proc_thermal_emum_mode_type { | |
60 | PROC_THERMAL_NONE, | |
61 | PROC_THERMAL_PCI, | |
62 | PROC_THERMAL_PLATFORM_DEV | |
63 | }; | |
64 | ||
65 | /* | |
66 | * We can have only one type of enumeration, PCI or Platform, | |
67 | * not both. So we don't need instance specific data. | |
68 | */ | |
69 | static enum proc_thermal_emum_mode_type proc_thermal_emum_mode = | |
70 | PROC_THERMAL_NONE; | |
71 | ||
72 | #define POWER_LIMIT_SHOW(index, suffix) \ | |
73 | static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \ | |
74 | struct device_attribute *attr, \ | |
75 | char *buf) \ | |
76 | { \ | |
77 | struct pci_dev *pci_dev; \ | |
78 | struct platform_device *pdev; \ | |
79 | struct proc_thermal_device *proc_dev; \ | |
80 | \ | |
81 | if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { \ | |
82 | pdev = to_platform_device(dev); \ | |
83 | proc_dev = platform_get_drvdata(pdev); \ | |
84 | } else { \ | |
85 | pci_dev = to_pci_dev(dev); \ | |
86 | proc_dev = pci_get_drvdata(pci_dev); \ | |
87 | } \ | |
88 | return sprintf(buf, "%lu\n",\ | |
89 | (unsigned long)proc_dev->power_limits[index].suffix * 1000); \ | |
90 | } | |
91 | ||
92 | POWER_LIMIT_SHOW(0, min_uw) | |
93 | POWER_LIMIT_SHOW(0, max_uw) | |
94 | POWER_LIMIT_SHOW(0, step_uw) | |
95 | POWER_LIMIT_SHOW(0, tmin_us) | |
96 | POWER_LIMIT_SHOW(0, tmax_us) | |
97 | ||
98 | POWER_LIMIT_SHOW(1, min_uw) | |
99 | POWER_LIMIT_SHOW(1, max_uw) | |
100 | POWER_LIMIT_SHOW(1, step_uw) | |
101 | POWER_LIMIT_SHOW(1, tmin_us) | |
102 | POWER_LIMIT_SHOW(1, tmax_us) | |
103 | ||
104 | static DEVICE_ATTR_RO(power_limit_0_min_uw); | |
105 | static DEVICE_ATTR_RO(power_limit_0_max_uw); | |
106 | static DEVICE_ATTR_RO(power_limit_0_step_uw); | |
107 | static DEVICE_ATTR_RO(power_limit_0_tmin_us); | |
108 | static DEVICE_ATTR_RO(power_limit_0_tmax_us); | |
109 | ||
110 | static DEVICE_ATTR_RO(power_limit_1_min_uw); | |
111 | static DEVICE_ATTR_RO(power_limit_1_max_uw); | |
112 | static DEVICE_ATTR_RO(power_limit_1_step_uw); | |
113 | static DEVICE_ATTR_RO(power_limit_1_tmin_us); | |
114 | static DEVICE_ATTR_RO(power_limit_1_tmax_us); | |
115 | ||
116 | static struct attribute *power_limit_attrs[] = { | |
117 | &dev_attr_power_limit_0_min_uw.attr, | |
118 | &dev_attr_power_limit_1_min_uw.attr, | |
119 | &dev_attr_power_limit_0_max_uw.attr, | |
120 | &dev_attr_power_limit_1_max_uw.attr, | |
121 | &dev_attr_power_limit_0_step_uw.attr, | |
122 | &dev_attr_power_limit_1_step_uw.attr, | |
123 | &dev_attr_power_limit_0_tmin_us.attr, | |
124 | &dev_attr_power_limit_1_tmin_us.attr, | |
125 | &dev_attr_power_limit_0_tmax_us.attr, | |
126 | &dev_attr_power_limit_1_tmax_us.attr, | |
127 | NULL | |
128 | }; | |
129 | ||
130 | static struct attribute_group power_limit_attribute_group = { | |
131 | .attrs = power_limit_attrs, | |
132 | .name = "power_limits" | |
133 | }; | |
134 | ||
1c55be02 SP |
135 | static int stored_tjmax; /* since it is fixed, we can have local storage */ |
136 | ||
137 | static int get_tjmax(void) | |
138 | { | |
139 | u32 eax, edx; | |
140 | u32 val; | |
141 | int err; | |
142 | ||
143 | err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); | |
144 | if (err) | |
145 | return err; | |
146 | ||
147 | val = (eax >> 16) & 0xff; | |
148 | if (val) | |
149 | return val; | |
150 | ||
151 | return -EINVAL; | |
152 | } | |
153 | ||
17e8351a | 154 | static int read_temp_msr(int *temp) |
1c55be02 SP |
155 | { |
156 | int cpu; | |
157 | u32 eax, edx; | |
158 | int err; | |
159 | unsigned long curr_temp_off = 0; | |
160 | ||
161 | *temp = 0; | |
162 | ||
163 | for_each_online_cpu(cpu) { | |
164 | err = rdmsr_safe_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax, | |
165 | &edx); | |
166 | if (err) | |
167 | goto err_ret; | |
168 | else { | |
169 | if (eax & 0x80000000) { | |
170 | curr_temp_off = (eax >> 16) & 0x7f; | |
171 | if (!*temp || curr_temp_off < *temp) | |
172 | *temp = curr_temp_off; | |
173 | } else { | |
174 | err = -EINVAL; | |
175 | goto err_ret; | |
176 | } | |
177 | } | |
178 | } | |
179 | ||
180 | return 0; | |
181 | err_ret: | |
182 | return err; | |
183 | } | |
184 | ||
185 | static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone, | |
17e8351a | 186 | int *temp) |
1c55be02 SP |
187 | { |
188 | int ret; | |
189 | ||
190 | ret = read_temp_msr(temp); | |
191 | if (!ret) | |
192 | *temp = (stored_tjmax - *temp) * 1000; | |
193 | ||
194 | return ret; | |
195 | } | |
196 | ||
197 | static struct thermal_zone_device_ops proc_thermal_local_ops = { | |
198 | .get_temp = proc_thermal_get_zone_temp, | |
199 | }; | |
200 | ||
f1ba9eb8 | 201 | static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv) |
47c93e6b | 202 | { |
f1ba9eb8 | 203 | int i; |
47c93e6b SP |
204 | acpi_status status; |
205 | struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; | |
206 | union acpi_object *elements, *ppcc; | |
207 | union acpi_object *p; | |
f1ba9eb8 | 208 | int ret = 0; |
47c93e6b | 209 | |
f1ba9eb8 SP |
210 | status = acpi_evaluate_object(proc_priv->adev->handle, "PPCC", |
211 | NULL, &buf); | |
47c93e6b SP |
212 | if (ACPI_FAILURE(status)) |
213 | return -ENODEV; | |
214 | ||
215 | p = buf.pointer; | |
216 | if (!p || (p->type != ACPI_TYPE_PACKAGE)) { | |
f1ba9eb8 | 217 | dev_err(proc_priv->dev, "Invalid PPCC data\n"); |
cc3f71a4 SP |
218 | ret = -EFAULT; |
219 | goto free_buffer; | |
47c93e6b | 220 | } |
f1ba9eb8 | 221 | |
47c93e6b | 222 | if (!p->package.count) { |
f1ba9eb8 | 223 | dev_err(proc_priv->dev, "Invalid PPCC package size\n"); |
cc3f71a4 SP |
224 | ret = -EFAULT; |
225 | goto free_buffer; | |
47c93e6b SP |
226 | } |
227 | ||
47c93e6b SP |
228 | for (i = 0; i < min((int)p->package.count - 1, 2); ++i) { |
229 | elements = &(p->package.elements[i+1]); | |
230 | if (elements->type != ACPI_TYPE_PACKAGE || | |
cc3f71a4 SP |
231 | elements->package.count != 6) { |
232 | ret = -EFAULT; | |
233 | goto free_buffer; | |
234 | } | |
47c93e6b SP |
235 | ppcc = elements->package.elements; |
236 | proc_priv->power_limits[i].index = ppcc[0].integer.value; | |
237 | proc_priv->power_limits[i].min_uw = ppcc[1].integer.value; | |
238 | proc_priv->power_limits[i].max_uw = ppcc[2].integer.value; | |
239 | proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value; | |
240 | proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value; | |
241 | proc_priv->power_limits[i].step_uw = ppcc[5].integer.value; | |
242 | } | |
243 | ||
f1ba9eb8 SP |
244 | free_buffer: |
245 | kfree(buf.pointer); | |
246 | ||
247 | return ret; | |
248 | } | |
249 | ||
250 | #define PROC_POWER_CAPABILITY_CHANGED 0x83 | |
251 | static void proc_thermal_notify(acpi_handle handle, u32 event, void *data) | |
252 | { | |
253 | struct proc_thermal_device *proc_priv = data; | |
254 | ||
255 | if (!proc_priv) | |
256 | return; | |
257 | ||
258 | switch (event) { | |
259 | case PROC_POWER_CAPABILITY_CHANGED: | |
260 | proc_thermal_read_ppcc(proc_priv); | |
261 | int340x_thermal_zone_device_update(proc_priv->int340x_zone); | |
262 | break; | |
263 | default: | |
264 | dev_err(proc_priv->dev, "Unsupported event [0x%x]\n", event); | |
265 | break; | |
266 | } | |
267 | } | |
268 | ||
269 | ||
270 | static int proc_thermal_add(struct device *dev, | |
271 | struct proc_thermal_device **priv) | |
272 | { | |
273 | struct proc_thermal_device *proc_priv; | |
274 | struct acpi_device *adev; | |
275 | acpi_status status; | |
276 | unsigned long long tmp; | |
277 | struct thermal_zone_device_ops *ops = NULL; | |
278 | int ret; | |
279 | ||
280 | adev = ACPI_COMPANION(dev); | |
281 | if (!adev) | |
282 | return -ENODEV; | |
283 | ||
284 | proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL); | |
285 | if (!proc_priv) | |
286 | return -ENOMEM; | |
287 | ||
288 | proc_priv->dev = dev; | |
289 | proc_priv->adev = adev; | |
47c93e6b SP |
290 | *priv = proc_priv; |
291 | ||
f1ba9eb8 SP |
292 | ret = proc_thermal_read_ppcc(proc_priv); |
293 | if (!ret) { | |
294 | ret = sysfs_create_group(&dev->kobj, | |
295 | &power_limit_attribute_group); | |
296 | ||
297 | } | |
1c55be02 | 298 | if (ret) |
f1ba9eb8 | 299 | return ret; |
1c55be02 SP |
300 | |
301 | status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp); | |
302 | if (ACPI_FAILURE(status)) { | |
303 | /* there is no _TMP method, add local method */ | |
304 | stored_tjmax = get_tjmax(); | |
305 | if (stored_tjmax > 0) | |
306 | ops = &proc_thermal_local_ops; | |
307 | } | |
308 | ||
309 | proc_priv->int340x_zone = int340x_thermal_zone_add(adev, ops); | |
310 | if (IS_ERR(proc_priv->int340x_zone)) { | |
1c55be02 | 311 | ret = PTR_ERR(proc_priv->int340x_zone); |
f1ba9eb8 | 312 | goto remove_group; |
1c55be02 SP |
313 | } else |
314 | ret = 0; | |
cc3f71a4 | 315 | |
f1ba9eb8 SP |
316 | ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, |
317 | proc_thermal_notify, | |
318 | (void *)proc_priv); | |
319 | if (ret) | |
320 | goto remove_zone; | |
321 | ||
322 | return 0; | |
323 | ||
324 | remove_zone: | |
325 | int340x_thermal_zone_remove(proc_priv->int340x_zone); | |
326 | remove_group: | |
327 | sysfs_remove_group(&proc_priv->dev->kobj, | |
328 | &power_limit_attribute_group); | |
cc3f71a4 SP |
329 | |
330 | return ret; | |
47c93e6b SP |
331 | } |
332 | ||
4aa971bb | 333 | static void proc_thermal_remove(struct proc_thermal_device *proc_priv) |
47c93e6b | 334 | { |
f1ba9eb8 SP |
335 | acpi_remove_notify_handler(proc_priv->adev->handle, |
336 | ACPI_DEVICE_NOTIFY, proc_thermal_notify); | |
1c55be02 | 337 | int340x_thermal_zone_remove(proc_priv->int340x_zone); |
47c93e6b SP |
338 | sysfs_remove_group(&proc_priv->dev->kobj, |
339 | &power_limit_attribute_group); | |
340 | } | |
341 | ||
342 | static int int3401_add(struct platform_device *pdev) | |
343 | { | |
344 | struct proc_thermal_device *proc_priv; | |
345 | int ret; | |
346 | ||
347 | if (proc_thermal_emum_mode == PROC_THERMAL_PCI) { | |
348 | dev_err(&pdev->dev, "error: enumerated as PCI dev\n"); | |
349 | return -ENODEV; | |
350 | } | |
351 | ||
352 | ret = proc_thermal_add(&pdev->dev, &proc_priv); | |
353 | if (ret) | |
354 | return ret; | |
355 | ||
356 | platform_set_drvdata(pdev, proc_priv); | |
357 | proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV; | |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | static int int3401_remove(struct platform_device *pdev) | |
363 | { | |
364 | proc_thermal_remove(platform_get_drvdata(pdev)); | |
365 | ||
366 | return 0; | |
367 | } | |
368 | ||
4d0dd6c1 SP |
369 | static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid) |
370 | { | |
371 | struct proc_thermal_device *proc_priv; | |
372 | struct pci_dev *pdev = devid; | |
373 | ||
374 | proc_priv = pci_get_drvdata(pdev); | |
375 | ||
376 | intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts); | |
377 | ||
378 | return IRQ_HANDLED; | |
379 | } | |
380 | ||
47c93e6b SP |
381 | static int proc_thermal_pci_probe(struct pci_dev *pdev, |
382 | const struct pci_device_id *unused) | |
383 | { | |
384 | struct proc_thermal_device *proc_priv; | |
385 | int ret; | |
386 | ||
387 | if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { | |
388 | dev_err(&pdev->dev, "error: enumerated as platform dev\n"); | |
389 | return -ENODEV; | |
390 | } | |
391 | ||
392 | ret = pci_enable_device(pdev); | |
393 | if (ret < 0) { | |
394 | dev_err(&pdev->dev, "error: could not enable device\n"); | |
395 | return ret; | |
396 | } | |
397 | ||
398 | ret = proc_thermal_add(&pdev->dev, &proc_priv); | |
399 | if (ret) { | |
400 | pci_disable_device(pdev); | |
401 | return ret; | |
402 | } | |
403 | ||
404 | pci_set_drvdata(pdev, proc_priv); | |
405 | proc_thermal_emum_mode = PROC_THERMAL_PCI; | |
406 | ||
4d0dd6c1 SP |
407 | if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) { |
408 | /* | |
409 | * Enumerate additional DTS sensors available via IOSF. | |
410 | * But we are not treating as a failure condition, if | |
411 | * there are no aux DTSs enabled or fails. This driver | |
412 | * already exposes sensors, which can be accessed via | |
413 | * ACPI/MSR. So we don't want to fail for auxiliary DTSs. | |
414 | */ | |
415 | proc_priv->soc_dts = intel_soc_dts_iosf_init( | |
416 | INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0); | |
417 | ||
418 | if (proc_priv->soc_dts && pdev->irq) { | |
419 | ret = pci_enable_msi(pdev); | |
420 | if (!ret) { | |
421 | ret = request_threaded_irq(pdev->irq, NULL, | |
422 | proc_thermal_pci_msi_irq, | |
423 | IRQF_ONESHOT, "proc_thermal", | |
424 | pdev); | |
425 | if (ret) { | |
426 | intel_soc_dts_iosf_exit( | |
427 | proc_priv->soc_dts); | |
428 | pci_disable_msi(pdev); | |
429 | proc_priv->soc_dts = NULL; | |
430 | } | |
431 | } | |
432 | } else | |
433 | dev_err(&pdev->dev, "No auxiliary DTSs enabled\n"); | |
434 | } | |
435 | ||
47c93e6b SP |
436 | return 0; |
437 | } | |
438 | ||
439 | static void proc_thermal_pci_remove(struct pci_dev *pdev) | |
440 | { | |
4d0dd6c1 SP |
441 | struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev); |
442 | ||
443 | if (proc_priv->soc_dts) { | |
444 | intel_soc_dts_iosf_exit(proc_priv->soc_dts); | |
445 | if (pdev->irq) { | |
446 | free_irq(pdev->irq, pdev); | |
447 | pci_disable_msi(pdev); | |
448 | } | |
449 | } | |
450 | proc_thermal_remove(proc_priv); | |
47c93e6b SP |
451 | pci_disable_device(pdev); |
452 | } | |
453 | ||
454 | static const struct pci_device_id proc_thermal_pci_ids[] = { | |
455 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)}, | |
456 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)}, | |
383b4d60 | 457 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)}, |
47c93e6b | 458 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)}, |
20bbfaf7 AW |
459 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)}, |
460 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)}, | |
461 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)}, | |
462 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)}, | |
47c93e6b SP |
463 | { 0, }, |
464 | }; | |
465 | ||
466 | MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids); | |
467 | ||
468 | static struct pci_driver proc_thermal_pci_driver = { | |
469 | .name = "proc_thermal", | |
470 | .probe = proc_thermal_pci_probe, | |
471 | .remove = proc_thermal_pci_remove, | |
472 | .id_table = proc_thermal_pci_ids, | |
473 | }; | |
474 | ||
475 | static const struct acpi_device_id int3401_device_ids[] = { | |
476 | {"INT3401", 0}, | |
477 | {"", 0}, | |
478 | }; | |
479 | MODULE_DEVICE_TABLE(acpi, int3401_device_ids); | |
480 | ||
481 | static struct platform_driver int3401_driver = { | |
482 | .probe = int3401_add, | |
483 | .remove = int3401_remove, | |
484 | .driver = { | |
485 | .name = "int3401 thermal", | |
486 | .acpi_match_table = int3401_device_ids, | |
487 | }, | |
488 | }; | |
489 | ||
490 | static int __init proc_thermal_init(void) | |
491 | { | |
492 | int ret; | |
493 | ||
494 | ret = platform_driver_register(&int3401_driver); | |
495 | if (ret) | |
496 | return ret; | |
497 | ||
498 | ret = pci_register_driver(&proc_thermal_pci_driver); | |
499 | ||
500 | return ret; | |
501 | } | |
502 | ||
503 | static void __exit proc_thermal_exit(void) | |
504 | { | |
505 | platform_driver_unregister(&int3401_driver); | |
506 | pci_unregister_driver(&proc_thermal_pci_driver); | |
507 | } | |
508 | ||
509 | module_init(proc_thermal_init); | |
510 | module_exit(proc_thermal_exit); | |
511 | ||
512 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); | |
513 | MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver"); | |
514 | MODULE_LICENSE("GPL v2"); |