664 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * HWMON driver for ASUS motherboards that provides sensor readouts via WMI4 * 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 3262 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 70static 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};89MODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table);90 91enum asus_wmi_sensor_class {92 VOLTAGE = 0x0,93 TEMPERATURE_C = 0x1,94 FAN_RPM = 0x2,95 CURRENT = 0x3,96 WATER_FLOW = 0x4,97};98 99enum 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 = 0x7108};109 110enum asus_wmi_type {111 SIGNED_INT = 0x0,112 UNSIGNED_INT = 0x1,113 SCALED = 0x3,114};115 116enum asus_wmi_source {117 SIO = 0x1,118 EC = 0x2119};120 121static 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 129static 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 */147struct 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 157struct 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 165struct 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 method173 */174static 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, 0,180 method_id, &input, 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 implemented189 */190static 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);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 213out_free_obj:214 ACPI_FREE(obj);215 return err;216}217 218/*219 * Gets the number of sensor items220 */221static 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);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 244out_free_obj:245 ACPI_FREE(obj);246 return err;247}248 249static 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, num + 1, 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(cfg, config, 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 etc268 */269static 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);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 338out_free_obj:339 ACPI_FREE(obj);340 return err;341}342 343static 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);349}350 351static 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);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 374out_free_obj:375 ACPI_FREE(obj);376 return err;377}378 379static 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(sensor->id, &value);390 if (ret)391 return ret;392 393 sensor->cached_value = value;394 }395 }396 397 return 0;398}399 400static 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 417static 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(sensor->source);427 if (ret)428 goto unlock;429 430 ret = asus_wmi_update_values_for_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 439unlock:440 mutex_unlock(&sensor_data->lock);441 442 return ret;443}444 445/* Now follow the functions that implement the hwmon interface */446static 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);458 if (ret)459 return ret;460 461 *val = asus_wmi_scale_sensor_value(value, sensor->data_type);462 463 return ret;464}465 466static 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 479static 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 493static 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 499static struct hwmon_chip_info asus_wmi_chip_info = {500 .ops = &asus_wmi_hwmon_ops,501 .info = NULL,502};503 504static 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(i, &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, nr_types,542 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, nr_types + 1,548 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, sensor_data->wmi.sensor_count,556 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 nr_count[type], type,568 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 nr_count[type],576 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, sizeof(*temp_sensor), GFP_KERNEL);584 if (!temp_sensor)585 return -ENOMEM;586 587 err = asus_wmi_sensor_info(i, 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, "asus_wmi_sensors",609 sensor_data, chip_info, NULL);610 611 return PTR_ERR_OR_ZERO(hwdev);612}613 614static 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(asus_wmi_dmi_table))621 return -ENODEV;622 623 sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL);624 if (!sensor_data)625 return -ENOMEM;626 627 if (asus_wmi_get_version(&version))628 return -ENODEV;629 630 if (asus_wmi_get_item_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, sensor_data);643 644 return asus_wmi_configure_sensor_setup(dev, sensor_data);645}646 647static const struct wmi_device_id asus_wmi_id_table[] = {648 { ASUSWMI_MONITORING_GUID, NULL },649 { }650};651 652static 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};659module_wmi_driver(asus_sensors_wmi_driver);660 661MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>");662MODULE_DESCRIPTION("Asus WMI Sensors Driver");663MODULE_LICENSE("GPL");664