1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ACPI INT3403 thermal driver |
4 | * Copyright (c) 2013, Intel Corporation. |
5 | */ |
6 | |
7 | #include <linux/kernel.h> |
8 | #include <linux/module.h> |
9 | #include <linux/init.h> |
10 | #include <linux/types.h> |
11 | #include <linux/acpi.h> |
12 | #include <linux/thermal.h> |
13 | #include <linux/platform_device.h> |
14 | #include "int340x_thermal_zone.h" |
15 | |
16 | #define INT3403_TYPE_SENSOR 0x03 |
17 | #define INT3403_TYPE_CHARGER 0x0B |
18 | #define INT3403_TYPE_BATTERY 0x0C |
19 | #define INT3403_PERF_CHANGED_EVENT 0x80 |
20 | #define INT3403_PERF_TRIP_POINT_CHANGED 0x81 |
21 | #define INT3403_THERMAL_EVENT 0x90 |
22 | |
23 | /* Preserved structure for future expandbility */ |
24 | struct int3403_sensor { |
25 | struct int34x_thermal_zone *int340x_zone; |
26 | }; |
27 | |
28 | struct int3403_performance_state { |
29 | u64 performance; |
30 | u64 power; |
31 | u64 latency; |
32 | u64 linear; |
33 | u64 control; |
34 | u64 raw_performace; |
35 | char *raw_unit; |
36 | int reserved; |
37 | }; |
38 | |
39 | struct int3403_cdev { |
40 | struct thermal_cooling_device *cdev; |
41 | unsigned long max_state; |
42 | }; |
43 | |
44 | struct int3403_priv { |
45 | struct platform_device *pdev; |
46 | struct acpi_device *adev; |
47 | unsigned long long type; |
48 | void *priv; |
49 | }; |
50 | |
51 | static void int3403_notify(acpi_handle handle, |
52 | u32 event, void *data) |
53 | { |
54 | struct int3403_priv *priv = data; |
55 | struct int3403_sensor *obj; |
56 | |
57 | if (!priv) |
58 | return; |
59 | |
60 | obj = priv->priv; |
61 | if (priv->type != INT3403_TYPE_SENSOR || !obj) |
62 | return; |
63 | |
64 | switch (event) { |
65 | case INT3403_PERF_CHANGED_EVENT: |
66 | break; |
67 | case INT3403_THERMAL_EVENT: |
68 | int340x_thermal_zone_device_update(tzone: obj->int340x_zone, |
69 | event: THERMAL_TRIP_VIOLATED); |
70 | break; |
71 | case INT3403_PERF_TRIP_POINT_CHANGED: |
72 | int340x_thermal_update_trips(int34x_zone: obj->int340x_zone); |
73 | int340x_thermal_zone_device_update(tzone: obj->int340x_zone, |
74 | event: THERMAL_TRIP_CHANGED); |
75 | break; |
76 | default: |
77 | dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n" , event); |
78 | break; |
79 | } |
80 | } |
81 | |
82 | static int int3403_sensor_add(struct int3403_priv *priv) |
83 | { |
84 | int result = 0; |
85 | struct int3403_sensor *obj; |
86 | |
87 | obj = devm_kzalloc(dev: &priv->pdev->dev, size: sizeof(*obj), GFP_KERNEL); |
88 | if (!obj) |
89 | return -ENOMEM; |
90 | |
91 | priv->priv = obj; |
92 | |
93 | obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); |
94 | if (IS_ERR(ptr: obj->int340x_zone)) |
95 | return PTR_ERR(ptr: obj->int340x_zone); |
96 | |
97 | result = acpi_install_notify_handler(device: priv->adev->handle, |
98 | ACPI_DEVICE_NOTIFY, handler: int3403_notify, |
99 | context: (void *)priv); |
100 | if (result) |
101 | goto err_free_obj; |
102 | |
103 | return 0; |
104 | |
105 | err_free_obj: |
106 | int340x_thermal_zone_remove(obj->int340x_zone); |
107 | return result; |
108 | } |
109 | |
110 | static int int3403_sensor_remove(struct int3403_priv *priv) |
111 | { |
112 | struct int3403_sensor *obj = priv->priv; |
113 | |
114 | acpi_remove_notify_handler(device: priv->adev->handle, |
115 | ACPI_DEVICE_NOTIFY, handler: int3403_notify); |
116 | int340x_thermal_zone_remove(obj->int340x_zone); |
117 | |
118 | return 0; |
119 | } |
120 | |
121 | /* INT3403 Cooling devices */ |
122 | static int int3403_get_max_state(struct thermal_cooling_device *cdev, |
123 | unsigned long *state) |
124 | { |
125 | struct int3403_priv *priv = cdev->devdata; |
126 | struct int3403_cdev *obj = priv->priv; |
127 | |
128 | *state = obj->max_state; |
129 | return 0; |
130 | } |
131 | |
132 | static int int3403_get_cur_state(struct thermal_cooling_device *cdev, |
133 | unsigned long *state) |
134 | { |
135 | struct int3403_priv *priv = cdev->devdata; |
136 | unsigned long long level; |
137 | acpi_status status; |
138 | |
139 | status = acpi_evaluate_integer(handle: priv->adev->handle, pathname: "PPPC" , NULL, data: &level); |
140 | if (ACPI_SUCCESS(status)) { |
141 | *state = level; |
142 | return 0; |
143 | } else |
144 | return -EINVAL; |
145 | } |
146 | |
147 | static int |
148 | int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) |
149 | { |
150 | struct int3403_priv *priv = cdev->devdata; |
151 | acpi_status status; |
152 | |
153 | status = acpi_execute_simple_method(handle: priv->adev->handle, method: "SPPC" , arg: state); |
154 | if (ACPI_SUCCESS(status)) |
155 | return 0; |
156 | else |
157 | return -EINVAL; |
158 | } |
159 | |
160 | static const struct thermal_cooling_device_ops int3403_cooling_ops = { |
161 | .get_max_state = int3403_get_max_state, |
162 | .get_cur_state = int3403_get_cur_state, |
163 | .set_cur_state = int3403_set_cur_state, |
164 | }; |
165 | |
166 | static int int3403_cdev_add(struct int3403_priv *priv) |
167 | { |
168 | int result = 0; |
169 | acpi_status status; |
170 | struct int3403_cdev *obj; |
171 | struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; |
172 | union acpi_object *p; |
173 | |
174 | obj = devm_kzalloc(dev: &priv->pdev->dev, size: sizeof(*obj), GFP_KERNEL); |
175 | if (!obj) |
176 | return -ENOMEM; |
177 | |
178 | status = acpi_evaluate_object(object: priv->adev->handle, pathname: "PPSS" , NULL, return_object_buffer: &buf); |
179 | if (ACPI_FAILURE(status)) |
180 | return -ENODEV; |
181 | |
182 | p = buf.pointer; |
183 | if (!p || (p->type != ACPI_TYPE_PACKAGE)) { |
184 | pr_warn("Invalid PPSS data\n" ); |
185 | kfree(objp: buf.pointer); |
186 | return -EFAULT; |
187 | } |
188 | |
189 | priv->priv = obj; |
190 | obj->max_state = p->package.count - 1; |
191 | obj->cdev = |
192 | thermal_cooling_device_register(acpi_device_bid(priv->adev), |
193 | priv, &int3403_cooling_ops); |
194 | if (IS_ERR(ptr: obj->cdev)) |
195 | result = PTR_ERR(ptr: obj->cdev); |
196 | |
197 | kfree(objp: buf.pointer); |
198 | /* TODO: add ACPI notification support */ |
199 | |
200 | return result; |
201 | } |
202 | |
203 | static int int3403_cdev_remove(struct int3403_priv *priv) |
204 | { |
205 | struct int3403_cdev *obj = priv->priv; |
206 | |
207 | thermal_cooling_device_unregister(obj->cdev); |
208 | return 0; |
209 | } |
210 | |
211 | static int int3403_add(struct platform_device *pdev) |
212 | { |
213 | struct int3403_priv *priv; |
214 | int result = 0; |
215 | unsigned long long tmp; |
216 | acpi_status status; |
217 | |
218 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct int3403_priv), |
219 | GFP_KERNEL); |
220 | if (!priv) |
221 | return -ENOMEM; |
222 | |
223 | priv->pdev = pdev; |
224 | priv->adev = ACPI_COMPANION(&(pdev->dev)); |
225 | if (!priv->adev) { |
226 | result = -EINVAL; |
227 | goto err; |
228 | } |
229 | |
230 | |
231 | status = acpi_evaluate_integer(handle: priv->adev->handle, pathname: "_TMP" , |
232 | NULL, data: &tmp); |
233 | if (ACPI_FAILURE(status)) { |
234 | status = acpi_evaluate_integer(handle: priv->adev->handle, pathname: "PTYP" , |
235 | NULL, data: &priv->type); |
236 | if (ACPI_FAILURE(status)) { |
237 | result = -EINVAL; |
238 | goto err; |
239 | } |
240 | } else { |
241 | priv->type = INT3403_TYPE_SENSOR; |
242 | } |
243 | |
244 | platform_set_drvdata(pdev, data: priv); |
245 | switch (priv->type) { |
246 | case INT3403_TYPE_SENSOR: |
247 | result = int3403_sensor_add(priv); |
248 | break; |
249 | case INT3403_TYPE_CHARGER: |
250 | case INT3403_TYPE_BATTERY: |
251 | result = int3403_cdev_add(priv); |
252 | break; |
253 | default: |
254 | result = -EINVAL; |
255 | } |
256 | |
257 | if (result) |
258 | goto err; |
259 | return result; |
260 | |
261 | err: |
262 | return result; |
263 | } |
264 | |
265 | static void int3403_remove(struct platform_device *pdev) |
266 | { |
267 | struct int3403_priv *priv = platform_get_drvdata(pdev); |
268 | |
269 | switch (priv->type) { |
270 | case INT3403_TYPE_SENSOR: |
271 | int3403_sensor_remove(priv); |
272 | break; |
273 | case INT3403_TYPE_CHARGER: |
274 | case INT3403_TYPE_BATTERY: |
275 | int3403_cdev_remove(priv); |
276 | break; |
277 | default: |
278 | break; |
279 | } |
280 | } |
281 | |
282 | static const struct acpi_device_id int3403_device_ids[] = { |
283 | {"INT3403" , 0}, |
284 | {"INTC1043" , 0}, |
285 | {"INTC1046" , 0}, |
286 | {"INTC1062" , 0}, |
287 | {"INTC10A1" , 0}, |
288 | {"" , 0}, |
289 | }; |
290 | MODULE_DEVICE_TABLE(acpi, int3403_device_ids); |
291 | |
292 | static struct platform_driver int3403_driver = { |
293 | .probe = int3403_add, |
294 | .remove_new = int3403_remove, |
295 | .driver = { |
296 | .name = "int3403 thermal" , |
297 | .acpi_match_table = int3403_device_ids, |
298 | }, |
299 | }; |
300 | |
301 | module_platform_driver(int3403_driver); |
302 | |
303 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>" ); |
304 | MODULE_LICENSE("GPL v2" ); |
305 | MODULE_DESCRIPTION("ACPI INT3403 thermal driver" ); |
306 | |