aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSrinivas Pandruvada <srinivas.pandruvada@linux.intel.com>2014-12-09 19:47:17 -0500
committerZhang Rui <rui.zhang@intel.com>2014-12-23 21:37:35 -0500
commit47c93e6b3f37bf2b709fb107f3db586e39b8fd56 (patch)
tree3d66b020d8c28f05d53a76ac0fd6ddba3c6d3bd2
parent0716b0ff0a11ca96e925bfac43a82eb88f75c928 (diff)
thermal: int340x: Introduce processor reporting device
The Int340x thermal provides a processor thermal device, which is used to control processor thermal states. These devices are either reported as a PCI device or an ACPI device. This device provides power limits, control states and optional temperature. This change implements minimal requirements to expose processor power limits which can be used during thermal power limiting. Power limits are exposed via an attribute group called "power_limits" under the device. The exported attributes are: power_limit_0_max_uw power_limit_1_max_uw power_limit_0_min_uw power_limit_1_min_uw power_limit_0_tmin_us power_limit_1_tmin_us power_limit_0_tmax_us power_limit_1_tmax_us power_limit_0_step_uw power_limit_1_step_uw Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
-rw-r--r--drivers/thermal/int340x_thermal/Makefile1
-rw-r--r--drivers/thermal/int340x_thermal/processor_thermal_device.c298
2 files changed, 299 insertions, 0 deletions
diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile
index ffe40bffaf1a..d4413698a85f 100644
--- a/drivers/thermal/int340x_thermal/Makefile
+++ b/drivers/thermal/int340x_thermal/Makefile
@@ -1,4 +1,5 @@
1obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o 1obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o
2obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o 2obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o
3obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o 3obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o
4obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o
4obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o 5obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o
diff --git a/drivers/thermal/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c
new file mode 100644
index 000000000000..f83c55b09e52
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/processor_thermal_device.c
@@ -0,0 +1,298 @@
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>
19#include <linux/platform_device.h>
20#include <linux/acpi.h>
21
22/* Broadwell-U/HSB thermal reporting device */
23#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
24#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
25
26/* Braswell thermal reporting device */
27#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
28
29struct power_config {
30 u32 index;
31 u32 min_uw;
32 u32 max_uw;
33 u32 tmin_us;
34 u32 tmax_us;
35 u32 step_uw;
36};
37
38struct proc_thermal_device {
39 struct device *dev;
40 struct acpi_device *adev;
41 struct power_config power_limits[2];
42};
43
44enum proc_thermal_emum_mode_type {
45 PROC_THERMAL_NONE,
46 PROC_THERMAL_PCI,
47 PROC_THERMAL_PLATFORM_DEV
48};
49
50/*
51 * We can have only one type of enumeration, PCI or Platform,
52 * not both. So we don't need instance specific data.
53 */
54static enum proc_thermal_emum_mode_type proc_thermal_emum_mode =
55 PROC_THERMAL_NONE;
56
57#define POWER_LIMIT_SHOW(index, suffix) \
58static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
59 struct device_attribute *attr, \
60 char *buf) \
61{ \
62 struct pci_dev *pci_dev; \
63 struct platform_device *pdev; \
64 struct proc_thermal_device *proc_dev; \
65\
66 if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { \
67 pdev = to_platform_device(dev); \
68 proc_dev = platform_get_drvdata(pdev); \
69 } else { \
70 pci_dev = to_pci_dev(dev); \
71 proc_dev = pci_get_drvdata(pci_dev); \
72 } \
73 return sprintf(buf, "%lu\n",\
74 (unsigned long)proc_dev->power_limits[index].suffix * 1000); \
75}
76
77POWER_LIMIT_SHOW(0, min_uw)
78POWER_LIMIT_SHOW(0, max_uw)
79POWER_LIMIT_SHOW(0, step_uw)
80POWER_LIMIT_SHOW(0, tmin_us)
81POWER_LIMIT_SHOW(0, tmax_us)
82
83POWER_LIMIT_SHOW(1, min_uw)
84POWER_LIMIT_SHOW(1, max_uw)
85POWER_LIMIT_SHOW(1, step_uw)
86POWER_LIMIT_SHOW(1, tmin_us)
87POWER_LIMIT_SHOW(1, tmax_us)
88
89static DEVICE_ATTR_RO(power_limit_0_min_uw);
90static DEVICE_ATTR_RO(power_limit_0_max_uw);
91static DEVICE_ATTR_RO(power_limit_0_step_uw);
92static DEVICE_ATTR_RO(power_limit_0_tmin_us);
93static DEVICE_ATTR_RO(power_limit_0_tmax_us);
94
95static DEVICE_ATTR_RO(power_limit_1_min_uw);
96static DEVICE_ATTR_RO(power_limit_1_max_uw);
97static DEVICE_ATTR_RO(power_limit_1_step_uw);
98static DEVICE_ATTR_RO(power_limit_1_tmin_us);
99static DEVICE_ATTR_RO(power_limit_1_tmax_us);
100
101static struct attribute *power_limit_attrs[] = {
102 &dev_attr_power_limit_0_min_uw.attr,
103 &dev_attr_power_limit_1_min_uw.attr,
104 &dev_attr_power_limit_0_max_uw.attr,
105 &dev_attr_power_limit_1_max_uw.attr,
106 &dev_attr_power_limit_0_step_uw.attr,
107 &dev_attr_power_limit_1_step_uw.attr,
108 &dev_attr_power_limit_0_tmin_us.attr,
109 &dev_attr_power_limit_1_tmin_us.attr,
110 &dev_attr_power_limit_0_tmax_us.attr,
111 &dev_attr_power_limit_1_tmax_us.attr,
112 NULL
113};
114
115static struct attribute_group power_limit_attribute_group = {
116 .attrs = power_limit_attrs,
117 .name = "power_limits"
118};
119
120static int proc_thermal_add(struct device *dev,
121 struct proc_thermal_device **priv)
122{
123 struct proc_thermal_device *proc_priv;
124 struct acpi_device *adev;
125 acpi_status status;
126 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
127 union acpi_object *elements, *ppcc;
128 union acpi_object *p;
129 int i;
130
131 adev = ACPI_COMPANION(dev);
132
133 status = acpi_evaluate_object(adev->handle, "PPCC", NULL, &buf);
134 if (ACPI_FAILURE(status))
135 return -ENODEV;
136
137 p = buf.pointer;
138 if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
139 dev_err(dev, "Invalid PPCC data\n");
140 return -EFAULT;
141 }
142 if (!p->package.count) {
143 dev_err(dev, "Invalid PPCC package size\n");
144 return -EFAULT;
145 }
146
147 proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL);
148 if (!proc_priv)
149 return -ENOMEM;
150
151 proc_priv->dev = dev;
152 proc_priv->adev = adev;
153
154 for (i = 0; i < min((int)p->package.count - 1, 2); ++i) {
155 elements = &(p->package.elements[i+1]);
156 if (elements->type != ACPI_TYPE_PACKAGE ||
157 elements->package.count != 6)
158 return -EFAULT;
159
160 ppcc = elements->package.elements;
161 proc_priv->power_limits[i].index = ppcc[0].integer.value;
162 proc_priv->power_limits[i].min_uw = ppcc[1].integer.value;
163 proc_priv->power_limits[i].max_uw = ppcc[2].integer.value;
164 proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value;
165 proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value;
166 proc_priv->power_limits[i].step_uw = ppcc[5].integer.value;
167 }
168
169 *priv = proc_priv;
170
171 return sysfs_create_group(&dev->kobj,
172 &power_limit_attribute_group);
173}
174
175void proc_thermal_remove(struct proc_thermal_device *proc_priv)
176{
177 sysfs_remove_group(&proc_priv->dev->kobj,
178 &power_limit_attribute_group);
179}
180
181static int int3401_add(struct platform_device *pdev)
182{
183 struct proc_thermal_device *proc_priv;
184 int ret;
185
186 if (proc_thermal_emum_mode == PROC_THERMAL_PCI) {
187 dev_err(&pdev->dev, "error: enumerated as PCI dev\n");
188 return -ENODEV;
189 }
190
191 ret = proc_thermal_add(&pdev->dev, &proc_priv);
192 if (ret)
193 return ret;
194
195 platform_set_drvdata(pdev, proc_priv);
196 proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV;
197
198 return 0;
199}
200
201static int int3401_remove(struct platform_device *pdev)
202{
203 proc_thermal_remove(platform_get_drvdata(pdev));
204
205 return 0;
206}
207
208static int proc_thermal_pci_probe(struct pci_dev *pdev,
209 const struct pci_device_id *unused)
210{
211 struct proc_thermal_device *proc_priv;
212 int ret;
213
214 if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) {
215 dev_err(&pdev->dev, "error: enumerated as platform dev\n");
216 return -ENODEV;
217 }
218
219 ret = pci_enable_device(pdev);
220 if (ret < 0) {
221 dev_err(&pdev->dev, "error: could not enable device\n");
222 return ret;
223 }
224
225 ret = proc_thermal_add(&pdev->dev, &proc_priv);
226 if (ret) {
227 pci_disable_device(pdev);
228 return ret;
229 }
230
231 pci_set_drvdata(pdev, proc_priv);
232 proc_thermal_emum_mode = PROC_THERMAL_PCI;
233
234 return 0;
235}
236
237static void proc_thermal_pci_remove(struct pci_dev *pdev)
238{
239 proc_thermal_remove(pci_get_drvdata(pdev));
240 pci_disable_device(pdev);
241}
242
243static const struct pci_device_id proc_thermal_pci_ids[] = {
244 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
245 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
246 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
247 { 0, },
248};
249
250MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
251
252static struct pci_driver proc_thermal_pci_driver = {
253 .name = "proc_thermal",
254 .probe = proc_thermal_pci_probe,
255 .remove = proc_thermal_pci_remove,
256 .id_table = proc_thermal_pci_ids,
257};
258
259static const struct acpi_device_id int3401_device_ids[] = {
260 {"INT3401", 0},
261 {"", 0},
262};
263MODULE_DEVICE_TABLE(acpi, int3401_device_ids);
264
265static struct platform_driver int3401_driver = {
266 .probe = int3401_add,
267 .remove = int3401_remove,
268 .driver = {
269 .name = "int3401 thermal",
270 .acpi_match_table = int3401_device_ids,
271 },
272};
273
274static int __init proc_thermal_init(void)
275{
276 int ret;
277
278 ret = platform_driver_register(&int3401_driver);
279 if (ret)
280 return ret;
281
282 ret = pci_register_driver(&proc_thermal_pci_driver);
283
284 return ret;
285}
286
287static void __exit proc_thermal_exit(void)
288{
289 platform_driver_unregister(&int3401_driver);
290 pci_unregister_driver(&proc_thermal_pci_driver);
291}
292
293module_init(proc_thermal_init);
294module_exit(proc_thermal_exit);
295
296MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
297MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
298MODULE_LICENSE("GPL v2");