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