1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Linux driver for WMI sensor information on Dell notebooks. |
4 | * |
5 | * Copyright (C) 2022 Armin Wolf <W_Armin@gmx.de> |
6 | */ |
7 | |
8 | #define pr_format(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/acpi.h> |
11 | #include <linux/debugfs.h> |
12 | #include <linux/device.h> |
13 | #include <linux/device/driver.h> |
14 | #include <linux/dev_printk.h> |
15 | #include <linux/errno.h> |
16 | #include <linux/kconfig.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/hwmon.h> |
19 | #include <linux/kstrtox.h> |
20 | #include <linux/math64.h> |
21 | #include <linux/module.h> |
22 | #include <linux/mutex.h> |
23 | #include <linux/limits.h> |
24 | #include <linux/pm.h> |
25 | #include <linux/power_supply.h> |
26 | #include <linux/printk.h> |
27 | #include <linux/seq_file.h> |
28 | #include <linux/sysfs.h> |
29 | #include <linux/types.h> |
30 | #include <linux/wmi.h> |
31 | |
32 | #include <acpi/battery.h> |
33 | |
34 | #include <asm/unaligned.h> |
35 | |
36 | #define DRIVER_NAME "dell-wmi-ddv" |
37 | |
38 | #define DELL_DDV_SUPPORTED_VERSION_MIN 2 |
39 | #define DELL_DDV_SUPPORTED_VERSION_MAX 3 |
40 | #define DELL_DDV_GUID "8A42EA14-4F2A-FD45-6422-0087F7A7E608" |
41 | |
42 | #define DELL_EPPID_LENGTH 20 |
43 | #define DELL_EPPID_EXT_LENGTH 23 |
44 | |
45 | static bool force; |
46 | module_param_unsafe(force, bool, 0); |
47 | MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions" ); |
48 | |
49 | enum dell_ddv_method { |
50 | DELL_DDV_BATTERY_DESIGN_CAPACITY = 0x01, |
51 | DELL_DDV_BATTERY_FULL_CHARGE_CAPACITY = 0x02, |
52 | DELL_DDV_BATTERY_MANUFACTURE_NAME = 0x03, |
53 | DELL_DDV_BATTERY_MANUFACTURE_DATE = 0x04, |
54 | DELL_DDV_BATTERY_SERIAL_NUMBER = 0x05, |
55 | DELL_DDV_BATTERY_CHEMISTRY_VALUE = 0x06, |
56 | DELL_DDV_BATTERY_TEMPERATURE = 0x07, |
57 | DELL_DDV_BATTERY_CURRENT = 0x08, |
58 | DELL_DDV_BATTERY_VOLTAGE = 0x09, |
59 | DELL_DDV_BATTERY_MANUFACTURER_ACCESS = 0x0A, |
60 | DELL_DDV_BATTERY_RELATIVE_CHARGE_STATE = 0x0B, |
61 | DELL_DDV_BATTERY_CYCLE_COUNT = 0x0C, |
62 | DELL_DDV_BATTERY_EPPID = 0x0D, |
63 | DELL_DDV_BATTERY_RAW_ANALYTICS_START = 0x0E, |
64 | DELL_DDV_BATTERY_RAW_ANALYTICS = 0x0F, |
65 | DELL_DDV_BATTERY_DESIGN_VOLTAGE = 0x10, |
66 | DELL_DDV_BATTERY_RAW_ANALYTICS_A_BLOCK = 0x11, /* version 3 */ |
67 | |
68 | DELL_DDV_INTERFACE_VERSION = 0x12, |
69 | |
70 | DELL_DDV_FAN_SENSOR_INFORMATION = 0x20, |
71 | DELL_DDV_THERMAL_SENSOR_INFORMATION = 0x22, |
72 | }; |
73 | |
74 | struct fan_sensor_entry { |
75 | u8 type; |
76 | __le16 rpm; |
77 | } __packed; |
78 | |
79 | struct thermal_sensor_entry { |
80 | u8 type; |
81 | s8 now; |
82 | s8 min; |
83 | s8 max; |
84 | u8 unknown; |
85 | } __packed; |
86 | |
87 | struct combined_channel_info { |
88 | struct hwmon_channel_info info; |
89 | u32 config[]; |
90 | }; |
91 | |
92 | struct combined_chip_info { |
93 | struct hwmon_chip_info chip; |
94 | const struct hwmon_channel_info *info[]; |
95 | }; |
96 | |
97 | struct dell_wmi_ddv_sensors { |
98 | bool active; |
99 | struct mutex lock; /* protect caching */ |
100 | unsigned long timestamp; |
101 | union acpi_object *obj; |
102 | u64 entries; |
103 | }; |
104 | |
105 | struct dell_wmi_ddv_data { |
106 | struct acpi_battery_hook hook; |
107 | struct device_attribute temp_attr; |
108 | struct device_attribute eppid_attr; |
109 | struct dell_wmi_ddv_sensors fans; |
110 | struct dell_wmi_ddv_sensors temps; |
111 | struct wmi_device *wdev; |
112 | }; |
113 | |
114 | static const char * const fan_labels[] = { |
115 | "CPU Fan" , |
116 | "Chassis Motherboard Fan" , |
117 | "Video Fan" , |
118 | "Power Supply Fan" , |
119 | "Chipset Fan" , |
120 | "Memory Fan" , |
121 | "PCI Fan" , |
122 | "HDD Fan" , |
123 | }; |
124 | |
125 | static const char * const fan_dock_labels[] = { |
126 | "Docking Chassis/Motherboard Fan" , |
127 | "Docking Video Fan" , |
128 | "Docking Power Supply Fan" , |
129 | "Docking Chipset Fan" , |
130 | }; |
131 | |
132 | static int dell_wmi_ddv_query_type(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, |
133 | union acpi_object **result, acpi_object_type type) |
134 | { |
135 | struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; |
136 | const struct acpi_buffer in = { |
137 | .length = sizeof(arg), |
138 | .pointer = &arg, |
139 | }; |
140 | union acpi_object *obj; |
141 | acpi_status ret; |
142 | |
143 | ret = wmidev_evaluate_method(wdev, instance: 0x0, method_id: method, in: &in, out: &out); |
144 | if (ACPI_FAILURE(ret)) |
145 | return -EIO; |
146 | |
147 | obj = out.pointer; |
148 | if (!obj) |
149 | return -ENODATA; |
150 | |
151 | if (obj->type != type) { |
152 | kfree(objp: obj); |
153 | return -ENOMSG; |
154 | } |
155 | |
156 | *result = obj; |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_method method, |
162 | u32 arg, u32 *res) |
163 | { |
164 | union acpi_object *obj; |
165 | int ret; |
166 | |
167 | ret = dell_wmi_ddv_query_type(wdev, method, arg, result: &obj, ACPI_TYPE_INTEGER); |
168 | if (ret < 0) |
169 | return ret; |
170 | |
171 | if (obj->integer.value <= U32_MAX) |
172 | *res = (u32)obj->integer.value; |
173 | else |
174 | ret = -ERANGE; |
175 | |
176 | kfree(objp: obj); |
177 | |
178 | return ret; |
179 | } |
180 | |
181 | static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, |
182 | u32 arg, union acpi_object **result) |
183 | { |
184 | union acpi_object *obj; |
185 | u64 buffer_size; |
186 | int ret; |
187 | |
188 | ret = dell_wmi_ddv_query_type(wdev, method, arg, result: &obj, ACPI_TYPE_PACKAGE); |
189 | if (ret < 0) |
190 | return ret; |
191 | |
192 | if (obj->package.count != 2 || |
193 | obj->package.elements[0].type != ACPI_TYPE_INTEGER || |
194 | obj->package.elements[1].type != ACPI_TYPE_BUFFER) { |
195 | ret = -ENOMSG; |
196 | |
197 | goto err_free; |
198 | } |
199 | |
200 | buffer_size = obj->package.elements[0].integer.value; |
201 | |
202 | if (!buffer_size) { |
203 | ret = -ENODATA; |
204 | |
205 | goto err_free; |
206 | } |
207 | |
208 | if (buffer_size > obj->package.elements[1].buffer.length) { |
209 | dev_warn(&wdev->dev, |
210 | FW_WARN "WMI buffer size (%llu) exceeds ACPI buffer size (%d)\n" , |
211 | buffer_size, obj->package.elements[1].buffer.length); |
212 | ret = -EMSGSIZE; |
213 | |
214 | goto err_free; |
215 | } |
216 | |
217 | *result = obj; |
218 | |
219 | return 0; |
220 | |
221 | err_free: |
222 | kfree(objp: obj); |
223 | |
224 | return ret; |
225 | } |
226 | |
227 | static int dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, |
228 | u32 arg, union acpi_object **result) |
229 | { |
230 | return dell_wmi_ddv_query_type(wdev, method, arg, result, ACPI_TYPE_STRING); |
231 | } |
232 | |
233 | /* |
234 | * Needs to be called with lock held, except during initialization. |
235 | */ |
236 | static int dell_wmi_ddv_update_sensors(struct wmi_device *wdev, enum dell_ddv_method method, |
237 | struct dell_wmi_ddv_sensors *sensors, size_t entry_size) |
238 | { |
239 | u64 buffer_size, rem, entries; |
240 | union acpi_object *obj; |
241 | u8 *buffer; |
242 | int ret; |
243 | |
244 | if (sensors->obj) { |
245 | if (time_before(jiffies, sensors->timestamp + HZ)) |
246 | return 0; |
247 | |
248 | kfree(objp: sensors->obj); |
249 | sensors->obj = NULL; |
250 | } |
251 | |
252 | ret = dell_wmi_ddv_query_buffer(wdev, method, arg: 0, result: &obj); |
253 | if (ret < 0) |
254 | return ret; |
255 | |
256 | /* buffer format sanity check */ |
257 | buffer_size = obj->package.elements[0].integer.value; |
258 | buffer = obj->package.elements[1].buffer.pointer; |
259 | entries = div64_u64_rem(dividend: buffer_size, divisor: entry_size, remainder: &rem); |
260 | if (rem != 1 || buffer[buffer_size - 1] != 0xff) { |
261 | ret = -ENOMSG; |
262 | goto err_free; |
263 | } |
264 | |
265 | if (!entries) { |
266 | ret = -ENODATA; |
267 | goto err_free; |
268 | } |
269 | |
270 | sensors->obj = obj; |
271 | sensors->entries = entries; |
272 | sensors->timestamp = jiffies; |
273 | |
274 | return 0; |
275 | |
276 | err_free: |
277 | kfree(objp: obj); |
278 | |
279 | return ret; |
280 | } |
281 | |
282 | static umode_t dell_wmi_ddv_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, |
283 | int channel) |
284 | { |
285 | return 0444; |
286 | } |
287 | |
288 | static int dell_wmi_ddv_fan_read_channel(struct dell_wmi_ddv_data *data, u32 attr, int channel, |
289 | long *val) |
290 | { |
291 | struct fan_sensor_entry *entry; |
292 | int ret; |
293 | |
294 | ret = dell_wmi_ddv_update_sensors(wdev: data->wdev, method: DELL_DDV_FAN_SENSOR_INFORMATION, |
295 | sensors: &data->fans, entry_size: sizeof(*entry)); |
296 | if (ret < 0) |
297 | return ret; |
298 | |
299 | if (channel >= data->fans.entries) |
300 | return -ENXIO; |
301 | |
302 | entry = (struct fan_sensor_entry *)data->fans.obj->package.elements[1].buffer.pointer; |
303 | switch (attr) { |
304 | case hwmon_fan_input: |
305 | *val = get_unaligned_le16(p: &entry[channel].rpm); |
306 | return 0; |
307 | default: |
308 | break; |
309 | } |
310 | |
311 | return -EOPNOTSUPP; |
312 | } |
313 | |
314 | static int dell_wmi_ddv_temp_read_channel(struct dell_wmi_ddv_data *data, u32 attr, int channel, |
315 | long *val) |
316 | { |
317 | struct thermal_sensor_entry *entry; |
318 | int ret; |
319 | |
320 | ret = dell_wmi_ddv_update_sensors(wdev: data->wdev, method: DELL_DDV_THERMAL_SENSOR_INFORMATION, |
321 | sensors: &data->temps, entry_size: sizeof(*entry)); |
322 | if (ret < 0) |
323 | return ret; |
324 | |
325 | if (channel >= data->temps.entries) |
326 | return -ENXIO; |
327 | |
328 | entry = (struct thermal_sensor_entry *)data->temps.obj->package.elements[1].buffer.pointer; |
329 | switch (attr) { |
330 | case hwmon_temp_input: |
331 | *val = entry[channel].now * 1000; |
332 | return 0; |
333 | case hwmon_temp_min: |
334 | *val = entry[channel].min * 1000; |
335 | return 0; |
336 | case hwmon_temp_max: |
337 | *val = entry[channel].max * 1000; |
338 | return 0; |
339 | default: |
340 | break; |
341 | } |
342 | |
343 | return -EOPNOTSUPP; |
344 | } |
345 | |
346 | static int dell_wmi_ddv_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
347 | int channel, long *val) |
348 | { |
349 | struct dell_wmi_ddv_data *data = dev_get_drvdata(dev); |
350 | int ret; |
351 | |
352 | switch (type) { |
353 | case hwmon_fan: |
354 | mutex_lock(&data->fans.lock); |
355 | ret = dell_wmi_ddv_fan_read_channel(data, attr, channel, val); |
356 | mutex_unlock(lock: &data->fans.lock); |
357 | return ret; |
358 | case hwmon_temp: |
359 | mutex_lock(&data->temps.lock); |
360 | ret = dell_wmi_ddv_temp_read_channel(data, attr, channel, val); |
361 | mutex_unlock(lock: &data->temps.lock); |
362 | return ret; |
363 | default: |
364 | break; |
365 | } |
366 | |
367 | return -EOPNOTSUPP; |
368 | } |
369 | |
370 | static int dell_wmi_ddv_fan_read_string(struct dell_wmi_ddv_data *data, int channel, |
371 | const char **str) |
372 | { |
373 | struct fan_sensor_entry *entry; |
374 | int ret; |
375 | u8 type; |
376 | |
377 | ret = dell_wmi_ddv_update_sensors(wdev: data->wdev, method: DELL_DDV_FAN_SENSOR_INFORMATION, |
378 | sensors: &data->fans, entry_size: sizeof(*entry)); |
379 | if (ret < 0) |
380 | return ret; |
381 | |
382 | if (channel >= data->fans.entries) |
383 | return -ENXIO; |
384 | |
385 | entry = (struct fan_sensor_entry *)data->fans.obj->package.elements[1].buffer.pointer; |
386 | type = entry[channel].type; |
387 | switch (type) { |
388 | case 0x00 ... 0x07: |
389 | *str = fan_labels[type]; |
390 | break; |
391 | case 0x11 ... 0x14: |
392 | *str = fan_dock_labels[type - 0x11]; |
393 | break; |
394 | default: |
395 | *str = "Unknown Fan" ; |
396 | break; |
397 | } |
398 | |
399 | return 0; |
400 | } |
401 | |
402 | static int dell_wmi_ddv_temp_read_string(struct dell_wmi_ddv_data *data, int channel, |
403 | const char **str) |
404 | { |
405 | struct thermal_sensor_entry *entry; |
406 | int ret; |
407 | |
408 | ret = dell_wmi_ddv_update_sensors(wdev: data->wdev, method: DELL_DDV_THERMAL_SENSOR_INFORMATION, |
409 | sensors: &data->temps, entry_size: sizeof(*entry)); |
410 | if (ret < 0) |
411 | return ret; |
412 | |
413 | if (channel >= data->temps.entries) |
414 | return -ENXIO; |
415 | |
416 | entry = (struct thermal_sensor_entry *)data->temps.obj->package.elements[1].buffer.pointer; |
417 | switch (entry[channel].type) { |
418 | case 0x00: |
419 | *str = "CPU" ; |
420 | break; |
421 | case 0x11: |
422 | *str = "Video" ; |
423 | break; |
424 | case 0x22: |
425 | *str = "Memory" ; /* sometimes called DIMM */ |
426 | break; |
427 | case 0x33: |
428 | *str = "Other" ; |
429 | break; |
430 | case 0x44: |
431 | *str = "Ambient" ; /* sometimes called SKIN */ |
432 | break; |
433 | case 0x52: |
434 | *str = "SODIMM" ; |
435 | break; |
436 | case 0x55: |
437 | *str = "HDD" ; |
438 | break; |
439 | case 0x62: |
440 | *str = "SODIMM 2" ; |
441 | break; |
442 | case 0x73: |
443 | *str = "NB" ; |
444 | break; |
445 | case 0x83: |
446 | *str = "Charger" ; |
447 | break; |
448 | case 0xbb: |
449 | *str = "Memory 3" ; |
450 | break; |
451 | default: |
452 | *str = "Unknown" ; |
453 | break; |
454 | } |
455 | |
456 | return 0; |
457 | } |
458 | |
459 | static int dell_wmi_ddv_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
460 | int channel, const char **str) |
461 | { |
462 | struct dell_wmi_ddv_data *data = dev_get_drvdata(dev); |
463 | int ret; |
464 | |
465 | switch (type) { |
466 | case hwmon_fan: |
467 | switch (attr) { |
468 | case hwmon_fan_label: |
469 | mutex_lock(&data->fans.lock); |
470 | ret = dell_wmi_ddv_fan_read_string(data, channel, str); |
471 | mutex_unlock(lock: &data->fans.lock); |
472 | return ret; |
473 | default: |
474 | break; |
475 | } |
476 | break; |
477 | case hwmon_temp: |
478 | switch (attr) { |
479 | case hwmon_temp_label: |
480 | mutex_lock(&data->temps.lock); |
481 | ret = dell_wmi_ddv_temp_read_string(data, channel, str); |
482 | mutex_unlock(lock: &data->temps.lock); |
483 | return ret; |
484 | default: |
485 | break; |
486 | } |
487 | break; |
488 | default: |
489 | break; |
490 | } |
491 | |
492 | return -EOPNOTSUPP; |
493 | } |
494 | |
495 | static const struct hwmon_ops dell_wmi_ddv_ops = { |
496 | .is_visible = dell_wmi_ddv_is_visible, |
497 | .read = dell_wmi_ddv_read, |
498 | .read_string = dell_wmi_ddv_read_string, |
499 | }; |
500 | |
501 | static struct hwmon_channel_info *dell_wmi_ddv_channel_create(struct device *dev, u64 count, |
502 | enum hwmon_sensor_types type, |
503 | u32 config) |
504 | { |
505 | struct combined_channel_info *cinfo; |
506 | int i; |
507 | |
508 | cinfo = devm_kzalloc(dev, struct_size(cinfo, config, count + 1), GFP_KERNEL); |
509 | if (!cinfo) |
510 | return ERR_PTR(error: -ENOMEM); |
511 | |
512 | cinfo->info.type = type; |
513 | cinfo->info.config = cinfo->config; |
514 | |
515 | for (i = 0; i < count; i++) |
516 | cinfo->config[i] = config; |
517 | |
518 | return &cinfo->info; |
519 | } |
520 | |
521 | static void dell_wmi_ddv_hwmon_cache_invalidate(struct dell_wmi_ddv_sensors *sensors) |
522 | { |
523 | if (!sensors->active) |
524 | return; |
525 | |
526 | mutex_lock(&sensors->lock); |
527 | kfree(objp: sensors->obj); |
528 | sensors->obj = NULL; |
529 | mutex_unlock(lock: &sensors->lock); |
530 | } |
531 | |
532 | static void dell_wmi_ddv_hwmon_cache_destroy(void *data) |
533 | { |
534 | struct dell_wmi_ddv_sensors *sensors = data; |
535 | |
536 | sensors->active = false; |
537 | mutex_destroy(lock: &sensors->lock); |
538 | kfree(objp: sensors->obj); |
539 | } |
540 | |
541 | static struct hwmon_channel_info *dell_wmi_ddv_channel_init(struct wmi_device *wdev, |
542 | enum dell_ddv_method method, |
543 | struct dell_wmi_ddv_sensors *sensors, |
544 | size_t entry_size, |
545 | enum hwmon_sensor_types type, |
546 | u32 config) |
547 | { |
548 | struct hwmon_channel_info *info; |
549 | int ret; |
550 | |
551 | ret = dell_wmi_ddv_update_sensors(wdev, method, sensors, entry_size); |
552 | if (ret < 0) |
553 | return ERR_PTR(error: ret); |
554 | |
555 | mutex_init(&sensors->lock); |
556 | sensors->active = true; |
557 | |
558 | ret = devm_add_action_or_reset(&wdev->dev, dell_wmi_ddv_hwmon_cache_destroy, sensors); |
559 | if (ret < 0) |
560 | return ERR_PTR(error: ret); |
561 | |
562 | info = dell_wmi_ddv_channel_create(dev: &wdev->dev, count: sensors->entries, type, config); |
563 | if (IS_ERR(ptr: info)) |
564 | devm_release_action(dev: &wdev->dev, action: dell_wmi_ddv_hwmon_cache_destroy, data: sensors); |
565 | |
566 | return info; |
567 | } |
568 | |
569 | static int dell_wmi_ddv_hwmon_add(struct dell_wmi_ddv_data *data) |
570 | { |
571 | struct wmi_device *wdev = data->wdev; |
572 | struct combined_chip_info *cinfo; |
573 | struct hwmon_channel_info *info; |
574 | struct device *hdev; |
575 | int index = 0; |
576 | int ret; |
577 | |
578 | if (!devres_open_group(dev: &wdev->dev, id: dell_wmi_ddv_hwmon_add, GFP_KERNEL)) |
579 | return -ENOMEM; |
580 | |
581 | cinfo = devm_kzalloc(dev: &wdev->dev, struct_size(cinfo, info, 4), GFP_KERNEL); |
582 | if (!cinfo) { |
583 | ret = -ENOMEM; |
584 | |
585 | goto err_release; |
586 | } |
587 | |
588 | cinfo->chip.ops = &dell_wmi_ddv_ops; |
589 | cinfo->chip.info = cinfo->info; |
590 | |
591 | info = dell_wmi_ddv_channel_create(dev: &wdev->dev, count: 1, type: hwmon_chip, HWMON_C_REGISTER_TZ); |
592 | if (IS_ERR(ptr: info)) { |
593 | ret = PTR_ERR(ptr: info); |
594 | |
595 | goto err_release; |
596 | } |
597 | |
598 | cinfo->info[index] = info; |
599 | index++; |
600 | |
601 | info = dell_wmi_ddv_channel_init(wdev, method: DELL_DDV_FAN_SENSOR_INFORMATION, sensors: &data->fans, |
602 | entry_size: sizeof(struct fan_sensor_entry), type: hwmon_fan, |
603 | config: (HWMON_F_INPUT | HWMON_F_LABEL)); |
604 | if (!IS_ERR(ptr: info)) { |
605 | cinfo->info[index] = info; |
606 | index++; |
607 | } |
608 | |
609 | info = dell_wmi_ddv_channel_init(wdev, method: DELL_DDV_THERMAL_SENSOR_INFORMATION, sensors: &data->temps, |
610 | entry_size: sizeof(struct thermal_sensor_entry), type: hwmon_temp, |
611 | config: (HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | |
612 | HWMON_T_LABEL)); |
613 | if (!IS_ERR(ptr: info)) { |
614 | cinfo->info[index] = info; |
615 | index++; |
616 | } |
617 | |
618 | if (index < 2) { |
619 | /* Finding no available sensors is not an error */ |
620 | ret = 0; |
621 | |
622 | goto err_release; |
623 | } |
624 | |
625 | hdev = devm_hwmon_device_register_with_info(dev: &wdev->dev, name: "dell_ddv" , drvdata: data, info: &cinfo->chip, |
626 | NULL); |
627 | if (IS_ERR(ptr: hdev)) { |
628 | ret = PTR_ERR(ptr: hdev); |
629 | |
630 | goto err_release; |
631 | } |
632 | |
633 | devres_close_group(dev: &wdev->dev, id: dell_wmi_ddv_hwmon_add); |
634 | |
635 | return 0; |
636 | |
637 | err_release: |
638 | devres_release_group(dev: &wdev->dev, id: dell_wmi_ddv_hwmon_add); |
639 | |
640 | return ret; |
641 | } |
642 | |
643 | static int dell_wmi_ddv_battery_index(struct acpi_device *acpi_dev, u32 *index) |
644 | { |
645 | const char *uid_str; |
646 | |
647 | uid_str = acpi_device_uid(acpi_dev); |
648 | if (!uid_str) |
649 | return -ENODEV; |
650 | |
651 | return kstrtou32(s: uid_str, base: 10, res: index); |
652 | } |
653 | |
654 | static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf) |
655 | { |
656 | struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, temp_attr); |
657 | u32 index, value; |
658 | int ret; |
659 | |
660 | ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), index: &index); |
661 | if (ret < 0) |
662 | return ret; |
663 | |
664 | ret = dell_wmi_ddv_query_integer(wdev: data->wdev, method: DELL_DDV_BATTERY_TEMPERATURE, arg: index, res: &value); |
665 | if (ret < 0) |
666 | return ret; |
667 | |
668 | /* Use 2731 instead of 2731.5 to avoid unnecessary rounding */ |
669 | return sysfs_emit(buf, fmt: "%d\n" , value - 2731); |
670 | } |
671 | |
672 | static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf) |
673 | { |
674 | struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, eppid_attr); |
675 | union acpi_object *obj; |
676 | u32 index; |
677 | int ret; |
678 | |
679 | ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), index: &index); |
680 | if (ret < 0) |
681 | return ret; |
682 | |
683 | ret = dell_wmi_ddv_query_string(wdev: data->wdev, method: DELL_DDV_BATTERY_EPPID, arg: index, result: &obj); |
684 | if (ret < 0) |
685 | return ret; |
686 | |
687 | if (obj->string.length != DELL_EPPID_LENGTH && obj->string.length != DELL_EPPID_EXT_LENGTH) |
688 | dev_info_once(&data->wdev->dev, FW_INFO "Suspicious ePPID length (%d)\n" , |
689 | obj->string.length); |
690 | |
691 | ret = sysfs_emit(buf, fmt: "%s\n" , obj->string.pointer); |
692 | |
693 | kfree(objp: obj); |
694 | |
695 | return ret; |
696 | } |
697 | |
698 | static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) |
699 | { |
700 | struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); |
701 | u32 index; |
702 | int ret; |
703 | |
704 | /* Return 0 instead of error to avoid being unloaded */ |
705 | ret = dell_wmi_ddv_battery_index(to_acpi_device(battery->dev.parent), index: &index); |
706 | if (ret < 0) |
707 | return 0; |
708 | |
709 | ret = device_create_file(device: &battery->dev, entry: &data->temp_attr); |
710 | if (ret < 0) |
711 | return ret; |
712 | |
713 | ret = device_create_file(device: &battery->dev, entry: &data->eppid_attr); |
714 | if (ret < 0) { |
715 | device_remove_file(dev: &battery->dev, attr: &data->temp_attr); |
716 | |
717 | return ret; |
718 | } |
719 | |
720 | return 0; |
721 | } |
722 | |
723 | static int dell_wmi_ddv_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) |
724 | { |
725 | struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); |
726 | |
727 | device_remove_file(dev: &battery->dev, attr: &data->temp_attr); |
728 | device_remove_file(dev: &battery->dev, attr: &data->eppid_attr); |
729 | |
730 | return 0; |
731 | } |
732 | |
733 | static void dell_wmi_ddv_battery_remove(void *data) |
734 | { |
735 | struct acpi_battery_hook *hook = data; |
736 | |
737 | battery_hook_unregister(hook); |
738 | } |
739 | |
740 | static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) |
741 | { |
742 | data->hook.name = "Dell DDV Battery Extension" ; |
743 | data->hook.add_battery = dell_wmi_ddv_add_battery; |
744 | data->hook.remove_battery = dell_wmi_ddv_remove_battery; |
745 | |
746 | sysfs_attr_init(&data->temp_attr.attr); |
747 | data->temp_attr.attr.name = "temp" ; |
748 | data->temp_attr.attr.mode = 0444; |
749 | data->temp_attr.show = temp_show; |
750 | |
751 | sysfs_attr_init(&data->eppid_attr.attr); |
752 | data->eppid_attr.attr.name = "eppid" ; |
753 | data->eppid_attr.attr.mode = 0444; |
754 | data->eppid_attr.show = eppid_show; |
755 | |
756 | battery_hook_register(hook: &data->hook); |
757 | |
758 | return devm_add_action_or_reset(&data->wdev->dev, dell_wmi_ddv_battery_remove, &data->hook); |
759 | } |
760 | |
761 | static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method) |
762 | { |
763 | struct device *dev = seq->private; |
764 | struct dell_wmi_ddv_data *data = dev_get_drvdata(dev); |
765 | union acpi_object *obj; |
766 | u64 size; |
767 | u8 *buf; |
768 | int ret; |
769 | |
770 | ret = dell_wmi_ddv_query_buffer(wdev: data->wdev, method, arg: 0, result: &obj); |
771 | if (ret < 0) |
772 | return ret; |
773 | |
774 | size = obj->package.elements[0].integer.value; |
775 | buf = obj->package.elements[1].buffer.pointer; |
776 | ret = seq_write(seq, data: buf, len: size); |
777 | kfree(objp: obj); |
778 | |
779 | return ret; |
780 | } |
781 | |
782 | static int dell_wmi_ddv_fan_read(struct seq_file *seq, void *offset) |
783 | { |
784 | return dell_wmi_ddv_buffer_read(seq, method: DELL_DDV_FAN_SENSOR_INFORMATION); |
785 | } |
786 | |
787 | static int dell_wmi_ddv_temp_read(struct seq_file *seq, void *offset) |
788 | { |
789 | return dell_wmi_ddv_buffer_read(seq, method: DELL_DDV_THERMAL_SENSOR_INFORMATION); |
790 | } |
791 | |
792 | static void dell_wmi_ddv_debugfs_remove(void *data) |
793 | { |
794 | struct dentry *entry = data; |
795 | |
796 | debugfs_remove(dentry: entry); |
797 | } |
798 | |
799 | static void dell_wmi_ddv_debugfs_init(struct wmi_device *wdev) |
800 | { |
801 | struct dentry *entry; |
802 | char name[64]; |
803 | |
804 | scnprintf(buf: name, ARRAY_SIZE(name), fmt: "%s-%s" , DRIVER_NAME, dev_name(dev: &wdev->dev)); |
805 | entry = debugfs_create_dir(name, NULL); |
806 | |
807 | debugfs_create_devm_seqfile(dev: &wdev->dev, name: "fan_sensor_information" , parent: entry, |
808 | read_fn: dell_wmi_ddv_fan_read); |
809 | debugfs_create_devm_seqfile(dev: &wdev->dev, name: "thermal_sensor_information" , parent: entry, |
810 | read_fn: dell_wmi_ddv_temp_read); |
811 | |
812 | devm_add_action_or_reset(&wdev->dev, dell_wmi_ddv_debugfs_remove, entry); |
813 | } |
814 | |
815 | static int dell_wmi_ddv_probe(struct wmi_device *wdev, const void *context) |
816 | { |
817 | struct dell_wmi_ddv_data *data; |
818 | u32 version; |
819 | int ret; |
820 | |
821 | ret = dell_wmi_ddv_query_integer(wdev, method: DELL_DDV_INTERFACE_VERSION, arg: 0, res: &version); |
822 | if (ret < 0) |
823 | return ret; |
824 | |
825 | dev_dbg(&wdev->dev, "WMI interface version: %d\n" , version); |
826 | if (version < DELL_DDV_SUPPORTED_VERSION_MIN || version > DELL_DDV_SUPPORTED_VERSION_MAX) { |
827 | if (!force) |
828 | return -ENODEV; |
829 | |
830 | dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u)\n" , |
831 | version); |
832 | } |
833 | |
834 | data = devm_kzalloc(dev: &wdev->dev, size: sizeof(*data), GFP_KERNEL); |
835 | if (!data) |
836 | return -ENOMEM; |
837 | |
838 | dev_set_drvdata(dev: &wdev->dev, data); |
839 | data->wdev = wdev; |
840 | |
841 | dell_wmi_ddv_debugfs_init(wdev); |
842 | |
843 | if (IS_REACHABLE(CONFIG_ACPI_BATTERY)) { |
844 | ret = dell_wmi_ddv_battery_add(data); |
845 | if (ret < 0) |
846 | dev_warn(&wdev->dev, "Unable to register ACPI battery hook: %d\n" , ret); |
847 | } |
848 | |
849 | if (IS_REACHABLE(CONFIG_HWMON)) { |
850 | ret = dell_wmi_ddv_hwmon_add(data); |
851 | if (ret < 0) |
852 | dev_warn(&wdev->dev, "Unable to register hwmon interface: %d\n" , ret); |
853 | } |
854 | |
855 | return 0; |
856 | } |
857 | |
858 | static int dell_wmi_ddv_resume(struct device *dev) |
859 | { |
860 | struct dell_wmi_ddv_data *data = dev_get_drvdata(dev); |
861 | |
862 | /* Force re-reading of all active sensors */ |
863 | dell_wmi_ddv_hwmon_cache_invalidate(sensors: &data->fans); |
864 | dell_wmi_ddv_hwmon_cache_invalidate(sensors: &data->temps); |
865 | |
866 | return 0; |
867 | } |
868 | |
869 | static DEFINE_SIMPLE_DEV_PM_OPS(dell_wmi_ddv_dev_pm_ops, NULL, dell_wmi_ddv_resume); |
870 | |
871 | static const struct wmi_device_id dell_wmi_ddv_id_table[] = { |
872 | { DELL_DDV_GUID, NULL }, |
873 | { } |
874 | }; |
875 | MODULE_DEVICE_TABLE(wmi, dell_wmi_ddv_id_table); |
876 | |
877 | static struct wmi_driver dell_wmi_ddv_driver = { |
878 | .driver = { |
879 | .name = DRIVER_NAME, |
880 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
881 | .pm = pm_sleep_ptr(&dell_wmi_ddv_dev_pm_ops), |
882 | }, |
883 | .id_table = dell_wmi_ddv_id_table, |
884 | .probe = dell_wmi_ddv_probe, |
885 | .no_singleton = true, |
886 | }; |
887 | module_wmi_driver(dell_wmi_ddv_driver); |
888 | |
889 | MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>" ); |
890 | MODULE_DESCRIPTION("Dell WMI sensor driver" ); |
891 | MODULE_LICENSE("GPL" ); |
892 | |