aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLan Tianyu <tianyu.lan@intel.com>2014-09-03 03:13:30 -0400
committerZhang Rui <rui.zhang@intel.com>2014-10-10 21:35:36 -0400
commit4384b8fe162d8aa03905d02073707bcf364cc7ce (patch)
tree4aee278cd1f64f79676f23748a760387c9261290
parent77e337c6e23e3b9d22e09ffec202a80f755a54c2 (diff)
Thermal: introduce int3403 thermal driver
ACPI INT3403 device object can be used to retrieve temperature date from temperature sensors present in the system, and to expose device' performance control. The previous INT3403 thermal driver supports temperature reporting only, thus remove it and introduce this new & enhanced one. Signed-off-by: Lan Tianyu <tianyu.lan@intel.com> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
-rw-r--r--drivers/thermal/Kconfig15
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/int3403_thermal.c296
-rw-r--r--drivers/thermal/int340x_thermal/Makefile1
-rw-r--r--drivers/thermal/int340x_thermal/int3403_thermal.c477
5 files changed, 478 insertions, 312 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index b34c5f54fc83..6f93e5c4e1f2 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -207,21 +207,6 @@ config X86_PKG_TEMP_THERMAL
207 two trip points which can be set by user to get notifications via thermal 207 two trip points which can be set by user to get notifications via thermal
208 notification methods. 208 notification methods.
209 209
210config ACPI_INT3403_THERMAL
211 tristate "ACPI INT3403 thermal driver"
212 depends on X86 && ACPI
213 help
214 Newer laptops and tablets that use ACPI may have thermal sensors
215 outside the core CPU/SOC for thermal safety reasons. These
216 temperature sensors are also exposed for the OS to use via the so
217 called INT3403 ACPI object. This driver will, on devices that have
218 such sensors, expose the temperature information from these sensors
219 to userspace via the normal thermal framework. This means that a wide
220 range of applications and GUI widgets can show this information to
221 the user or use this information for making decisions. For example,
222 the Intel Thermal Daemon can use this information to allow the user
223 to select his laptop to run without turning on the fans.
224
225config INTEL_SOC_DTS_THERMAL 210config INTEL_SOC_DTS_THERMAL
226 tristate "Intel SoCs DTS thermal driver" 211 tristate "Intel SoCs DTS thermal driver"
227 depends on X86 && IOSF_MBI 212 depends on X86 && IOSF_MBI
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 216503eaa232..39b85b5b45fc 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -31,6 +31,5 @@ obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
31obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o 31obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o
32obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o 32obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
33obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ 33obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
34obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o
35obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ 34obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
36obj-$(CONFIG_ST_THERMAL) += st/ 35obj-$(CONFIG_ST_THERMAL) += st/
diff --git a/drivers/thermal/int3403_thermal.c b/drivers/thermal/int3403_thermal.c
deleted file mode 100644
index 17554eeb3953..000000000000
--- a/drivers/thermal/int3403_thermal.c
+++ /dev/null
@@ -1,296 +0,0 @@
1/*
2 * ACPI INT3403 thermal driver
3 * Copyright (c) 2013, 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/types.h>
19#include <linux/acpi.h>
20#include <linux/thermal.h>
21
22#define INT3403_TYPE_SENSOR 0x03
23#define INT3403_PERF_CHANGED_EVENT 0x80
24#define INT3403_THERMAL_EVENT 0x90
25
26#define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100)
27#define KELVIN_OFFSET 2732
28#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off))
29
30#define ACPI_INT3403_CLASS "int3403"
31#define ACPI_INT3403_FILE_STATE "state"
32
33struct int3403_sensor {
34 struct thermal_zone_device *tzone;
35 unsigned long *thresholds;
36 unsigned long crit_temp;
37 int crit_trip_id;
38 unsigned long psv_temp;
39 int psv_trip_id;
40};
41
42static int sys_get_curr_temp(struct thermal_zone_device *tzone,
43 unsigned long *temp)
44{
45 struct acpi_device *device = tzone->devdata;
46 unsigned long long tmp;
47 acpi_status status;
48
49 status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp);
50 if (ACPI_FAILURE(status))
51 return -EIO;
52
53 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET);
54
55 return 0;
56}
57
58static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
59 int trip, unsigned long *temp)
60{
61 struct acpi_device *device = tzone->devdata;
62 unsigned long long hyst;
63 acpi_status status;
64
65 status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst);
66 if (ACPI_FAILURE(status))
67 return -EIO;
68
69 /*
70 * Thermal hysteresis represents a temperature difference.
71 * Kelvin and Celsius have same degree size. So the
72 * conversion here between tenths of degree Kelvin unit
73 * and Milli-Celsius unit is just to multiply 100.
74 */
75 *temp = hyst * 100;
76
77 return 0;
78}
79
80static int sys_get_trip_temp(struct thermal_zone_device *tzone,
81 int trip, unsigned long *temp)
82{
83 struct acpi_device *device = tzone->devdata;
84 struct int3403_sensor *obj = acpi_driver_data(device);
85
86 if (trip == obj->crit_trip_id)
87 *temp = obj->crit_temp;
88 else if (trip == obj->psv_trip_id)
89 *temp = obj->psv_temp;
90 else {
91 /*
92 * get_trip_temp is a mandatory callback but
93 * PATx method doesn't return any value, so return
94 * cached value, which was last set from user space.
95 */
96 *temp = obj->thresholds[trip];
97 }
98
99 return 0;
100}
101
102static int sys_get_trip_type(struct thermal_zone_device *thermal,
103 int trip, enum thermal_trip_type *type)
104{
105 struct acpi_device *device = thermal->devdata;
106 struct int3403_sensor *obj = acpi_driver_data(device);
107
108 /* Mandatory callback, may not mean much here */
109 if (trip == obj->crit_trip_id)
110 *type = THERMAL_TRIP_CRITICAL;
111 else
112 *type = THERMAL_TRIP_PASSIVE;
113
114 return 0;
115}
116
117int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip,
118 unsigned long temp)
119{
120 struct acpi_device *device = tzone->devdata;
121 acpi_status status;
122 char name[10];
123 int ret = 0;
124 struct int3403_sensor *obj = acpi_driver_data(device);
125
126 snprintf(name, sizeof(name), "PAT%d", trip);
127 if (acpi_has_method(device->handle, name)) {
128 status = acpi_execute_simple_method(device->handle, name,
129 MILLI_CELSIUS_TO_DECI_KELVIN(temp,
130 KELVIN_OFFSET));
131 if (ACPI_FAILURE(status))
132 ret = -EIO;
133 else
134 obj->thresholds[trip] = temp;
135 } else {
136 ret = -EIO;
137 dev_err(&device->dev, "sys_set_trip_temp: method not found\n");
138 }
139
140 return ret;
141}
142
143static struct thermal_zone_device_ops tzone_ops = {
144 .get_temp = sys_get_curr_temp,
145 .get_trip_temp = sys_get_trip_temp,
146 .get_trip_type = sys_get_trip_type,
147 .set_trip_temp = sys_set_trip_temp,
148 .get_trip_hyst = sys_get_trip_hyst,
149};
150
151static void acpi_thermal_notify(struct acpi_device *device, u32 event)
152{
153 struct int3403_sensor *obj;
154
155 if (!device)
156 return;
157
158 obj = acpi_driver_data(device);
159 if (!obj)
160 return;
161
162 switch (event) {
163 case INT3403_PERF_CHANGED_EVENT:
164 break;
165 case INT3403_THERMAL_EVENT:
166 thermal_zone_device_update(obj->tzone);
167 break;
168 default:
169 dev_err(&device->dev, "Unsupported event [0x%x]\n", event);
170 break;
171 }
172}
173
174static int sys_get_trip_crt(struct acpi_device *device, unsigned long *temp)
175{
176 unsigned long long crt;
177 acpi_status status;
178
179 status = acpi_evaluate_integer(device->handle, "_CRT", NULL, &crt);
180 if (ACPI_FAILURE(status))
181 return -EIO;
182
183 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(crt, KELVIN_OFFSET);
184
185 return 0;
186}
187
188static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp)
189{
190 unsigned long long psv;
191 acpi_status status;
192
193 status = acpi_evaluate_integer(device->handle, "_PSV", NULL, &psv);
194 if (ACPI_FAILURE(status))
195 return -EIO;
196
197 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(psv, KELVIN_OFFSET);
198
199 return 0;
200}
201
202static int acpi_int3403_add(struct acpi_device *device)
203{
204 int result = 0;
205 unsigned long long ptyp;
206 acpi_status status;
207 struct int3403_sensor *obj;
208 unsigned long long trip_cnt;
209 int trip_mask = 0;
210
211 if (!device)
212 return -EINVAL;
213
214 status = acpi_evaluate_integer(device->handle, "PTYP", NULL, &ptyp);
215 if (ACPI_FAILURE(status))
216 return -EINVAL;
217
218 if (ptyp != INT3403_TYPE_SENSOR)
219 return -EINVAL;
220
221 obj = devm_kzalloc(&device->dev, sizeof(*obj), GFP_KERNEL);
222 if (!obj)
223 return -ENOMEM;
224
225 device->driver_data = obj;
226
227 status = acpi_evaluate_integer(device->handle, "PATC", NULL,
228 &trip_cnt);
229 if (ACPI_FAILURE(status))
230 trip_cnt = 0;
231
232 if (trip_cnt) {
233 /* We have to cache, thresholds can't be readback */
234 obj->thresholds = devm_kzalloc(&device->dev,
235 sizeof(*obj->thresholds) * trip_cnt,
236 GFP_KERNEL);
237 if (!obj->thresholds)
238 return -ENOMEM;
239 trip_mask = BIT(trip_cnt) - 1;
240 }
241
242 obj->psv_trip_id = -1;
243 if (!sys_get_trip_psv(device, &obj->psv_temp))
244 obj->psv_trip_id = trip_cnt++;
245
246 obj->crit_trip_id = -1;
247 if (!sys_get_trip_crt(device, &obj->crit_temp))
248 obj->crit_trip_id = trip_cnt++;
249
250 obj->tzone = thermal_zone_device_register(acpi_device_bid(device),
251 trip_cnt, trip_mask, device, &tzone_ops,
252 NULL, 0, 0);
253 if (IS_ERR(obj->tzone)) {
254 result = PTR_ERR(obj->tzone);
255 return result;
256 }
257
258 strcpy(acpi_device_name(device), "INT3403");
259 strcpy(acpi_device_class(device), ACPI_INT3403_CLASS);
260
261 return 0;
262}
263
264static int acpi_int3403_remove(struct acpi_device *device)
265{
266 struct int3403_sensor *obj;
267
268 obj = acpi_driver_data(device);
269 thermal_zone_device_unregister(obj->tzone);
270
271 return 0;
272}
273
274ACPI_MODULE_NAME("int3403");
275static const struct acpi_device_id int3403_device_ids[] = {
276 {"INT3403", 0},
277 {"", 0},
278};
279MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
280
281static struct acpi_driver acpi_int3403_driver = {
282 .name = "INT3403",
283 .class = ACPI_INT3403_CLASS,
284 .ids = int3403_device_ids,
285 .ops = {
286 .add = acpi_int3403_add,
287 .remove = acpi_int3403_remove,
288 .notify = acpi_thermal_notify,
289 },
290};
291
292module_acpi_driver(acpi_int3403_driver);
293
294MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
295MODULE_LICENSE("GPL v2");
296MODULE_DESCRIPTION("ACPI INT3403 thermal driver");
diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile
index 67c98fd24200..c4a5c50d7ee4 100644
--- a/drivers/thermal/int340x_thermal/Makefile
+++ b/drivers/thermal/int340x_thermal/Makefile
@@ -1,2 +1,3 @@
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
diff --git a/drivers/thermal/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c
new file mode 100644
index 000000000000..d20dba986f0f
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int3403_thermal.c
@@ -0,0 +1,477 @@
1/*
2 * ACPI INT3403 thermal driver
3 * Copyright (c) 2013, 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/types.h>
19#include <linux/acpi.h>
20#include <linux/thermal.h>
21#include <linux/platform_device.h>
22
23#define INT3403_TYPE_SENSOR 0x03
24#define INT3403_TYPE_CHARGER 0x0B
25#define INT3403_TYPE_BATTERY 0x0C
26#define INT3403_PERF_CHANGED_EVENT 0x80
27#define INT3403_THERMAL_EVENT 0x90
28
29#define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100)
30#define KELVIN_OFFSET 2732
31#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off))
32
33struct int3403_sensor {
34 struct thermal_zone_device *tzone;
35 unsigned long *thresholds;
36 unsigned long crit_temp;
37 int crit_trip_id;
38 unsigned long psv_temp;
39 int psv_trip_id;
40
41};
42
43struct int3403_performance_state {
44 u64 performance;
45 u64 power;
46 u64 latency;
47 u64 linear;
48 u64 control;
49 u64 raw_performace;
50 char *raw_unit;
51 int reserved;
52};
53
54struct int3403_cdev {
55 struct thermal_cooling_device *cdev;
56 unsigned long max_state;
57};
58
59struct int3403_priv {
60 struct platform_device *pdev;
61 struct acpi_device *adev;
62 unsigned long long type;
63 void *priv;
64};
65
66static int sys_get_curr_temp(struct thermal_zone_device *tzone,
67 unsigned long *temp)
68{
69 struct int3403_priv *priv = tzone->devdata;
70 struct acpi_device *device = priv->adev;
71 unsigned long long tmp;
72 acpi_status status;
73
74 status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp);
75 if (ACPI_FAILURE(status))
76 return -EIO;
77
78 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET);
79
80 return 0;
81}
82
83static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
84 int trip, unsigned long *temp)
85{
86 struct int3403_priv *priv = tzone->devdata;
87 struct acpi_device *device = priv->adev;
88 unsigned long long hyst;
89 acpi_status status;
90
91 status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst);
92 if (ACPI_FAILURE(status))
93 return -EIO;
94
95 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(hyst, KELVIN_OFFSET);
96
97 return 0;
98}
99
100static int sys_get_trip_temp(struct thermal_zone_device *tzone,
101 int trip, unsigned long *temp)
102{
103 struct int3403_priv *priv = tzone->devdata;
104 struct int3403_sensor *obj = priv->priv;
105
106 if (priv->type != INT3403_TYPE_SENSOR || !obj)
107 return -EINVAL;
108
109 if (trip == obj->crit_trip_id)
110 *temp = obj->crit_temp;
111 else if (trip == obj->psv_trip_id)
112 *temp = obj->psv_temp;
113 else {
114 /*
115 * get_trip_temp is a mandatory callback but
116 * PATx method doesn't return any value, so return
117 * cached value, which was last set from user space
118 */
119 *temp = obj->thresholds[trip];
120 }
121
122 return 0;
123}
124
125static int sys_get_trip_type(struct thermal_zone_device *thermal,
126 int trip, enum thermal_trip_type *type)
127{
128 struct int3403_priv *priv = thermal->devdata;
129 struct int3403_sensor *obj = priv->priv;
130
131 /* Mandatory callback, may not mean much here */
132 if (trip == obj->crit_trip_id)
133 *type = THERMAL_TRIP_CRITICAL;
134 else
135 *type = THERMAL_TRIP_PASSIVE;
136
137 return 0;
138}
139
140int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip,
141 unsigned long temp)
142{
143 struct int3403_priv *priv = tzone->devdata;
144 struct acpi_device *device = priv->adev;
145 struct int3403_sensor *obj = priv->priv;
146 acpi_status status;
147 char name[10];
148 int ret = 0;
149
150 snprintf(name, sizeof(name), "PAT%d", trip);
151 if (acpi_has_method(device->handle, name)) {
152 status = acpi_execute_simple_method(device->handle, name,
153 MILLI_CELSIUS_TO_DECI_KELVIN(temp,
154 KELVIN_OFFSET));
155 if (ACPI_FAILURE(status))
156 ret = -EIO;
157 else
158 obj->thresholds[trip] = temp;
159 } else {
160 ret = -EIO;
161 dev_err(&device->dev, "sys_set_trip_temp: method not found\n");
162 }
163
164 return ret;
165}
166
167static struct thermal_zone_device_ops tzone_ops = {
168 .get_temp = sys_get_curr_temp,
169 .get_trip_temp = sys_get_trip_temp,
170 .get_trip_type = sys_get_trip_type,
171 .set_trip_temp = sys_set_trip_temp,
172 .get_trip_hyst = sys_get_trip_hyst,
173};
174
175static struct thermal_zone_params int3403_thermal_params = {
176 .governor_name = "user_space",
177 .no_hwmon = true,
178};
179
180static void int3403_notify(acpi_handle handle,
181 u32 event, void *data)
182{
183 struct int3403_priv *priv = data;
184 struct int3403_sensor *obj;
185
186 if (!priv)
187 return;
188
189 obj = priv->priv;
190 if (priv->type != INT3403_TYPE_SENSOR || !obj)
191 return;
192
193 switch (event) {
194 case INT3403_PERF_CHANGED_EVENT:
195 break;
196 case INT3403_THERMAL_EVENT:
197 thermal_zone_device_update(obj->tzone);
198 break;
199 default:
200 dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
201 break;
202 }
203}
204
205static int sys_get_trip_crt(struct acpi_device *device, unsigned long *temp)
206{
207 unsigned long long crt;
208 acpi_status status;
209
210 status = acpi_evaluate_integer(device->handle, "_CRT", NULL, &crt);
211 if (ACPI_FAILURE(status))
212 return -EIO;
213
214 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(crt, KELVIN_OFFSET);
215
216 return 0;
217}
218
219static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp)
220{
221 unsigned long long psv;
222 acpi_status status;
223
224 status = acpi_evaluate_integer(device->handle, "_PSV", NULL, &psv);
225 if (ACPI_FAILURE(status))
226 return -EIO;
227
228 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(psv, KELVIN_OFFSET);
229
230 return 0;
231}
232
233static int int3403_sensor_add(struct int3403_priv *priv)
234{
235 int result = 0;
236 acpi_status status;
237 struct int3403_sensor *obj;
238 unsigned long long trip_cnt;
239 int trip_mask = 0;
240
241 obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
242 if (!obj)
243 return -ENOMEM;
244
245 priv->priv = obj;
246
247 status = acpi_evaluate_integer(priv->adev->handle, "PATC", NULL,
248 &trip_cnt);
249 if (ACPI_FAILURE(status))
250 trip_cnt = 0;
251
252 if (trip_cnt) {
253 /* We have to cache, thresholds can't be readback */
254 obj->thresholds = devm_kzalloc(&priv->pdev->dev,
255 sizeof(*obj->thresholds) * trip_cnt,
256 GFP_KERNEL);
257 if (!obj->thresholds) {
258 result = -ENOMEM;
259 goto err_free_obj;
260 }
261 trip_mask = BIT(trip_cnt) - 1;
262 }
263
264 obj->psv_trip_id = -1;
265 if (!sys_get_trip_psv(priv->adev, &obj->psv_temp))
266 obj->psv_trip_id = trip_cnt++;
267
268 obj->crit_trip_id = -1;
269 if (!sys_get_trip_crt(priv->adev, &obj->crit_temp))
270 obj->crit_trip_id = trip_cnt++;
271
272 obj->tzone = thermal_zone_device_register(acpi_device_bid(priv->adev),
273 trip_cnt, trip_mask, priv, &tzone_ops,
274 &int3403_thermal_params, 0, 0);
275 if (IS_ERR(obj->tzone)) {
276 result = PTR_ERR(obj->tzone);
277 obj->tzone = NULL;
278 goto err_free_obj;
279 }
280
281 result = acpi_install_notify_handler(priv->adev->handle,
282 ACPI_DEVICE_NOTIFY, int3403_notify,
283 (void *)priv);
284 if (result)
285 goto err_free_obj;
286
287 return 0;
288
289 err_free_obj:
290 if (obj->tzone)
291 thermal_zone_device_unregister(obj->tzone);
292 return result;
293}
294
295static int int3403_sensor_remove(struct int3403_priv *priv)
296{
297 struct int3403_sensor *obj = priv->priv;
298
299 thermal_zone_device_unregister(obj->tzone);
300 return 0;
301}
302
303/* INT3403 Cooling devices */
304static int int3403_get_max_state(struct thermal_cooling_device *cdev,
305 unsigned long *state)
306{
307 struct int3403_priv *priv = cdev->devdata;
308 struct int3403_cdev *obj = priv->priv;
309
310 *state = obj->max_state;
311 return 0;
312}
313
314static int int3403_get_cur_state(struct thermal_cooling_device *cdev,
315 unsigned long *state)
316{
317 struct int3403_priv *priv = cdev->devdata;
318 unsigned long long level;
319 acpi_status status;
320
321 status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level);
322 if (ACPI_SUCCESS(status)) {
323 *state = level;
324 return 0;
325 } else
326 return -EINVAL;
327}
328
329static int
330int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
331{
332 struct int3403_priv *priv = cdev->devdata;
333 acpi_status status;
334
335 status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state);
336 if (ACPI_SUCCESS(status))
337 return 0;
338 else
339 return -EINVAL;
340}
341
342static const struct thermal_cooling_device_ops int3403_cooling_ops = {
343 .get_max_state = int3403_get_max_state,
344 .get_cur_state = int3403_get_cur_state,
345 .set_cur_state = int3403_set_cur_state,
346};
347
348static int int3403_cdev_add(struct int3403_priv *priv)
349{
350 int result = 0;
351 acpi_status status;
352 struct int3403_cdev *obj;
353 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
354 union acpi_object *p;
355
356 obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
357 if (!obj)
358 return -ENOMEM;
359
360 status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf);
361 if (ACPI_FAILURE(status))
362 return -ENODEV;
363
364 p = buf.pointer;
365 if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
366 printk(KERN_WARNING "Invalid PPSS data\n");
367 return -EFAULT;
368 }
369
370 obj->max_state = p->package.count - 1;
371 obj->cdev =
372 thermal_cooling_device_register(acpi_device_bid(priv->adev),
373 priv, &int3403_cooling_ops);
374 if (IS_ERR(obj->cdev))
375 result = PTR_ERR(obj->cdev);
376
377 priv->priv = obj;
378
379 /* TODO: add ACPI notification support */
380
381 return result;
382}
383
384static int int3403_cdev_remove(struct int3403_priv *priv)
385{
386 struct int3403_cdev *obj = priv->priv;
387
388 thermal_cooling_device_unregister(obj->cdev);
389 return 0;
390}
391
392static int int3403_add(struct platform_device *pdev)
393{
394 struct int3403_priv *priv;
395 int result = 0;
396 acpi_status status;
397
398 priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv),
399 GFP_KERNEL);
400 if (!priv)
401 return -ENOMEM;
402
403 priv->pdev = pdev;
404 priv->adev = ACPI_COMPANION(&(pdev->dev));
405 if (!priv->adev) {
406 result = -EINVAL;
407 goto err;
408 }
409
410 status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
411 NULL, &priv->type);
412 if (ACPI_FAILURE(status)) {
413 result = -EINVAL;
414 goto err;
415 }
416
417 platform_set_drvdata(pdev, priv);
418 switch (priv->type) {
419 case INT3403_TYPE_SENSOR:
420 result = int3403_sensor_add(priv);
421 break;
422 case INT3403_TYPE_CHARGER:
423 case INT3403_TYPE_BATTERY:
424 result = int3403_cdev_add(priv);
425 break;
426 default:
427 result = -EINVAL;
428 }
429
430 if (result)
431 goto err;
432 return result;
433
434err:
435 return result;
436}
437
438static int int3403_remove(struct platform_device *pdev)
439{
440 struct int3403_priv *priv = platform_get_drvdata(pdev);
441
442 switch (priv->type) {
443 case INT3403_TYPE_SENSOR:
444 int3403_sensor_remove(priv);
445 break;
446 case INT3403_TYPE_CHARGER:
447 case INT3403_TYPE_BATTERY:
448 int3403_cdev_remove(priv);
449 break;
450 default:
451 break;
452 }
453
454 return 0;
455}
456
457static const struct acpi_device_id int3403_device_ids[] = {
458 {"INT3403", 0},
459 {"", 0},
460};
461MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
462
463static struct platform_driver int3403_driver = {
464 .probe = int3403_add,
465 .remove = int3403_remove,
466 .driver = {
467 .name = "int3403 thermal",
468 .owner = THIS_MODULE,
469 .acpi_match_table = int3403_device_ids,
470 },
471};
472
473module_platform_driver(int3403_driver);
474
475MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
476MODULE_LICENSE("GPL v2");
477MODULE_DESCRIPTION("ACPI INT3403 thermal driver");