162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * HWMON driver for ASUS motherboards that provides sensor readouts via WMI 462306a36Sopenharmony_ci * interface present in the UEFI of the X370/X470/B450/X399 Ryzen motherboards. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * WMI interface provides: 962306a36Sopenharmony_ci * - CPU Core Voltage, 1062306a36Sopenharmony_ci * - CPU SOC Voltage, 1162306a36Sopenharmony_ci * - DRAM Voltage, 1262306a36Sopenharmony_ci * - VDDP Voltage, 1362306a36Sopenharmony_ci * - 1.8V PLL Voltage, 1462306a36Sopenharmony_ci * - +12V Voltage, 1562306a36Sopenharmony_ci * - +5V Voltage, 1662306a36Sopenharmony_ci * - 3VSB Voltage, 1762306a36Sopenharmony_ci * - VBAT Voltage, 1862306a36Sopenharmony_ci * - AVCC3 Voltage, 1962306a36Sopenharmony_ci * - SB 1.05V Voltage, 2062306a36Sopenharmony_ci * - CPU Core Voltage, 2162306a36Sopenharmony_ci * - CPU SOC Voltage, 2262306a36Sopenharmony_ci * - DRAM Voltage, 2362306a36Sopenharmony_ci * - CPU Fan RPM, 2462306a36Sopenharmony_ci * - Chassis Fan 1 RPM, 2562306a36Sopenharmony_ci * - Chassis Fan 2 RPM, 2662306a36Sopenharmony_ci * - Chassis Fan 3 RPM, 2762306a36Sopenharmony_ci * - HAMP Fan RPM, 2862306a36Sopenharmony_ci * - Water Pump RPM, 2962306a36Sopenharmony_ci * - CPU OPT RPM, 3062306a36Sopenharmony_ci * - Water Flow RPM, 3162306a36Sopenharmony_ci * - AIO Pump RPM, 3262306a36Sopenharmony_ci * - CPU Temperature, 3362306a36Sopenharmony_ci * - CPU Socket Temperature, 3462306a36Sopenharmony_ci * - Motherboard Temperature, 3562306a36Sopenharmony_ci * - Chipset Temperature, 3662306a36Sopenharmony_ci * - Tsensor 1 Temperature, 3762306a36Sopenharmony_ci * - CPU VRM Temperature, 3862306a36Sopenharmony_ci * - Water In, 3962306a36Sopenharmony_ci * - Water Out, 4062306a36Sopenharmony_ci * - CPU VRM Output Current. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#include <linux/acpi.h> 4462306a36Sopenharmony_ci#include <linux/dmi.h> 4562306a36Sopenharmony_ci#include <linux/hwmon.h> 4662306a36Sopenharmony_ci#include <linux/init.h> 4762306a36Sopenharmony_ci#include <linux/jiffies.h> 4862306a36Sopenharmony_ci#include <linux/kernel.h> 4962306a36Sopenharmony_ci#include <linux/module.h> 5062306a36Sopenharmony_ci#include <linux/mutex.h> 5162306a36Sopenharmony_ci#include <linux/units.h> 5262306a36Sopenharmony_ci#include <linux/wmi.h> 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" 5562306a36Sopenharmony_ci#define ASUSWMI_METHODID_GET_VALUE 0x52574543 /* RWEC */ 5662306a36Sopenharmony_ci#define ASUSWMI_METHODID_UPDATE_BUFFER 0x51574543 /* QWEC */ 5762306a36Sopenharmony_ci#define ASUSWMI_METHODID_GET_INFO 0x50574543 /* PWEC */ 5862306a36Sopenharmony_ci#define ASUSWMI_METHODID_GET_NUMBER 0x50574572 /* PWEr */ 5962306a36Sopenharmony_ci#define ASUSWMI_METHODID_GET_VERSION 0x50574574 /* PWEt */ 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#define ASUS_WMI_MAX_STR_SIZE 32 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name) { \ 6462306a36Sopenharmony_ci .matches = { \ 6562306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ 6662306a36Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ 6762306a36Sopenharmony_ci }, \ 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic const struct dmi_system_id asus_wmi_dmi_table[] = { 7162306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A"), 7262306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO"), 7362306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI EXTREME"), 7462306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("CROSSHAIR VI HERO"), 7562306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO (WI-FI AC)"), 7662306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO"), 7762306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"), 7862306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"), 7962306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"), 8062306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING II"), 8162306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"), 8262306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"), 8362306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"), 8462306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING"), 8562306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME"), 8662306a36Sopenharmony_ci DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME ALPHA"), 8762306a36Sopenharmony_ci {} 8862306a36Sopenharmony_ci}; 8962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cienum asus_wmi_sensor_class { 9262306a36Sopenharmony_ci VOLTAGE = 0x0, 9362306a36Sopenharmony_ci TEMPERATURE_C = 0x1, 9462306a36Sopenharmony_ci FAN_RPM = 0x2, 9562306a36Sopenharmony_ci CURRENT = 0x3, 9662306a36Sopenharmony_ci WATER_FLOW = 0x4, 9762306a36Sopenharmony_ci}; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cienum asus_wmi_location { 10062306a36Sopenharmony_ci CPU = 0x0, 10162306a36Sopenharmony_ci CPU_SOC = 0x1, 10262306a36Sopenharmony_ci DRAM = 0x2, 10362306a36Sopenharmony_ci MOTHERBOARD = 0x3, 10462306a36Sopenharmony_ci CHIPSET = 0x4, 10562306a36Sopenharmony_ci AUX = 0x5, 10662306a36Sopenharmony_ci VRM = 0x6, 10762306a36Sopenharmony_ci COOLER = 0x7 10862306a36Sopenharmony_ci}; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cienum asus_wmi_type { 11162306a36Sopenharmony_ci SIGNED_INT = 0x0, 11262306a36Sopenharmony_ci UNSIGNED_INT = 0x1, 11362306a36Sopenharmony_ci SCALED = 0x3, 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cienum asus_wmi_source { 11762306a36Sopenharmony_ci SIO = 0x1, 11862306a36Sopenharmony_ci EC = 0x2 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic enum hwmon_sensor_types asus_data_types[] = { 12262306a36Sopenharmony_ci [VOLTAGE] = hwmon_in, 12362306a36Sopenharmony_ci [TEMPERATURE_C] = hwmon_temp, 12462306a36Sopenharmony_ci [FAN_RPM] = hwmon_fan, 12562306a36Sopenharmony_ci [CURRENT] = hwmon_curr, 12662306a36Sopenharmony_ci [WATER_FLOW] = hwmon_fan, 12762306a36Sopenharmony_ci}; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic u32 hwmon_attributes[hwmon_max] = { 13062306a36Sopenharmony_ci [hwmon_chip] = HWMON_C_REGISTER_TZ, 13162306a36Sopenharmony_ci [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, 13262306a36Sopenharmony_ci [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, 13362306a36Sopenharmony_ci [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, 13462306a36Sopenharmony_ci [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, 13562306a36Sopenharmony_ci}; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci/** 13862306a36Sopenharmony_ci * struct asus_wmi_sensor_info - sensor info. 13962306a36Sopenharmony_ci * @id: sensor id. 14062306a36Sopenharmony_ci * @data_type: sensor class e.g. voltage, temp etc. 14162306a36Sopenharmony_ci * @location: sensor location. 14262306a36Sopenharmony_ci * @name: sensor name. 14362306a36Sopenharmony_ci * @source: sensor source. 14462306a36Sopenharmony_ci * @type: sensor type signed, unsigned etc. 14562306a36Sopenharmony_ci * @cached_value: cached sensor value. 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_cistruct asus_wmi_sensor_info { 14862306a36Sopenharmony_ci u32 id; 14962306a36Sopenharmony_ci int data_type; 15062306a36Sopenharmony_ci int location; 15162306a36Sopenharmony_ci char name[ASUS_WMI_MAX_STR_SIZE]; 15262306a36Sopenharmony_ci int source; 15362306a36Sopenharmony_ci int type; 15462306a36Sopenharmony_ci long cached_value; 15562306a36Sopenharmony_ci}; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistruct asus_wmi_wmi_info { 15862306a36Sopenharmony_ci unsigned long source_last_updated[3]; /* in jiffies */ 15962306a36Sopenharmony_ci int sensor_count; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci const struct asus_wmi_sensor_info **info[hwmon_max]; 16262306a36Sopenharmony_ci struct asus_wmi_sensor_info **info_by_id; 16362306a36Sopenharmony_ci}; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistruct asus_wmi_sensors { 16662306a36Sopenharmony_ci struct asus_wmi_wmi_info wmi; 16762306a36Sopenharmony_ci /* lock access to internal cache */ 16862306a36Sopenharmony_ci struct mutex lock; 16962306a36Sopenharmony_ci}; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci/* 17262306a36Sopenharmony_ci * Universal method for calling WMI method 17362306a36Sopenharmony_ci */ 17462306a36Sopenharmony_cistatic int asus_wmi_call_method(u32 method_id, u32 *args, struct acpi_buffer *output) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct acpi_buffer input = {(acpi_size) sizeof(*args), args }; 17762306a36Sopenharmony_ci acpi_status status; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, 18062306a36Sopenharmony_ci method_id, &input, output); 18162306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 18262306a36Sopenharmony_ci return -EIO; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci return 0; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci/* 18862306a36Sopenharmony_ci * Gets the version of the ASUS sensors interface implemented 18962306a36Sopenharmony_ci */ 19062306a36Sopenharmony_cistatic int asus_wmi_get_version(u32 *version) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 19362306a36Sopenharmony_ci u32 args[] = {0, 0, 0}; 19462306a36Sopenharmony_ci union acpi_object *obj; 19562306a36Sopenharmony_ci int err; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VERSION, args, &output); 19862306a36Sopenharmony_ci if (err) 19962306a36Sopenharmony_ci return err; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci obj = output.pointer; 20262306a36Sopenharmony_ci if (!obj) 20362306a36Sopenharmony_ci return -EIO; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci if (obj->type != ACPI_TYPE_INTEGER) { 20662306a36Sopenharmony_ci err = -EIO; 20762306a36Sopenharmony_ci goto out_free_obj; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci err = 0; 21162306a36Sopenharmony_ci *version = obj->integer.value; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ciout_free_obj: 21462306a36Sopenharmony_ci ACPI_FREE(obj); 21562306a36Sopenharmony_ci return err; 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci/* 21962306a36Sopenharmony_ci * Gets the number of sensor items 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_cistatic int asus_wmi_get_item_count(u32 *count) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 22462306a36Sopenharmony_ci u32 args[] = {0, 0, 0}; 22562306a36Sopenharmony_ci union acpi_object *obj; 22662306a36Sopenharmony_ci int err; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci err = asus_wmi_call_method(ASUSWMI_METHODID_GET_NUMBER, args, &output); 22962306a36Sopenharmony_ci if (err) 23062306a36Sopenharmony_ci return err; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci obj = output.pointer; 23362306a36Sopenharmony_ci if (!obj) 23462306a36Sopenharmony_ci return -EIO; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci if (obj->type != ACPI_TYPE_INTEGER) { 23762306a36Sopenharmony_ci err = -EIO; 23862306a36Sopenharmony_ci goto out_free_obj; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci err = 0; 24262306a36Sopenharmony_ci *count = obj->integer.value; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ciout_free_obj: 24562306a36Sopenharmony_ci ACPI_FREE(obj); 24662306a36Sopenharmony_ci return err; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, 25062306a36Sopenharmony_ci struct device *dev, int num, 25162306a36Sopenharmony_ci enum hwmon_sensor_types type, u32 config) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci u32 *cfg; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); 25662306a36Sopenharmony_ci if (!cfg) 25762306a36Sopenharmony_ci return -ENOMEM; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci asus_wmi_hwmon_chan->type = type; 26062306a36Sopenharmony_ci asus_wmi_hwmon_chan->config = cfg; 26162306a36Sopenharmony_ci memset32(cfg, config, num); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci/* 26762306a36Sopenharmony_ci * For a given sensor item returns details e.g. type (voltage/temperature/fan speed etc), bank etc 26862306a36Sopenharmony_ci */ 26962306a36Sopenharmony_cistatic int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s) 27062306a36Sopenharmony_ci{ 27162306a36Sopenharmony_ci union acpi_object name_obj, data_type_obj, location_obj, source_obj, type_obj; 27262306a36Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 27362306a36Sopenharmony_ci u32 args[] = {index, 0}; 27462306a36Sopenharmony_ci union acpi_object *obj; 27562306a36Sopenharmony_ci int err; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci err = asus_wmi_call_method(ASUSWMI_METHODID_GET_INFO, args, &output); 27862306a36Sopenharmony_ci if (err) 27962306a36Sopenharmony_ci return err; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci s->id = index; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci obj = output.pointer; 28462306a36Sopenharmony_ci if (!obj) 28562306a36Sopenharmony_ci return -EIO; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci if (obj->type != ACPI_TYPE_PACKAGE) { 28862306a36Sopenharmony_ci err = -EIO; 28962306a36Sopenharmony_ci goto out_free_obj; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (obj->package.count != 5) { 29362306a36Sopenharmony_ci err = -EIO; 29462306a36Sopenharmony_ci goto out_free_obj; 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci name_obj = obj->package.elements[0]; 29862306a36Sopenharmony_ci if (name_obj.type != ACPI_TYPE_STRING) { 29962306a36Sopenharmony_ci err = -EIO; 30062306a36Sopenharmony_ci goto out_free_obj; 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci strncpy(s->name, name_obj.string.pointer, sizeof(s->name) - 1); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci data_type_obj = obj->package.elements[1]; 30662306a36Sopenharmony_ci if (data_type_obj.type != ACPI_TYPE_INTEGER) { 30762306a36Sopenharmony_ci err = -EIO; 30862306a36Sopenharmony_ci goto out_free_obj; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci s->data_type = data_type_obj.integer.value; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci location_obj = obj->package.elements[2]; 31462306a36Sopenharmony_ci if (location_obj.type != ACPI_TYPE_INTEGER) { 31562306a36Sopenharmony_ci err = -EIO; 31662306a36Sopenharmony_ci goto out_free_obj; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci s->location = location_obj.integer.value; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci source_obj = obj->package.elements[3]; 32262306a36Sopenharmony_ci if (source_obj.type != ACPI_TYPE_INTEGER) { 32362306a36Sopenharmony_ci err = -EIO; 32462306a36Sopenharmony_ci goto out_free_obj; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci s->source = source_obj.integer.value; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci type_obj = obj->package.elements[4]; 33062306a36Sopenharmony_ci if (type_obj.type != ACPI_TYPE_INTEGER) { 33162306a36Sopenharmony_ci err = -EIO; 33262306a36Sopenharmony_ci goto out_free_obj; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci err = 0; 33662306a36Sopenharmony_ci s->type = type_obj.integer.value; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ciout_free_obj: 33962306a36Sopenharmony_ci ACPI_FREE(obj); 34062306a36Sopenharmony_ci return err; 34162306a36Sopenharmony_ci} 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_cistatic int asus_wmi_update_buffer(int source) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 34662306a36Sopenharmony_ci u32 args[] = {source, 0}; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return asus_wmi_call_method(ASUSWMI_METHODID_UPDATE_BUFFER, args, &output); 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic int asus_wmi_get_sensor_value(u8 index, long *value) 35262306a36Sopenharmony_ci{ 35362306a36Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 35462306a36Sopenharmony_ci u32 args[] = {index, 0}; 35562306a36Sopenharmony_ci union acpi_object *obj; 35662306a36Sopenharmony_ci int err; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VALUE, args, &output); 35962306a36Sopenharmony_ci if (err) 36062306a36Sopenharmony_ci return err; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci obj = output.pointer; 36362306a36Sopenharmony_ci if (!obj) 36462306a36Sopenharmony_ci return -EIO; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (obj->type != ACPI_TYPE_INTEGER) { 36762306a36Sopenharmony_ci err = -EIO; 36862306a36Sopenharmony_ci goto out_free_obj; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci err = 0; 37262306a36Sopenharmony_ci *value = obj->integer.value; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ciout_free_obj: 37562306a36Sopenharmony_ci ACPI_FREE(obj); 37662306a36Sopenharmony_ci return err; 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic int asus_wmi_update_values_for_source(u8 source, struct asus_wmi_sensors *sensor_data) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci struct asus_wmi_sensor_info *sensor; 38262306a36Sopenharmony_ci long value = 0; 38362306a36Sopenharmony_ci int ret; 38462306a36Sopenharmony_ci int i; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci for (i = 0; i < sensor_data->wmi.sensor_count; i++) { 38762306a36Sopenharmony_ci sensor = sensor_data->wmi.info_by_id[i]; 38862306a36Sopenharmony_ci if (sensor && sensor->source == source) { 38962306a36Sopenharmony_ci ret = asus_wmi_get_sensor_value(sensor->id, &value); 39062306a36Sopenharmony_ci if (ret) 39162306a36Sopenharmony_ci return ret; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci sensor->cached_value = value; 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci return 0; 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_cistatic int asus_wmi_scale_sensor_value(u32 value, int data_type) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci /* FAN_RPM and WATER_FLOW don't need scaling */ 40362306a36Sopenharmony_ci switch (data_type) { 40462306a36Sopenharmony_ci case VOLTAGE: 40562306a36Sopenharmony_ci /* value in microVolts */ 40662306a36Sopenharmony_ci return DIV_ROUND_CLOSEST(value, KILO); 40762306a36Sopenharmony_ci case TEMPERATURE_C: 40862306a36Sopenharmony_ci /* value in Celsius */ 40962306a36Sopenharmony_ci return value * MILLIDEGREE_PER_DEGREE; 41062306a36Sopenharmony_ci case CURRENT: 41162306a36Sopenharmony_ci /* value in Amperes */ 41262306a36Sopenharmony_ci return value * MILLI; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci return value; 41562306a36Sopenharmony_ci} 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic int asus_wmi_get_cached_value_or_update(const struct asus_wmi_sensor_info *sensor, 41862306a36Sopenharmony_ci struct asus_wmi_sensors *sensor_data, 41962306a36Sopenharmony_ci u32 *value) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci int ret = 0; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci mutex_lock(&sensor_data->lock); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci if (time_after(jiffies, sensor_data->wmi.source_last_updated[sensor->source] + HZ)) { 42662306a36Sopenharmony_ci ret = asus_wmi_update_buffer(sensor->source); 42762306a36Sopenharmony_ci if (ret) 42862306a36Sopenharmony_ci goto unlock; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci ret = asus_wmi_update_values_for_source(sensor->source, sensor_data); 43162306a36Sopenharmony_ci if (ret) 43262306a36Sopenharmony_ci goto unlock; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci sensor_data->wmi.source_last_updated[sensor->source] = jiffies; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci *value = sensor->cached_value; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ciunlock: 44062306a36Sopenharmony_ci mutex_unlock(&sensor_data->lock); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci return ret; 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci/* Now follow the functions that implement the hwmon interface */ 44662306a36Sopenharmony_cistatic int asus_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 44762306a36Sopenharmony_ci u32 attr, int channel, long *val) 44862306a36Sopenharmony_ci{ 44962306a36Sopenharmony_ci const struct asus_wmi_sensor_info *sensor; 45062306a36Sopenharmony_ci u32 value = 0; 45162306a36Sopenharmony_ci int ret; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci sensor = *(sensor_data->wmi.info[type] + channel); 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci ret = asus_wmi_get_cached_value_or_update(sensor, sensor_data, &value); 45862306a36Sopenharmony_ci if (ret) 45962306a36Sopenharmony_ci return ret; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci *val = asus_wmi_scale_sensor_value(value, sensor->data_type); 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci return ret; 46462306a36Sopenharmony_ci} 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic int asus_wmi_hwmon_read_string(struct device *dev, 46762306a36Sopenharmony_ci enum hwmon_sensor_types type, u32 attr, 46862306a36Sopenharmony_ci int channel, const char **str) 46962306a36Sopenharmony_ci{ 47062306a36Sopenharmony_ci struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); 47162306a36Sopenharmony_ci const struct asus_wmi_sensor_info *sensor; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci sensor = *(sensor_data->wmi.info[type] + channel); 47462306a36Sopenharmony_ci *str = sensor->name; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci return 0; 47762306a36Sopenharmony_ci} 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic umode_t asus_wmi_hwmon_is_visible(const void *drvdata, 48062306a36Sopenharmony_ci enum hwmon_sensor_types type, u32 attr, 48162306a36Sopenharmony_ci int channel) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci const struct asus_wmi_sensors *sensor_data = drvdata; 48462306a36Sopenharmony_ci const struct asus_wmi_sensor_info *sensor; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci sensor = *(sensor_data->wmi.info[type] + channel); 48762306a36Sopenharmony_ci if (sensor) 48862306a36Sopenharmony_ci return 0444; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci return 0; 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_cistatic const struct hwmon_ops asus_wmi_hwmon_ops = { 49462306a36Sopenharmony_ci .is_visible = asus_wmi_hwmon_is_visible, 49562306a36Sopenharmony_ci .read = asus_wmi_hwmon_read, 49662306a36Sopenharmony_ci .read_string = asus_wmi_hwmon_read_string, 49762306a36Sopenharmony_ci}; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_cistatic struct hwmon_chip_info asus_wmi_chip_info = { 50062306a36Sopenharmony_ci .ops = &asus_wmi_hwmon_ops, 50162306a36Sopenharmony_ci .info = NULL, 50262306a36Sopenharmony_ci}; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_cistatic int asus_wmi_configure_sensor_setup(struct device *dev, 50562306a36Sopenharmony_ci struct asus_wmi_sensors *sensor_data) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci const struct hwmon_channel_info **ptr_asus_wmi_ci; 50862306a36Sopenharmony_ci struct hwmon_channel_info *asus_wmi_hwmon_chan; 50962306a36Sopenharmony_ci int nr_count[hwmon_max] = {}, nr_types = 0; 51062306a36Sopenharmony_ci struct asus_wmi_sensor_info *temp_sensor; 51162306a36Sopenharmony_ci const struct hwmon_chip_info *chip_info; 51262306a36Sopenharmony_ci enum hwmon_sensor_types type; 51362306a36Sopenharmony_ci struct device *hwdev; 51462306a36Sopenharmony_ci int i, idx; 51562306a36Sopenharmony_ci int err; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci for (i = 0; i < sensor_data->wmi.sensor_count; i++) { 51862306a36Sopenharmony_ci struct asus_wmi_sensor_info sensor; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci err = asus_wmi_sensor_info(i, &sensor); 52162306a36Sopenharmony_ci if (err) 52262306a36Sopenharmony_ci return err; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci switch (sensor.data_type) { 52562306a36Sopenharmony_ci case TEMPERATURE_C: 52662306a36Sopenharmony_ci case VOLTAGE: 52762306a36Sopenharmony_ci case CURRENT: 52862306a36Sopenharmony_ci case FAN_RPM: 52962306a36Sopenharmony_ci case WATER_FLOW: 53062306a36Sopenharmony_ci type = asus_data_types[sensor.data_type]; 53162306a36Sopenharmony_ci if (!nr_count[type]) 53262306a36Sopenharmony_ci nr_types++; 53362306a36Sopenharmony_ci nr_count[type]++; 53462306a36Sopenharmony_ci break; 53562306a36Sopenharmony_ci } 53662306a36Sopenharmony_ci } 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci if (nr_count[hwmon_temp]) 53962306a36Sopenharmony_ci nr_count[hwmon_chip]++, nr_types++; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, 54262306a36Sopenharmony_ci sizeof(*asus_wmi_hwmon_chan), 54362306a36Sopenharmony_ci GFP_KERNEL); 54462306a36Sopenharmony_ci if (!asus_wmi_hwmon_chan) 54562306a36Sopenharmony_ci return -ENOMEM; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, 54862306a36Sopenharmony_ci sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); 54962306a36Sopenharmony_ci if (!ptr_asus_wmi_ci) 55062306a36Sopenharmony_ci return -ENOMEM; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci asus_wmi_chip_info.info = ptr_asus_wmi_ci; 55362306a36Sopenharmony_ci chip_info = &asus_wmi_chip_info; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci sensor_data->wmi.info_by_id = devm_kcalloc(dev, sensor_data->wmi.sensor_count, 55662306a36Sopenharmony_ci sizeof(*sensor_data->wmi.info_by_id), 55762306a36Sopenharmony_ci GFP_KERNEL); 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci if (!sensor_data->wmi.info_by_id) 56062306a36Sopenharmony_ci return -ENOMEM; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci for (type = 0; type < hwmon_max; type++) { 56362306a36Sopenharmony_ci if (!nr_count[type]) 56462306a36Sopenharmony_ci continue; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci err = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, 56762306a36Sopenharmony_ci nr_count[type], type, 56862306a36Sopenharmony_ci hwmon_attributes[type]); 56962306a36Sopenharmony_ci if (err) 57062306a36Sopenharmony_ci return err; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci sensor_data->wmi.info[type] = devm_kcalloc(dev, 57562306a36Sopenharmony_ci nr_count[type], 57662306a36Sopenharmony_ci sizeof(*sensor_data->wmi.info), 57762306a36Sopenharmony_ci GFP_KERNEL); 57862306a36Sopenharmony_ci if (!sensor_data->wmi.info[type]) 57962306a36Sopenharmony_ci return -ENOMEM; 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci for (i = sensor_data->wmi.sensor_count - 1; i >= 0; i--) { 58362306a36Sopenharmony_ci temp_sensor = devm_kzalloc(dev, sizeof(*temp_sensor), GFP_KERNEL); 58462306a36Sopenharmony_ci if (!temp_sensor) 58562306a36Sopenharmony_ci return -ENOMEM; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci err = asus_wmi_sensor_info(i, temp_sensor); 58862306a36Sopenharmony_ci if (err) 58962306a36Sopenharmony_ci continue; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci switch (temp_sensor->data_type) { 59262306a36Sopenharmony_ci case TEMPERATURE_C: 59362306a36Sopenharmony_ci case VOLTAGE: 59462306a36Sopenharmony_ci case CURRENT: 59562306a36Sopenharmony_ci case FAN_RPM: 59662306a36Sopenharmony_ci case WATER_FLOW: 59762306a36Sopenharmony_ci type = asus_data_types[temp_sensor->data_type]; 59862306a36Sopenharmony_ci idx = --nr_count[type]; 59962306a36Sopenharmony_ci *(sensor_data->wmi.info[type] + idx) = temp_sensor; 60062306a36Sopenharmony_ci sensor_data->wmi.info_by_id[i] = temp_sensor; 60162306a36Sopenharmony_ci break; 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci } 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci dev_dbg(dev, "board has %d sensors", 60662306a36Sopenharmony_ci sensor_data->wmi.sensor_count); 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_sensors", 60962306a36Sopenharmony_ci sensor_data, chip_info, NULL); 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(hwdev); 61262306a36Sopenharmony_ci} 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_cistatic int asus_wmi_probe(struct wmi_device *wdev, const void *context) 61562306a36Sopenharmony_ci{ 61662306a36Sopenharmony_ci struct asus_wmi_sensors *sensor_data; 61762306a36Sopenharmony_ci struct device *dev = &wdev->dev; 61862306a36Sopenharmony_ci u32 version = 0; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci if (!dmi_check_system(asus_wmi_dmi_table)) 62162306a36Sopenharmony_ci return -ENODEV; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); 62462306a36Sopenharmony_ci if (!sensor_data) 62562306a36Sopenharmony_ci return -ENOMEM; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci if (asus_wmi_get_version(&version)) 62862306a36Sopenharmony_ci return -ENODEV; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci if (asus_wmi_get_item_count(&sensor_data->wmi.sensor_count)) 63162306a36Sopenharmony_ci return -ENODEV; 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci if (sensor_data->wmi.sensor_count <= 0 || version < 2) { 63462306a36Sopenharmony_ci dev_info(dev, "version: %u with %d sensors is unsupported\n", 63562306a36Sopenharmony_ci version, sensor_data->wmi.sensor_count); 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci return -ENODEV; 63862306a36Sopenharmony_ci } 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci mutex_init(&sensor_data->lock); 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci dev_set_drvdata(dev, sensor_data); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci return asus_wmi_configure_sensor_setup(dev, sensor_data); 64562306a36Sopenharmony_ci} 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_cistatic const struct wmi_device_id asus_wmi_id_table[] = { 64862306a36Sopenharmony_ci { ASUSWMI_MONITORING_GUID, NULL }, 64962306a36Sopenharmony_ci { } 65062306a36Sopenharmony_ci}; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_cistatic struct wmi_driver asus_sensors_wmi_driver = { 65362306a36Sopenharmony_ci .driver = { 65462306a36Sopenharmony_ci .name = "asus_wmi_sensors", 65562306a36Sopenharmony_ci }, 65662306a36Sopenharmony_ci .id_table = asus_wmi_id_table, 65762306a36Sopenharmony_ci .probe = asus_wmi_probe, 65862306a36Sopenharmony_ci}; 65962306a36Sopenharmony_cimodule_wmi_driver(asus_sensors_wmi_driver); 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ciMODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>"); 66262306a36Sopenharmony_ciMODULE_DESCRIPTION("Asus WMI Sensors Driver"); 66362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 664