1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * HWMON driver for ASUS motherboards that provides sensor readouts via WMI |
4 | * interface present in the UEFI of the X370/X470/B450/X399 Ryzen motherboards. |
5 | * |
6 | * Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> |
7 | * |
8 | * WMI interface provides: |
9 | * - CPU Core Voltage, |
10 | * - CPU SOC Voltage, |
11 | * - DRAM Voltage, |
12 | * - VDDP Voltage, |
13 | * - 1.8V PLL Voltage, |
14 | * - +12V Voltage, |
15 | * - +5V Voltage, |
16 | * - 3VSB Voltage, |
17 | * - VBAT Voltage, |
18 | * - AVCC3 Voltage, |
19 | * - SB 1.05V Voltage, |
20 | * - CPU Core Voltage, |
21 | * - CPU SOC Voltage, |
22 | * - DRAM Voltage, |
23 | * - CPU Fan RPM, |
24 | * - Chassis Fan 1 RPM, |
25 | * - Chassis Fan 2 RPM, |
26 | * - Chassis Fan 3 RPM, |
27 | * - HAMP Fan RPM, |
28 | * - Water Pump RPM, |
29 | * - CPU OPT RPM, |
30 | * - Water Flow RPM, |
31 | * - AIO Pump RPM, |
32 | * - CPU Temperature, |
33 | * - CPU Socket Temperature, |
34 | * - Motherboard Temperature, |
35 | * - Chipset Temperature, |
36 | * - Tsensor 1 Temperature, |
37 | * - CPU VRM Temperature, |
38 | * - Water In, |
39 | * - Water Out, |
40 | * - CPU VRM Output Current. |
41 | */ |
42 | |
43 | #include <linux/acpi.h> |
44 | #include <linux/dmi.h> |
45 | #include <linux/hwmon.h> |
46 | #include <linux/init.h> |
47 | #include <linux/jiffies.h> |
48 | #include <linux/kernel.h> |
49 | #include <linux/module.h> |
50 | #include <linux/mutex.h> |
51 | #include <linux/units.h> |
52 | #include <linux/wmi.h> |
53 | |
54 | #define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" |
55 | #define ASUSWMI_METHODID_GET_VALUE 0x52574543 /* RWEC */ |
56 | #define ASUSWMI_METHODID_UPDATE_BUFFER 0x51574543 /* QWEC */ |
57 | #define ASUSWMI_METHODID_GET_INFO 0x50574543 /* PWEC */ |
58 | #define ASUSWMI_METHODID_GET_NUMBER 0x50574572 /* PWEr */ |
59 | #define ASUSWMI_METHODID_GET_VERSION 0x50574574 /* PWEt */ |
60 | |
61 | #define ASUS_WMI_MAX_STR_SIZE 32 |
62 | |
63 | #define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name) { \ |
64 | .matches = { \ |
65 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ |
66 | DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ |
67 | }, \ |
68 | } |
69 | |
70 | static const struct dmi_system_id asus_wmi_dmi_table[] = { |
71 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A" ), |
72 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO" ), |
73 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI EXTREME" ), |
74 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("CROSSHAIR VI HERO" ), |
75 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO (WI-FI AC)" ), |
76 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO" ), |
77 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)" ), |
78 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING" ), |
79 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING" ), |
80 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING II" ), |
81 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING" ), |
82 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING" ), |
83 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING" ), |
84 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING" ), |
85 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME" ), |
86 | DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME ALPHA" ), |
87 | {} |
88 | }; |
89 | MODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table); |
90 | |
91 | enum asus_wmi_sensor_class { |
92 | VOLTAGE = 0x0, |
93 | TEMPERATURE_C = 0x1, |
94 | FAN_RPM = 0x2, |
95 | CURRENT = 0x3, |
96 | WATER_FLOW = 0x4, |
97 | }; |
98 | |
99 | enum asus_wmi_location { |
100 | CPU = 0x0, |
101 | CPU_SOC = 0x1, |
102 | DRAM = 0x2, |
103 | MOTHERBOARD = 0x3, |
104 | CHIPSET = 0x4, |
105 | AUX = 0x5, |
106 | VRM = 0x6, |
107 | COOLER = 0x7 |
108 | }; |
109 | |
110 | enum asus_wmi_type { |
111 | SIGNED_INT = 0x0, |
112 | UNSIGNED_INT = 0x1, |
113 | SCALED = 0x3, |
114 | }; |
115 | |
116 | enum asus_wmi_source { |
117 | SIO = 0x1, |
118 | EC = 0x2 |
119 | }; |
120 | |
121 | static enum hwmon_sensor_types asus_data_types[] = { |
122 | [VOLTAGE] = hwmon_in, |
123 | [TEMPERATURE_C] = hwmon_temp, |
124 | [FAN_RPM] = hwmon_fan, |
125 | [CURRENT] = hwmon_curr, |
126 | [WATER_FLOW] = hwmon_fan, |
127 | }; |
128 | |
129 | static u32 hwmon_attributes[hwmon_max] = { |
130 | [hwmon_chip] = HWMON_C_REGISTER_TZ, |
131 | [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, |
132 | [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, |
133 | [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, |
134 | [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, |
135 | }; |
136 | |
137 | /** |
138 | * struct asus_wmi_sensor_info - sensor info. |
139 | * @id: sensor id. |
140 | * @data_type: sensor class e.g. voltage, temp etc. |
141 | * @location: sensor location. |
142 | * @name: sensor name. |
143 | * @source: sensor source. |
144 | * @type: sensor type signed, unsigned etc. |
145 | * @cached_value: cached sensor value. |
146 | */ |
147 | struct asus_wmi_sensor_info { |
148 | u32 id; |
149 | int data_type; |
150 | int location; |
151 | char name[ASUS_WMI_MAX_STR_SIZE]; |
152 | int source; |
153 | int type; |
154 | long cached_value; |
155 | }; |
156 | |
157 | struct asus_wmi_wmi_info { |
158 | unsigned long source_last_updated[3]; /* in jiffies */ |
159 | int sensor_count; |
160 | |
161 | const struct asus_wmi_sensor_info **info[hwmon_max]; |
162 | struct asus_wmi_sensor_info **info_by_id; |
163 | }; |
164 | |
165 | struct asus_wmi_sensors { |
166 | struct asus_wmi_wmi_info wmi; |
167 | /* lock access to internal cache */ |
168 | struct mutex lock; |
169 | }; |
170 | |
171 | /* |
172 | * Universal method for calling WMI method |
173 | */ |
174 | static int asus_wmi_call_method(u32 method_id, u32 *args, struct acpi_buffer *output) |
175 | { |
176 | struct acpi_buffer input = {(acpi_size) sizeof(*args), args }; |
177 | acpi_status status; |
178 | |
179 | status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, instance: 0, |
180 | method_id, in: &input, out: output); |
181 | if (ACPI_FAILURE(status)) |
182 | return -EIO; |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | /* |
188 | * Gets the version of the ASUS sensors interface implemented |
189 | */ |
190 | static int asus_wmi_get_version(u32 *version) |
191 | { |
192 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
193 | u32 args[] = {0, 0, 0}; |
194 | union acpi_object *obj; |
195 | int err; |
196 | |
197 | err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VERSION, args, output: &output); |
198 | if (err) |
199 | return err; |
200 | |
201 | obj = output.pointer; |
202 | if (!obj) |
203 | return -EIO; |
204 | |
205 | if (obj->type != ACPI_TYPE_INTEGER) { |
206 | err = -EIO; |
207 | goto out_free_obj; |
208 | } |
209 | |
210 | err = 0; |
211 | *version = obj->integer.value; |
212 | |
213 | out_free_obj: |
214 | ACPI_FREE(obj); |
215 | return err; |
216 | } |
217 | |
218 | /* |
219 | * Gets the number of sensor items |
220 | */ |
221 | static int asus_wmi_get_item_count(u32 *count) |
222 | { |
223 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
224 | u32 args[] = {0, 0, 0}; |
225 | union acpi_object *obj; |
226 | int err; |
227 | |
228 | err = asus_wmi_call_method(ASUSWMI_METHODID_GET_NUMBER, args, output: &output); |
229 | if (err) |
230 | return err; |
231 | |
232 | obj = output.pointer; |
233 | if (!obj) |
234 | return -EIO; |
235 | |
236 | if (obj->type != ACPI_TYPE_INTEGER) { |
237 | err = -EIO; |
238 | goto out_free_obj; |
239 | } |
240 | |
241 | err = 0; |
242 | *count = obj->integer.value; |
243 | |
244 | out_free_obj: |
245 | ACPI_FREE(obj); |
246 | return err; |
247 | } |
248 | |
249 | static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, |
250 | struct device *dev, int num, |
251 | enum hwmon_sensor_types type, u32 config) |
252 | { |
253 | u32 *cfg; |
254 | |
255 | cfg = devm_kcalloc(dev, n: num + 1, size: sizeof(*cfg), GFP_KERNEL); |
256 | if (!cfg) |
257 | return -ENOMEM; |
258 | |
259 | asus_wmi_hwmon_chan->type = type; |
260 | asus_wmi_hwmon_chan->config = cfg; |
261 | memset32(s: cfg, v: config, n: num); |
262 | |
263 | return 0; |
264 | } |
265 | |
266 | /* |
267 | * For a given sensor item returns details e.g. type (voltage/temperature/fan speed etc), bank etc |
268 | */ |
269 | static int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s) |
270 | { |
271 | union acpi_object name_obj, data_type_obj, location_obj, source_obj, type_obj; |
272 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
273 | u32 args[] = {index, 0}; |
274 | union acpi_object *obj; |
275 | int err; |
276 | |
277 | err = asus_wmi_call_method(ASUSWMI_METHODID_GET_INFO, args, output: &output); |
278 | if (err) |
279 | return err; |
280 | |
281 | s->id = index; |
282 | |
283 | obj = output.pointer; |
284 | if (!obj) |
285 | return -EIO; |
286 | |
287 | if (obj->type != ACPI_TYPE_PACKAGE) { |
288 | err = -EIO; |
289 | goto out_free_obj; |
290 | } |
291 | |
292 | if (obj->package.count != 5) { |
293 | err = -EIO; |
294 | goto out_free_obj; |
295 | } |
296 | |
297 | name_obj = obj->package.elements[0]; |
298 | if (name_obj.type != ACPI_TYPE_STRING) { |
299 | err = -EIO; |
300 | goto out_free_obj; |
301 | } |
302 | |
303 | strscpy(s->name, name_obj.string.pointer, sizeof(s->name)); |
304 | |
305 | data_type_obj = obj->package.elements[1]; |
306 | if (data_type_obj.type != ACPI_TYPE_INTEGER) { |
307 | err = -EIO; |
308 | goto out_free_obj; |
309 | } |
310 | |
311 | s->data_type = data_type_obj.integer.value; |
312 | |
313 | location_obj = obj->package.elements[2]; |
314 | if (location_obj.type != ACPI_TYPE_INTEGER) { |
315 | err = -EIO; |
316 | goto out_free_obj; |
317 | } |
318 | |
319 | s->location = location_obj.integer.value; |
320 | |
321 | source_obj = obj->package.elements[3]; |
322 | if (source_obj.type != ACPI_TYPE_INTEGER) { |
323 | err = -EIO; |
324 | goto out_free_obj; |
325 | } |
326 | |
327 | s->source = source_obj.integer.value; |
328 | |
329 | type_obj = obj->package.elements[4]; |
330 | if (type_obj.type != ACPI_TYPE_INTEGER) { |
331 | err = -EIO; |
332 | goto out_free_obj; |
333 | } |
334 | |
335 | err = 0; |
336 | s->type = type_obj.integer.value; |
337 | |
338 | out_free_obj: |
339 | ACPI_FREE(obj); |
340 | return err; |
341 | } |
342 | |
343 | static int asus_wmi_update_buffer(int source) |
344 | { |
345 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
346 | u32 args[] = {source, 0}; |
347 | |
348 | return asus_wmi_call_method(ASUSWMI_METHODID_UPDATE_BUFFER, args, output: &output); |
349 | } |
350 | |
351 | static int asus_wmi_get_sensor_value(u8 index, long *value) |
352 | { |
353 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
354 | u32 args[] = {index, 0}; |
355 | union acpi_object *obj; |
356 | int err; |
357 | |
358 | err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VALUE, args, output: &output); |
359 | if (err) |
360 | return err; |
361 | |
362 | obj = output.pointer; |
363 | if (!obj) |
364 | return -EIO; |
365 | |
366 | if (obj->type != ACPI_TYPE_INTEGER) { |
367 | err = -EIO; |
368 | goto out_free_obj; |
369 | } |
370 | |
371 | err = 0; |
372 | *value = obj->integer.value; |
373 | |
374 | out_free_obj: |
375 | ACPI_FREE(obj); |
376 | return err; |
377 | } |
378 | |
379 | static int asus_wmi_update_values_for_source(u8 source, struct asus_wmi_sensors *sensor_data) |
380 | { |
381 | struct asus_wmi_sensor_info *sensor; |
382 | long value = 0; |
383 | int ret; |
384 | int i; |
385 | |
386 | for (i = 0; i < sensor_data->wmi.sensor_count; i++) { |
387 | sensor = sensor_data->wmi.info_by_id[i]; |
388 | if (sensor && sensor->source == source) { |
389 | ret = asus_wmi_get_sensor_value(index: sensor->id, value: &value); |
390 | if (ret) |
391 | return ret; |
392 | |
393 | sensor->cached_value = value; |
394 | } |
395 | } |
396 | |
397 | return 0; |
398 | } |
399 | |
400 | static int asus_wmi_scale_sensor_value(u32 value, int data_type) |
401 | { |
402 | /* FAN_RPM and WATER_FLOW don't need scaling */ |
403 | switch (data_type) { |
404 | case VOLTAGE: |
405 | /* value in microVolts */ |
406 | return DIV_ROUND_CLOSEST(value, KILO); |
407 | case TEMPERATURE_C: |
408 | /* value in Celsius */ |
409 | return value * MILLIDEGREE_PER_DEGREE; |
410 | case CURRENT: |
411 | /* value in Amperes */ |
412 | return value * MILLI; |
413 | } |
414 | return value; |
415 | } |
416 | |
417 | static int asus_wmi_get_cached_value_or_update(const struct asus_wmi_sensor_info *sensor, |
418 | struct asus_wmi_sensors *sensor_data, |
419 | u32 *value) |
420 | { |
421 | int ret = 0; |
422 | |
423 | mutex_lock(&sensor_data->lock); |
424 | |
425 | if (time_after(jiffies, sensor_data->wmi.source_last_updated[sensor->source] + HZ)) { |
426 | ret = asus_wmi_update_buffer(source: sensor->source); |
427 | if (ret) |
428 | goto unlock; |
429 | |
430 | ret = asus_wmi_update_values_for_source(source: sensor->source, sensor_data); |
431 | if (ret) |
432 | goto unlock; |
433 | |
434 | sensor_data->wmi.source_last_updated[sensor->source] = jiffies; |
435 | } |
436 | |
437 | *value = sensor->cached_value; |
438 | |
439 | unlock: |
440 | mutex_unlock(lock: &sensor_data->lock); |
441 | |
442 | return ret; |
443 | } |
444 | |
445 | /* Now follow the functions that implement the hwmon interface */ |
446 | static int asus_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, |
447 | u32 attr, int channel, long *val) |
448 | { |
449 | const struct asus_wmi_sensor_info *sensor; |
450 | u32 value = 0; |
451 | int ret; |
452 | |
453 | struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); |
454 | |
455 | sensor = *(sensor_data->wmi.info[type] + channel); |
456 | |
457 | ret = asus_wmi_get_cached_value_or_update(sensor, sensor_data, value: &value); |
458 | if (ret) |
459 | return ret; |
460 | |
461 | *val = asus_wmi_scale_sensor_value(value, data_type: sensor->data_type); |
462 | |
463 | return ret; |
464 | } |
465 | |
466 | static int asus_wmi_hwmon_read_string(struct device *dev, |
467 | enum hwmon_sensor_types type, u32 attr, |
468 | int channel, const char **str) |
469 | { |
470 | struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); |
471 | const struct asus_wmi_sensor_info *sensor; |
472 | |
473 | sensor = *(sensor_data->wmi.info[type] + channel); |
474 | *str = sensor->name; |
475 | |
476 | return 0; |
477 | } |
478 | |
479 | static umode_t asus_wmi_hwmon_is_visible(const void *drvdata, |
480 | enum hwmon_sensor_types type, u32 attr, |
481 | int channel) |
482 | { |
483 | const struct asus_wmi_sensors *sensor_data = drvdata; |
484 | const struct asus_wmi_sensor_info *sensor; |
485 | |
486 | sensor = *(sensor_data->wmi.info[type] + channel); |
487 | if (sensor) |
488 | return 0444; |
489 | |
490 | return 0; |
491 | } |
492 | |
493 | static const struct hwmon_ops asus_wmi_hwmon_ops = { |
494 | .is_visible = asus_wmi_hwmon_is_visible, |
495 | .read = asus_wmi_hwmon_read, |
496 | .read_string = asus_wmi_hwmon_read_string, |
497 | }; |
498 | |
499 | static struct hwmon_chip_info asus_wmi_chip_info = { |
500 | .ops = &asus_wmi_hwmon_ops, |
501 | .info = NULL, |
502 | }; |
503 | |
504 | static int asus_wmi_configure_sensor_setup(struct device *dev, |
505 | struct asus_wmi_sensors *sensor_data) |
506 | { |
507 | const struct hwmon_channel_info **ptr_asus_wmi_ci; |
508 | struct hwmon_channel_info *asus_wmi_hwmon_chan; |
509 | int nr_count[hwmon_max] = {}, nr_types = 0; |
510 | struct asus_wmi_sensor_info *temp_sensor; |
511 | const struct hwmon_chip_info *chip_info; |
512 | enum hwmon_sensor_types type; |
513 | struct device *hwdev; |
514 | int i, idx; |
515 | int err; |
516 | |
517 | for (i = 0; i < sensor_data->wmi.sensor_count; i++) { |
518 | struct asus_wmi_sensor_info sensor; |
519 | |
520 | err = asus_wmi_sensor_info(index: i, s: &sensor); |
521 | if (err) |
522 | return err; |
523 | |
524 | switch (sensor.data_type) { |
525 | case TEMPERATURE_C: |
526 | case VOLTAGE: |
527 | case CURRENT: |
528 | case FAN_RPM: |
529 | case WATER_FLOW: |
530 | type = asus_data_types[sensor.data_type]; |
531 | if (!nr_count[type]) |
532 | nr_types++; |
533 | nr_count[type]++; |
534 | break; |
535 | } |
536 | } |
537 | |
538 | if (nr_count[hwmon_temp]) |
539 | nr_count[hwmon_chip]++, nr_types++; |
540 | |
541 | asus_wmi_hwmon_chan = devm_kcalloc(dev, n: nr_types, |
542 | size: sizeof(*asus_wmi_hwmon_chan), |
543 | GFP_KERNEL); |
544 | if (!asus_wmi_hwmon_chan) |
545 | return -ENOMEM; |
546 | |
547 | ptr_asus_wmi_ci = devm_kcalloc(dev, n: nr_types + 1, |
548 | size: sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); |
549 | if (!ptr_asus_wmi_ci) |
550 | return -ENOMEM; |
551 | |
552 | asus_wmi_chip_info.info = ptr_asus_wmi_ci; |
553 | chip_info = &asus_wmi_chip_info; |
554 | |
555 | sensor_data->wmi.info_by_id = devm_kcalloc(dev, n: sensor_data->wmi.sensor_count, |
556 | size: sizeof(*sensor_data->wmi.info_by_id), |
557 | GFP_KERNEL); |
558 | |
559 | if (!sensor_data->wmi.info_by_id) |
560 | return -ENOMEM; |
561 | |
562 | for (type = 0; type < hwmon_max; type++) { |
563 | if (!nr_count[type]) |
564 | continue; |
565 | |
566 | err = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, |
567 | num: nr_count[type], type, |
568 | config: hwmon_attributes[type]); |
569 | if (err) |
570 | return err; |
571 | |
572 | *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; |
573 | |
574 | sensor_data->wmi.info[type] = devm_kcalloc(dev, |
575 | n: nr_count[type], |
576 | size: sizeof(*sensor_data->wmi.info), |
577 | GFP_KERNEL); |
578 | if (!sensor_data->wmi.info[type]) |
579 | return -ENOMEM; |
580 | } |
581 | |
582 | for (i = sensor_data->wmi.sensor_count - 1; i >= 0; i--) { |
583 | temp_sensor = devm_kzalloc(dev, size: sizeof(*temp_sensor), GFP_KERNEL); |
584 | if (!temp_sensor) |
585 | return -ENOMEM; |
586 | |
587 | err = asus_wmi_sensor_info(index: i, s: temp_sensor); |
588 | if (err) |
589 | continue; |
590 | |
591 | switch (temp_sensor->data_type) { |
592 | case TEMPERATURE_C: |
593 | case VOLTAGE: |
594 | case CURRENT: |
595 | case FAN_RPM: |
596 | case WATER_FLOW: |
597 | type = asus_data_types[temp_sensor->data_type]; |
598 | idx = --nr_count[type]; |
599 | *(sensor_data->wmi.info[type] + idx) = temp_sensor; |
600 | sensor_data->wmi.info_by_id[i] = temp_sensor; |
601 | break; |
602 | } |
603 | } |
604 | |
605 | dev_dbg(dev, "board has %d sensors" , |
606 | sensor_data->wmi.sensor_count); |
607 | |
608 | hwdev = devm_hwmon_device_register_with_info(dev, name: "asus_wmi_sensors" , |
609 | drvdata: sensor_data, info: chip_info, NULL); |
610 | |
611 | return PTR_ERR_OR_ZERO(ptr: hwdev); |
612 | } |
613 | |
614 | static int asus_wmi_probe(struct wmi_device *wdev, const void *context) |
615 | { |
616 | struct asus_wmi_sensors *sensor_data; |
617 | struct device *dev = &wdev->dev; |
618 | u32 version = 0; |
619 | |
620 | if (!dmi_check_system(list: asus_wmi_dmi_table)) |
621 | return -ENODEV; |
622 | |
623 | sensor_data = devm_kzalloc(dev, size: sizeof(*sensor_data), GFP_KERNEL); |
624 | if (!sensor_data) |
625 | return -ENOMEM; |
626 | |
627 | if (asus_wmi_get_version(version: &version)) |
628 | return -ENODEV; |
629 | |
630 | if (asus_wmi_get_item_count(count: &sensor_data->wmi.sensor_count)) |
631 | return -ENODEV; |
632 | |
633 | if (sensor_data->wmi.sensor_count <= 0 || version < 2) { |
634 | dev_info(dev, "version: %u with %d sensors is unsupported\n" , |
635 | version, sensor_data->wmi.sensor_count); |
636 | |
637 | return -ENODEV; |
638 | } |
639 | |
640 | mutex_init(&sensor_data->lock); |
641 | |
642 | dev_set_drvdata(dev, data: sensor_data); |
643 | |
644 | return asus_wmi_configure_sensor_setup(dev, sensor_data); |
645 | } |
646 | |
647 | static const struct wmi_device_id asus_wmi_id_table[] = { |
648 | { ASUSWMI_MONITORING_GUID, NULL }, |
649 | { } |
650 | }; |
651 | |
652 | static struct wmi_driver asus_sensors_wmi_driver = { |
653 | .driver = { |
654 | .name = "asus_wmi_sensors" , |
655 | }, |
656 | .id_table = asus_wmi_id_table, |
657 | .probe = asus_wmi_probe, |
658 | }; |
659 | module_wmi_driver(asus_sensors_wmi_driver); |
660 | |
661 | MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>" ); |
662 | MODULE_DESCRIPTION("Asus WMI Sensors Driver" ); |
663 | MODULE_LICENSE("GPL" ); |
664 | |