diff --git a/Documentation/hwmon/asus_wmi_ec_sensors.rst b/Documentation/hwmon/asus_wmi_ec_sensors.rst new file mode 100644 index 000000000000..1b287f229e86 --- /dev/null +++ b/Documentation/hwmon/asus_wmi_ec_sensors.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_wmi_ec_sensors +================================= + +Supported boards: + * PRIME X570-PRO, + * Pro WS X570-ACE, + * ROG CROSSHAIR VIII DARK HERO, + * ROG CROSSHAIR VIII FORMULA, + * ROG CROSSHAIR VIII HERO, + * ROG STRIX B550-E GAMING, + * ROG STRIX B550-I GAMING, + * ROG STRIX X570-E GAMING. + +Authors: + - Eugene Shalygin + +Description: +------------ +ASUS mainboards publish hardware monitoring information via Super I/O +chip and the ACPI embedded controller (EC) registers. Some of the sensors +are only available via the EC. + +ASUS WMI interface provides a method (BREC) to read data from EC registers, +which is utilized by this driver to publish those sensor readings to the +HWMON system. The driver is aware of and reads the following sensors: + +1. Chipset (PCH) temperature +2. CPU package temperature +3. Motherboard temperature +4. Readings from the T_Sensor header +5. VRM temperature +6. CPU_Opt fan RPM +7. Chipset fan RPM +8. Readings from the "Water flow meter" header (RPM) +9. Readings from the "Water In" and "Water Out" temperature headers +10. CPU current diff --git a/Documentation/hwmon/asus_wmi_sensors.rst b/Documentation/hwmon/asus_wmi_sensors.rst new file mode 100644 index 000000000000..408fd3b4a0de --- /dev/null +++ b/Documentation/hwmon/asus_wmi_sensors.rst @@ -0,0 +1,76 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_wmi_sensors +================================= + +Supported boards: + * PRIME X399-A, + * PRIME X470-PRO, + * ROG CROSSHAIR VI EXTREME, + * ROG CROSSHAIR VI HERO, + * ROG CROSSHAIR VI HERO (WI-FI AC), + * ROG CROSSHAIR VII HERO, + * ROG CROSSHAIR VII HERO (WI-FI), + * ROG STRIX B450-E GAMING, + * ROG STRIX B450-F GAMING, + * ROG STRIX B450-I GAMING, + * ROG STRIX X399-E GAMING, + * ROG STRIX X470-F GAMING, + * ROG STRIX X470-I GAMING, + * ROG ZENITH EXTREME, + * ROG ZENITH EXTREME ALPHA. + +Authors: + - Ed Brindley + +Description: +------------ +ASUS mainboards publish hardware monitoring information via WMI interface. + +ASUS WMI interface provides a methods to get list of sensors and values of +such, which is utilized by this driver to publish those sensor readings to the +HWMON system. The driver is aware of and reads the following sensors: + * CPU Core Voltage, + * CPU SOC Voltage, + * DRAM Voltage, + * VDDP Voltage, + * 1.8V PLL Voltage, + * +12V Voltage, + * +5V Voltage, + * 3VSB Voltage, + * VBAT Voltage, + * AVCC3 Voltage, + * SB 1.05V Voltage, + * CPU Core Voltage, + * CPU SOC Voltage, + * DRAM Voltage, + * CPU Fan RPM, + * Chassis Fan 1 RPM, + * Chassis Fan 2 RPM, + * Chassis Fan 3 RPM, + * HAMP Fan RPM, + * Water Pump RPM, + * CPU OPT RPM, + * Water Flow RPM, + * AIO Pump RPM, + * CPU Temperature, + * CPU Socket Temperature, + * Motherboard Temperature, + * Chipset Temperature, + * Tsensor 1 Temperature, + * CPU VRM Temperature, + * Water In, + * Water Out, + * CPU VRM Output Current. + +Known Issues: +* The WMI implementation in some of Asus' BIOSes is buggy. This can result in + fans stopping, fans getting stuck at max speed, or temperature readouts + getting stuck. This is not an issue with the driver, but the BIOS. The Prime + X470 Pro seems particularly bad for this. The more frequently the WMI + interface is polled the greater the potential for this to happen. Until you + have subjected your computer to an extended soak test while polling the + sensors frequently, don't leave you computer unattended. Upgrading to new + BIOS version with method version greater than or equal to two should + rectify the issue. +* A few boards report 12v voltages to be ~10v. diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 7046bf1870d9..6dca9df9d24a 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -43,6 +43,8 @@ Hardware Monitoring Kernel Drivers asb100 asc7621 aspeed-pwm-tacho + asus_wmi_ec_sensors + asus_wmi_sensors bcm54140 bel-pfe bpa-rs600 diff --git a/MAINTAINERS b/MAINTAINERS index cc5eaf4e65dc..46036c184330 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2937,6 +2937,14 @@ W: http://acpi4asus.sf.net F: drivers/platform/x86/asus*.c F: drivers/platform/x86/eeepc*.c +ASUS WMI HARDWARE MONITOR DRIVER +M: Eugene Shalygin +M: Denis Pauk +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus_wmi_ec_sensors.c +F: drivers/hwmon/asus_wmi_sensors.c + ASUS WIRELESS RADIO CONTROL DRIVER M: João Paulo Rechi Vita L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 64bd3dfba2c4..d99aef4b2583 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2215,6 +2215,30 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. +config SENSORS_ASUS_WMI + tristate "ASUS WMI X370/X470/B450/X399" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI hardware monitoring + interface found in X370/X470/B450/X399 ASUS motherboards. This driver + will provide readings of fans, voltages and temperatures through the system + firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors. + +config SENSORS_ASUS_WMI_EC + tristate "ASUS WMI B550/X570" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI embedded controller + hardware monitoring interface found in B550/X570 ASUS motherboards. + This driver will provide readings of fans, voltages and temperatures + through the system firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors_ec. + endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index baee6a8d4dd1..656a6191a0f8 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o # APCI drivers obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o +obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. diff --git a/drivers/hwmon/asus_wmi_ec_sensors.c b/drivers/hwmon/asus_wmi_ec_sensors.c new file mode 100644 index 000000000000..f612abc66c89 --- /dev/null +++ b/drivers/hwmon/asus_wmi_ec_sensors.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS B550/X570 motherboards that publish sensor + * values via the embedded controller registers. + * + * Copyright (C) 2021 Eugene Shalygin + * Copyright (C) 2018-2019 Ed Brindley + * + * EC provides: + * - Chipset temperature + * - CPU temperature + * - Motherboard temperature + * - T_Sensor temperature + * - VRM temperature + * - Water In temperature + * - Water Out temperature + * - CPU Optional Fan RPM + * - Chipset Fan RPM + * - Water Flow Fan RPM + * - CPU current + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_BLOCK_READ_EC 0x42524543 /* BREC */ +/* From the ASUS DSDT source */ +#define ASUSWMI_BREC_REGISTERS_MAX 16 +#define ASUSWMI_MAX_BUF_LEN 128 +#define SENSOR_LABEL_LEN 16 + +static u32 hwmon_attributes[] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +struct asus_wmi_ec_sensor_address { + u8 index; + u8 bank; + u8 size; +}; + +#define MAKE_SENSOR_ADDRESS(size_i, bank_i, index_i) { \ + .size = size_i, \ + .bank = bank_i, \ + .index = index_i, \ +} + +struct ec_sensor_info { + struct asus_wmi_ec_sensor_address addr; + char label[SENSOR_LABEL_LEN]; + enum hwmon_sensor_types type; +}; + +#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \ + .addr = MAKE_SENSOR_ADDRESS(size, bank, index), \ + .label = sensor_label, \ + .type = sensor_type, \ +} + +enum known_ec_sensor { + SENSOR_TEMP_CHIPSET, + SENSOR_TEMP_CPU, + SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, + SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_FAN_CHIPSET, + SENSOR_FAN_VRM_HS, + SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_TEMP_WATER_IN, + SENSOR_TEMP_WATER_OUT, + SENSOR_MAX +}; + +/* All known sensors for ASUS EC controllers */ +static const struct ec_sensor_info known_ec_sensors[] = { + [SENSOR_TEMP_CHIPSET] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [SENSOR_TEMP_CPU] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [SENSOR_TEMP_MB] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [SENSOR_TEMP_T_SENSOR] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [SENSOR_TEMP_VRM] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [SENSOR_FAN_CPU_OPT] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [SENSOR_FAN_VRM_HS] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [SENSOR_FAN_CHIPSET] = EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), + [SENSOR_FAN_WATER_FLOW] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), + [SENSOR_CURR_CPU] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [SENSOR_TEMP_WATER_IN] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [SENSOR_TEMP_WATER_OUT] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), +}; + +struct asus_wmi_data { + const enum known_ec_sensor known_board_sensors[SENSOR_MAX + 1]; +}; + +/* boards with EC support */ +static struct asus_wmi_data sensors_board_PW_X570_P = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_PW_X570_A = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_R_C8H = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without chipset fan */ +static struct asus_wmi_data sensors_board_R_C8DH = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without water */ +static struct asus_wmi_data sensors_board_R_C8F = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_I_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_VRM_HS, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_X570_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, sensors) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = sensors, \ +} + +static const struct dmi_system_id asus_wmi_ec_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &sensors_board_PW_X570_P), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &sensors_board_PW_X570_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &sensors_board_R_C8DH), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &sensors_board_R_C8F), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &sensors_board_R_C8H), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &sensors_board_RS_B550_E_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &sensors_board_RS_B550_I_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &sensors_board_RS_X570_E_G), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_ec_dmi_table); + +struct ec_sensor { + enum known_ec_sensor info_index; + long cached_value; +}; + +/** + * struct asus_wmi_ec_info - sensor info. + * @sensors: list of sensors. + * @read_arg: UTF-16LE string to pass to BRxx() WMI function. + * @read_buffer: decoded output from WMI result. + * @nr_sensors: number of board EC sensors. + * @nr_registers: number of EC registers to read (sensor might span more than 1 register). + * @last_updated: in jiffies. + */ +struct asus_wmi_ec_info { + struct ec_sensor sensors[SENSOR_MAX]; + char read_arg[(ASUSWMI_BREC_REGISTERS_MAX * 4 + 1) * 2]; + u8 read_buffer[ASUSWMI_BREC_REGISTERS_MAX]; + unsigned int nr_sensors; + unsigned int nr_registers; + unsigned long last_updated; +}; + +struct asus_wmi_sensors { + struct asus_wmi_ec_info ec; + /* lock access to internal cache */ + struct mutex lock; +}; + +static int asus_wmi_ec_fill_board_sensors(struct asus_wmi_ec_info *ec, + const enum known_ec_sensor *bsi) +{ + struct ec_sensor *s = ec->sensors; + int i; + + ec->nr_sensors = 0; + ec->nr_registers = 0; + + for (i = 0; bsi[i] != SENSOR_MAX; i++) { + s[i].info_index = bsi[i]; + ec->nr_sensors++; + ec->nr_registers += known_ec_sensors[bsi[i]].addr.size; + } + + return 0; +} + +/* + * The next four functions convert to or from BRxx string argument format. + * The format of the string is as follows: + * - The string consists of two-byte UTF-16LE characters. + * - The value of the very first byte in the string is equal to the total + * length of the next string in bytes, thus excluding the first two-byte + * character. + * - The rest of the string encodes the pairs of (bank, index) pairs, where + * both values are byte-long (0x00 to 0xFF). + * - Numbers are encoded as UTF-16LE hex values. + */ +static int asus_wmi_ec_decode_reply_buffer(const u8 *in, u32 length, u8 *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + u32 len = min_t(u32, get_unaligned_le16(in), length - 2); + + utf16s_to_utf8s((wchar_t *)(in + 2), len / 2, UTF16_LITTLE_ENDIAN, buffer, sizeof(buffer)); + + return hex2bin(out, buffer, len / 4); +} + +static void asus_wmi_ec_encode_registers(const u8 *in, u32 len, char *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + + bin2hex(buffer, in, len); + + utf8s_to_utf16s(buffer, len * 2, UTF16_LITTLE_ENDIAN, (wchar_t *)(out + 2), len * 2); + + put_unaligned_le16(len * 4, out); +} + +static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec) +{ + u8 registers[ASUSWMI_BREC_REGISTERS_MAX * 2]; + const struct ec_sensor_info *si; + int i, j, offset; + + offset = 0; + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + for (j = 0; j < si->addr.size; j++) { + registers[offset++] = si->addr.bank; + registers[offset++] = si->addr.index + j; + } + } + + asus_wmi_ec_encode_registers(registers, offset, ec->read_arg); +} + +static int asus_wmi_ec_block_read(u32 method_id, char *query, u8 *out) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret; + + /* The first byte of the BRxx() argument string has to be the string size. */ + input.length = query[0] + 2; + input.pointer = query; + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) { + ret = -EIO; + goto out_free_obj; + } + + ret = asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, obj->buffer.length, out); + +out_free_obj: + ACPI_FREE(obj); + return ret; +} + +static inline long get_sensor_value(const struct ec_sensor_info *si, u8 *data) +{ + switch (si->addr.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } +} + +static void asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec) +{ + const struct ec_sensor_info *si; + struct ec_sensor *s; + u8 i_sensor; + u8 *data; + + data = ec->read_buffer; + for (i_sensor = 0; i_sensor < ec->nr_sensors; i_sensor++) { + s = &ec->sensors[i_sensor]; + si = &known_ec_sensors[s->info_index]; + s->cached_value = get_sensor_value(si, data); + data += si->addr.size; + } +} + +static long asus_wmi_ec_scale_sensor_value(long value, int data_type) +{ + switch (data_type) { + case hwmon_curr: + case hwmon_temp: + case hwmon_in: + return value * MILLI; + default: + return value; + } +} + +static int asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec, + enum hwmon_sensor_types type, int channel) +{ + int i; + + for (i = 0; i < ec->nr_sensors; i++) { + if (known_ec_sensors[ec->sensors[i].info_index].type == type) { + if (channel == 0) + return i; + + channel--; + } + } + return -EINVAL; +} + +static int asus_wmi_ec_get_cached_value_or_update(struct asus_wmi_sensors *sensor_data, + int sensor_index, + long *value) +{ + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, ec->last_updated + HZ)) { + ret = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC, + ec->read_arg, ec->read_buffer); + if (ret) + goto unlock; + + asus_wmi_ec_update_ec_sensors(ec); + ec->last_updated = jiffies; + } + + *value = ec->sensors[sensor_index].cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ + +static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret, sidx, info_index; + long value = 0; + + sidx = asus_wmi_ec_find_sensor_index(ec, type, channel); + if (sidx < 0) + return sidx; + + ret = asus_wmi_ec_get_cached_value_or_update(sensor_data, sidx, &value); + if (ret) + return ret; + + info_index = ec->sensors[sidx].info_index; + *val = asus_wmi_ec_scale_sensor_value(value, known_ec_sensors[info_index].type); + + return ret; +} + +static int asus_wmi_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int sensor_index; + + sensor_index = asus_wmi_ec_find_sensor_index(ec, type, channel); + *str = known_ec_sensors[ec->sensors[sensor_index].info_index].label; + + return 0; +} + +static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_ec_info *ec = &sensor_data->ec; + int index; + + index = asus_wmi_ec_find_sensor_index(ec, type, channel); + + return index < 0 ? 0 : 0444; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +static const struct hwmon_ops asus_wmi_ec_hwmon_ops = { + .is_visible = asus_wmi_ec_hwmon_is_visible, + .read = asus_wmi_ec_hwmon_read, + .read_string = asus_wmi_ec_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_ec_chip_info = { + .ops = &asus_wmi_ec_hwmon_ops, +}; + +static int asus_wmi_ec_configure_sensor_setup(struct device *dev, + const enum known_ec_sensor *bsi) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + const struct hwmon_channel_info **asus_wmi_ci; + int nr_count[hwmon_max] = {}, nr_types = 0; + const struct hwmon_chip_info *chip_info; + const struct ec_sensor_info *si; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, ret; + + ret = asus_wmi_ec_fill_board_sensors(ec, bsi); + if (ret) + return ret; + + if (!sensor_data->ec.nr_sensors) + return -ENODEV; + + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + if (!nr_count[si->type]) + nr_types++; + nr_count[si->type]++; + } + + if (nr_count[hwmon_temp]) { + nr_count[hwmon_chip]++; + nr_types++; + } + + /* + * If we can get values for all the registers in a single query, + * the query will not change from call to call. + */ + asus_wmi_ec_make_block_read_query(ec); + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*asus_wmi_ci), GFP_KERNEL); + if (!asus_wmi_ci) + return -ENOMEM; + + asus_wmi_ec_chip_info.info = asus_wmi_ci; + chip_info = &asus_wmi_ec_chip_info; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + ret = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (ret) + return ret; + + *asus_wmi_ci++ = asus_wmi_hwmon_chan++; + } + + dev_dbg(dev, "board has %d EC sensors that span %d registers", + ec->nr_sensors, ec->nr_registers); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_ec_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct asus_wmi_data *board_sensors; + const struct dmi_system_id *dmi_id; + const enum known_ec_sensor *bsi; + struct device *dev = &wdev->dev; + + dmi_id = dmi_first_match(asus_wmi_ec_dmi_table); + if (!dmi_id) + return -ENODEV; + + board_sensors = dmi_id->driver_data; + bsi = board_sensors->known_board_sensors; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_ec_configure_sensor_setup(dev, bsi); +} + +static const struct wmi_device_id asus_ec_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_ec_sensors", + }, + .id_table = asus_ec_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley "); +MODULE_AUTHOR("Eugene Shalygin "); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c new file mode 100644 index 000000000000..2f204249560e --- /dev/null +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS motherboards that provides sensor readouts via WMI + * interface present in the UEFI of the X370/X470/B450/X399 Ryzen motherboards. + * + * Copyright (C) 2018-2019 Ed Brindley + * + * WMI interface provides: + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - VDDP Voltage, + * - 1.8V PLL Voltage, + * - +12V Voltage, + * - +5V Voltage, + * - 3VSB Voltage, + * - VBAT Voltage, + * - AVCC3 Voltage, + * - SB 1.05V Voltage, + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - CPU Fan RPM, + * - Chassis Fan 1 RPM, + * - Chassis Fan 2 RPM, + * - Chassis Fan 3 RPM, + * - HAMP Fan RPM, + * - Water Pump RPM, + * - CPU OPT RPM, + * - Water Flow RPM, + * - AIO Pump RPM, + * - CPU Temperature, + * - CPU Socket Temperature, + * - Motherboard Temperature, + * - Chipset Temperature, + * - Tsensor 1 Temperature, + * - CPU VRM Temperature, + * - Water In, + * - Water Out, + * - CPU VRM Output Current. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_GET_VALUE 0x52574543 /* RWEC */ +#define ASUSWMI_METHODID_UPDATE_BUFFER 0x51574543 /* QWEC */ +#define ASUSWMI_METHODID_GET_INFO 0x50574543 /* PWEC */ +#define ASUSWMI_METHODID_GET_NUMBER 0x50574572 /* PWEr */ +#define ASUSWMI_METHODID_GET_VERSION 0x50574574 /* PWEt */ + +#define ASUS_WMI_MAX_STR_SIZE 32 + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ +} + +static const struct dmi_system_id asus_wmi_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO (WI-FI AC)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME ALPHA"), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table); + +enum asus_wmi_sensor_class { + VOLTAGE = 0x0, + TEMPERATURE_C = 0x1, + FAN_RPM = 0x2, + CURRENT = 0x3, + WATER_FLOW = 0x4, +}; + +enum asus_wmi_location { + CPU = 0x0, + CPU_SOC = 0x1, + DRAM = 0x2, + MOTHERBOARD = 0x3, + CHIPSET = 0x4, + AUX = 0x5, + VRM = 0x6, + COOLER = 0x7 +}; + +enum asus_wmi_type { + SIGNED_INT = 0x0, + UNSIGNED_INT = 0x1, + SCALED = 0x3, +}; + +enum asus_wmi_source { + SIO = 0x1, + EC = 0x2 +}; + +static enum hwmon_sensor_types asus_data_types[] = { + [VOLTAGE] = hwmon_in, + [TEMPERATURE_C] = hwmon_temp, + [FAN_RPM] = hwmon_fan, + [CURRENT] = hwmon_curr, + [WATER_FLOW] = hwmon_fan, +}; + +static u32 hwmon_attributes[] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +/** + * struct asus_wmi_sensor_info - sensor info. + * @id: sensor id. + * @data_type: sensor class e.g. voltage, temp etc. + * @location: sensor location. + * @name: sensor name. + * @source: sensor source. + * @type: sensor type signed, unsigned etc. + * @cached_value: cached sensor value. + */ +struct asus_wmi_sensor_info { + u32 id; + int data_type; + int location; + char name[ASUS_WMI_MAX_STR_SIZE]; + int source; + int type; + long cached_value; +}; + +struct asus_wmi_wmi_info { + unsigned long source_last_updated[3]; /* in jiffies */ + int sensor_count; + + const struct asus_wmi_sensor_info **info[hwmon_max]; + struct asus_wmi_sensor_info **info_by_id; +}; + +struct asus_wmi_sensors { + struct asus_wmi_wmi_info wmi; + /* lock access to internal cache */ + struct mutex lock; +}; + +/* + * Universal method for calling WMI method + */ +static int asus_wmi_call_method(u32 method_id, u32 *args, struct acpi_buffer *output) +{ + struct acpi_buffer input = {(acpi_size) sizeof(*args), args }; + acpi_status status; + + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, + method_id, &input, output); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * Gets the version of the ASUS sensors interface implemented + */ +static int asus_wmi_get_version(u32 *version) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VERSION, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *version = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +/* + * Gets the number of sensor items + */ +static int asus_wmi_get_item_count(u32 *count) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_NUMBER, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *count = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +/* + * For a given sensor item returns details e.g. type (voltage/temperature/fan speed etc), bank etc + */ +static int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s) +{ + union acpi_object name_obj, data_type_obj, location_obj, source_obj, type_obj; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_INFO, args, &output); + if (err) + return err; + + s->id = index; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_PACKAGE) { + err = -EIO; + goto out_free_obj; + } + + if (obj->package.count != 5) { + err = -EIO; + goto out_free_obj; + } + + name_obj = obj->package.elements[0]; + if (name_obj.type != ACPI_TYPE_STRING) { + err = -EIO; + goto out_free_obj; + } + + strncpy(s->name, name_obj.string.pointer, sizeof(s->name) - 1); + + data_type_obj = obj->package.elements[1]; + if (data_type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->data_type = data_type_obj.integer.value; + + location_obj = obj->package.elements[2]; + if (location_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->location = location_obj.integer.value; + + source_obj = obj->package.elements[3]; + if (source_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->source = source_obj.integer.value; + + type_obj = obj->package.elements[4]; + if (type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + s->type = type_obj.integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_buffer(int source) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {source, 0}; + + return asus_wmi_call_method(ASUSWMI_METHODID_UPDATE_BUFFER, args, &output); +} + +static int asus_wmi_get_sensor_value(u8 index, long *value) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VALUE, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *value = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_values_for_source(u8 source, struct asus_wmi_sensors *sensor_data) +{ + struct asus_wmi_sensor_info *sensor; + long value = 0; + int ret; + int i; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + sensor = sensor_data->wmi.info_by_id[i]; + if (sensor && sensor->source == source) { + ret = asus_wmi_get_sensor_value(sensor->id, &value); + if (ret) + return ret; + + sensor->cached_value = value; + } + } + + return 0; +} + +static int asus_wmi_scale_sensor_value(u32 value, int data_type) +{ + /* FAN_RPM and WATER_FLOW don't need scaling */ + switch (data_type) { + case VOLTAGE: + /* value in microVolts */ + return DIV_ROUND_CLOSEST(value, KILO); + case TEMPERATURE_C: + /* value in Celsius */ + return value * MILLIDEGREE_PER_DEGREE; + case CURRENT: + /* value in Amperes */ + return value * MILLI; + } + return value; +} + +static int asus_wmi_get_cached_value_or_update(const struct asus_wmi_sensor_info *sensor, + struct asus_wmi_sensors *sensor_data, + u32 *value) +{ + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, sensor_data->wmi.source_last_updated[sensor->source] + HZ)) { + ret = asus_wmi_update_buffer(sensor->source); + if (ret) + goto unlock; + + ret = asus_wmi_update_values_for_source(sensor->source, sensor_data); + if (ret) + goto unlock; + + sensor_data->wmi.source_last_updated[sensor->source] = jiffies; + } + + *value = sensor->cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ +static int asus_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + const struct asus_wmi_sensor_info *sensor; + u32 value = 0; + int ret; + + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + + sensor = *(sensor_data->wmi.info[type] + channel); + + ret = asus_wmi_get_cached_value_or_update(sensor, sensor_data, &value); + if (ret) + return ret; + + *val = asus_wmi_scale_sensor_value(value, sensor->data_type); + + return ret; +} + +static int asus_wmi_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + *str = sensor->name; + + return 0; +} + +static umode_t asus_wmi_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + if (sensor) + return 0444; + + return 0; +} + +static const struct hwmon_ops asus_wmi_hwmon_ops = { + .is_visible = asus_wmi_hwmon_is_visible, + .read = asus_wmi_hwmon_read, + .read_string = asus_wmi_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_chip_info = { + .ops = &asus_wmi_hwmon_ops, + .info = NULL, +}; + +static int asus_wmi_configure_sensor_setup(struct device *dev, + struct asus_wmi_sensors *sensor_data) +{ + const struct hwmon_channel_info **ptr_asus_wmi_ci; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + int nr_count[hwmon_max] = {}, nr_types = 0; + struct asus_wmi_sensor_info *temp_sensor; + const struct hwmon_chip_info *chip_info; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, idx; + int err; + + temp_sensor = devm_kcalloc(dev, 1, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + return err; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + if (!nr_count[type]) + nr_types++; + nr_count[type]++; + break; + } + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, + sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, + sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); + if (!ptr_asus_wmi_ci) + return -ENOMEM; + + asus_wmi_chip_info.info = ptr_asus_wmi_ci; + chip_info = &asus_wmi_chip_info; + + sensor_data->wmi.info_by_id = devm_kcalloc(dev, sensor_data->wmi.sensor_count, + sizeof(*sensor_data->wmi.info_by_id), + GFP_KERNEL); + + if (!sensor_data->wmi.info_by_id) + return -ENOMEM; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + err = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (err) + return err; + + *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; + + sensor_data->wmi.info[type] = devm_kcalloc(dev, + nr_count[type], + sizeof(*sensor_data->wmi.info), + GFP_KERNEL); + if (!sensor_data->wmi.info[type]) + return -ENOMEM; + } + + for (i = sensor_data->wmi.sensor_count - 1; i >= 0; i--) { + temp_sensor = devm_kzalloc(dev, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + continue; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + idx = --nr_count[type]; + *(sensor_data->wmi.info[type] + idx) = temp_sensor; + sensor_data->wmi.info_by_id[i] = temp_sensor; + break; + } + } + + dev_dbg(dev, "board has %d sensors", + sensor_data->wmi.sensor_count); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct device *dev = &wdev->dev; + u32 version = 0; + + if (!dmi_check_system(asus_wmi_dmi_table)) + return -ENODEV; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + if (asus_wmi_get_version(&version)) + return -ENODEV; + + if (asus_wmi_get_item_count(&sensor_data->wmi.sensor_count)) + return -ENODEV; + + if (sensor_data->wmi.sensor_count <= 0 || version < 2) { + dev_info(dev, "version: %u with %d sensors is unsupported\n", + version, sensor_data->wmi.sensor_count); + + return -ENODEV; + } + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_configure_sensor_setup(dev, sensor_data); +} + +static const struct wmi_device_id asus_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_sensors", + }, + .id_table = asus_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley "); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 93dca471972e..5bbe383b2aa5 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -56,6 +56,8 @@ #include #include #include +#include +#include #include "lm75.h" #define USE_ALTERNATE @@ -140,6 +142,8 @@ struct nct6775_sio_data { int ld; enum kinds kind; enum sensor_access access; + bool i2c_enabled; + acpi_handle acpi_wmi_mutex; /* superio_() callbacks */ void (*sio_outb)(struct nct6775_sio_data *sio_data, int reg, int val); @@ -155,6 +159,7 @@ struct nct6775_sio_data { #define ASUSWMI_METHODID_RHWM 0x5248574D #define ASUSWMI_METHODID_WHWM 0x5748574D #define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE +#define ASUSWMI_DELAY_MSEC_LOCK 500 /* Wait 0.5 s max. to get the lock */ static int nct6775_asuswmi_evaluate_method(u32 method_id, u8 bank, u8 reg, u8 val, u32 *retval) { @@ -1244,6 +1249,7 @@ struct nct6775_data { unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); struct mutex update_lock; + acpi_handle acpi_wmi_mutex; /* Additionaly acpi_acquire_mutex */ bool valid; /* true if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -1323,9 +1329,14 @@ struct nct6775_data { u8 fandiv2; u8 sio_reg_enable; + /* ASUS boards specific i2c connected to nct6775 */ + struct i2c_adapter *i2c_adapter; + /* nct6775_*() callbacks */ u16 (*read_value)(struct nct6775_data *data, u16 reg); int (*write_value)(struct nct6775_data *data, u16 reg, u16 value); + int (*lock)(struct nct6775_data *data); + void (*unlock)(struct nct6775_data *data, struct device *dev); }; struct sensor_device_template { @@ -1561,6 +1572,21 @@ static int nct6775_wmi_write_value(struct nct6775_data *data, u16 reg, u16 value return res; } +static int nct6775_wmi_lock(struct nct6775_data *data) +{ + if (ACPI_FAILURE(acpi_acquire_mutex(data->acpi_wmi_mutex, NULL, + ASUSWMI_DELAY_MSEC_LOCK))) + return -EIO; + + return 0; +} + +static void nct6775_wmi_unlock(struct nct6775_data *data, struct device *dev) +{ + if (ACPI_FAILURE(acpi_release_mutex(data->acpi_wmi_mutex, NULL))) + dev_err(dev, "Failed to release mutex."); +} + /* * On older chips, only registers 0x50-0x5f are banked. * On more recent chips, all registers are banked. @@ -1918,12 +1944,26 @@ static void nct6775_update_pwm_limits(struct device *dev) } } +static int nct6775_lock(struct nct6775_data *data) +{ + mutex_lock(&data->update_lock); + + return 0; +} + +static void nct6775_unlock(struct nct6775_data *data, struct device *dev) +{ + mutex_unlock(&data->update_lock); +} + static struct nct6775_data *nct6775_update_device(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; + int i, j, err; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return data; if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || !data->valid) { @@ -2011,7 +2051,7 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) data->valid = true; } - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return data; } @@ -2043,11 +2083,15 @@ store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf, err = kstrtoul(buf, 10, &val); if (err < 0) return err; - mutex_lock(&data->update_lock); + + err = data->lock(data); + if (err) + return err; + data->in[nr][index] = in_to_reg(val, nr); data->write_value(data, data->REG_IN_MINMAX[index - 1][nr], data->in[nr][index]); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2127,14 +2171,17 @@ store_beep(struct device *dev, struct device_attribute *attr, const char *buf, if (val > 1) return -EINVAL; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + if (val) data->beeps |= (1ULL << nr); else data->beeps &= ~(1ULL << nr); data->write_value(data, data->REG_BEEP[regindex], (data->beeps >> (regindex << 3)) & 0xff); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2183,14 +2230,17 @@ store_temp_beep(struct device *dev, struct device_attribute *attr, bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; regindex = bit >> 3; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + if (val) data->beeps |= (1ULL << bit); else data->beeps &= ~(1ULL << bit); data->write_value(data, data->REG_BEEP[regindex], (data->beeps >> (regindex << 3)) & 0xff); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2284,7 +2334,10 @@ store_fan_min(struct device *dev, struct device_attribute *attr, if (err < 0) return err; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + if (!data->has_fan_div) { /* NCT6776F or NCT6779D; we know this is a 13 bit register */ if (!val) { @@ -2357,7 +2410,7 @@ store_fan_min(struct device *dev, struct device_attribute *attr, write_min: data->write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2390,13 +2443,16 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, if (val > 4) return -EINVAL; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->fan_pulses[nr] = val & 3; reg = data->read_value(data, data->REG_FAN_PULSES[nr]); reg &= ~(0x03 << data->FAN_PULSE_SHIFT[nr]); reg |= (val & 3) << data->FAN_PULSE_SHIFT[nr]; data->write_value(data, data->REG_FAN_PULSES[nr], reg); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2494,11 +2550,14 @@ store_temp(struct device *dev, struct device_attribute *attr, const char *buf, if (err < 0) return err; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->temp[index][nr] = LM75_TEMP_TO_REG(val); nct6775_write_temp(data, data->reg_temp[index][nr], data->temp[index][nr]); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2527,10 +2586,13 @@ store_temp_offset(struct device *dev, struct device_attribute *attr, val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->temp_offset[nr] = val; data->write_value(data, data->REG_TEMP_OFFSET[nr], val); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2563,7 +2625,9 @@ store_temp_type(struct device *dev, struct device_attribute *attr, if (val != 1 && val != 3 && val != 4) return -EINVAL; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; data->temp_type[nr] = val; vbit = 0x02 << nr; @@ -2584,7 +2648,7 @@ store_temp_type(struct device *dev, struct device_attribute *attr, data->write_value(data, data->REG_VBAT, vbat); data->write_value(data, data->REG_DIODE, diode); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2704,14 +2768,17 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, return count; } - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->pwm_mode[nr] = val; reg = data->read_value(data, data->REG_PWM_MODE[nr]); reg &= ~data->PWM_MODE_MASK[nr]; if (!val) reg |= data->PWM_MODE_MASK[nr]; data->write_value(data, data->REG_PWM_MODE[nr], reg); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2756,7 +2823,10 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, return err; val = clamp_val(val, minval[index], maxval[index]); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->pwm[index][nr] = val; data->write_value(data, data->REG_PWM[index][nr], val); if (index == 2) { /* floor: disable if val == 0 */ @@ -2766,7 +2836,7 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, reg |= 0x80; data->write_value(data, data->REG_TEMP_SEL[nr], reg); } - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2866,7 +2936,10 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, return -EINVAL; } - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->pwm_enable[nr] = val; if (val == off) { /* @@ -2880,7 +2953,7 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, reg &= 0x0f; reg |= pwm_enable_to_reg(val) << 4; data->write_value(data, data->REG_FAN_MODE[nr], reg); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2929,14 +3002,17 @@ store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, if (!(data->have_temp & BIT(val - 1)) || !data->temp_src[val - 1]) return -EINVAL; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + src = data->temp_src[val - 1]; data->pwm_temp_sel[nr] = src; reg = data->read_value(data, data->REG_TEMP_SEL[nr]); reg &= 0xe0; reg |= src; data->write_value(data, data->REG_TEMP_SEL[nr], reg); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -2973,7 +3049,10 @@ store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, !data->temp_src[val - 1])) return -EINVAL; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + if (val) { src = data->temp_src[val - 1]; data->pwm_weight_temp_sel[nr] = src; @@ -2987,7 +3066,7 @@ store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, reg &= 0x7f; data->write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); } - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3018,10 +3097,13 @@ store_target_temp(struct device *dev, struct device_attribute *attr, val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->target_temp_mask); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->target_temp[nr] = val; pwm_update_registers(data, nr); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3055,10 +3137,13 @@ store_target_speed(struct device *dev, struct device_attribute *attr, val = clamp_val(val, 0, 1350000U); speed = fan_to_reg(val, data->fan_div[nr]); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->target_speed[nr] = speed; pwm_update_registers(data, nr); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3092,7 +3177,10 @@ store_temp_tolerance(struct device *dev, struct device_attribute *attr, /* Limit tolerance as needed */ val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->tolerance_mask); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->temp_tolerance[index][nr] = val; if (index) pwm_update_registers(data, nr); @@ -3100,7 +3188,7 @@ store_temp_tolerance(struct device *dev, struct device_attribute *attr, data->write_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[nr], val); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3169,10 +3257,13 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, /* Limit tolerance as needed */ val = clamp_val(val, 0, data->speed_tolerance_limit); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->target_speed_tolerance[nr] = val; pwm_update_registers(data, nr); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3220,10 +3311,13 @@ store_weight_temp(struct device *dev, struct device_attribute *attr, val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->weight_temp[index][nr] = val; data->write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3269,10 +3363,13 @@ store_fan_time(struct device *dev, struct device_attribute *attr, return err; val = step_time_to_reg(val, data->pwm_mode[nr]); - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->fan_time[index][nr] = val; data->write_value(data, data->REG_FAN_TIME[index][nr], val); - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3310,7 +3407,10 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, val = 0xff; } - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->auto_pwm[nr][point] = val; if (point < data->auto_pwm_num) { data->write_value(data, @@ -3355,7 +3455,7 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, break; } } - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3391,7 +3491,10 @@ store_auto_temp(struct device *dev, struct device_attribute *attr, if (val > 255000) return -EINVAL; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); if (point < data->auto_pwm_num) { data->write_value(data, @@ -3401,7 +3504,7 @@ store_auto_temp(struct device *dev, struct device_attribute *attr, data->write_value(data, data->REG_CRITICAL_TEMP[nr], data->auto_temp[nr][point]); } - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3570,7 +3673,9 @@ clear_caseopen(struct device *dev, struct device_attribute *attr, if (kstrtoul(buf, 10, &val) || val != 0) return -EINVAL; - mutex_lock(&data->update_lock); + ret = data->lock(data); + if (ret) + return ret; /* * Use CR registers to clear caseopen status. @@ -3593,7 +3698,7 @@ clear_caseopen(struct device *dev, struct device_attribute *attr, data->valid = false; /* Force cache refresh */ error: - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return count; } @@ -3939,6 +4044,257 @@ static void add_temp_sensors(struct nct6775_data *data, const u16 *regp, } } +/* Nuvoton SMBus address offsets */ +#define SMBHSTDAT (0 + nuvoton_nct6793d_smba) +#define SMBBLKSZ (1 + nuvoton_nct6793d_smba) +#define SMBHSTCMD (2 + nuvoton_nct6793d_smba) +//Index field is the Command field on other controllers +#define SMBHSTIDX (3 + nuvoton_nct6793d_smba) +#define SMBHSTCTL (4 + nuvoton_nct6793d_smba) +#define SMBHSTADD (5 + nuvoton_nct6793d_smba) +#define SMBHSTERR (9 + nuvoton_nct6793d_smba) +#define SMBHSTSTS (0xE + nuvoton_nct6793d_smba) + +/* Command register */ +#define NCT6793D_READ_BYTE 0 +#define NCT6793D_READ_WORD 1 +#define NCT6793D_WRITE_BYTE 8 +#define NCT6793D_WRITE_WORD 9 +#define NCT6793D_WRITE_BLOCK 10 + +/* Control register */ +#define NCT6793D_MANUAL_START 128 +#define NCT6793D_SOFT_RESET 64 + +/* Error register */ +#define NCT6793D_NO_ACK 32 + +/* Status register */ +#define NCT6793D_FIFO_EMPTY 1 +#define NCT6793D_MANUAL_ACTIVE 4 + +/* Other settings */ +#define MAX_RETRIES 400 + + +/* Return negative errno on error. */ +static s32 nct6775_i2c_access(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + struct nct6775_data *adapdata = i2c_get_adapdata(adap); + unsigned short nuvoton_nct6793d_smba = adapdata->addr; + int i, len, cnt; + union i2c_smbus_data tmp_data; + int timeout = 0, ret; + + tmp_data.word = 0; + cnt = 0; + len = 0; + + ret = adapdata->lock(adapdata); + if (ret) + return ret; + + outb_p(NCT6793D_SOFT_RESET, SMBHSTCTL); + + switch (size) { + case I2C_SMBUS_QUICK: + outb_p((addr << 1) | read_write, + SMBHSTADD); + break; + case I2C_SMBUS_BYTE_DATA: + tmp_data.byte = data->byte; + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTIDX); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(tmp_data.byte, SMBHSTDAT); + outb_p(NCT6793D_WRITE_BYTE, SMBHSTCMD); + } else { + outb_p(NCT6793D_READ_BYTE, SMBHSTCMD); + } + break; + case I2C_SMBUS_BYTE: + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTIDX); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(tmp_data.byte, SMBHSTDAT); + outb_p(NCT6793D_WRITE_BYTE, SMBHSTCMD); + } else { + outb_p(NCT6793D_READ_BYTE, SMBHSTCMD); + } + break; + case I2C_SMBUS_WORD_DATA: + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTIDX); + if (read_write == I2C_SMBUS_WRITE) { + outb_p(data->word & 0xff, SMBHSTDAT); + outb_p((data->word & 0xff00) >> 8, SMBHSTDAT); + outb_p(NCT6793D_WRITE_WORD, SMBHSTCMD); + } else { + outb_p(NCT6793D_READ_WORD, SMBHSTCMD); + } + break; + case I2C_SMBUS_BLOCK_DATA: + outb_p((addr << 1) | read_write, + SMBHSTADD); + outb_p(command, SMBHSTIDX); + if (read_write == I2C_SMBUS_WRITE) { + len = data->block[0]; + + if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) { + adapdata->unlock(adapdata, &adap->dev); + return -EINVAL; + } + + outb_p(len, SMBBLKSZ); + + cnt = 1; + if (len >= 4) { + for (i = cnt; i <= 4; i++) + outb_p(data->block[i], SMBHSTDAT); + + len -= 4; + cnt += 4; + } else { + for (i = cnt; i <= len; i++) + outb_p(data->block[i], SMBHSTDAT); + + len = 0; + } + + outb_p(NCT6793D_WRITE_BLOCK, SMBHSTCMD); + } else { + adapdata->unlock(adapdata, &adap->dev); + return -EOPNOTSUPP; + } + break; + default: + dev_warn(&adap->dev, "Unsupported transaction %d\n", size); + adapdata->unlock(adapdata, &adap->dev); + return -EOPNOTSUPP; + } + + outb_p(NCT6793D_MANUAL_START, SMBHSTCTL); + + while ((size == I2C_SMBUS_BLOCK_DATA) && (len > 0)) { + if (read_write == I2C_SMBUS_WRITE) { + timeout = 0; + + while ((inb_p(SMBHSTSTS) & NCT6793D_FIFO_EMPTY) == 0) { + if (timeout > MAX_RETRIES) { + adapdata->unlock(adapdata, &adap->dev); + return -ETIMEDOUT; + } + + usleep_range(250, 500); + timeout++; + } + + //Load more bytes into FIFO + if (len >= 4) { + for (i = cnt; i <= (cnt + 4); i++) + outb_p(data->block[i], SMBHSTDAT); + + len -= 4; + cnt += 4; + } else { + for (i = cnt; i <= (cnt + len); i++) + outb_p(data->block[i], SMBHSTDAT); + + len = 0; + } + } else { + adapdata->unlock(adapdata, &adap->dev); + return -EOPNOTSUPP; + } + } + + //wait for manual mode to complete + timeout = 0; + while ((inb_p(SMBHSTSTS) & NCT6793D_MANUAL_ACTIVE) != 0) { + if (timeout > MAX_RETRIES) { + adapdata->unlock(adapdata, &adap->dev); + return -ETIMEDOUT; + } + + usleep_range(250, 500); + timeout++; + } + + if ((inb_p(SMBHSTERR) & NCT6793D_NO_ACK) != 0) { + adapdata->unlock(adapdata, &adap->dev); + return -ENXIO; + } + + if (read_write == I2C_SMBUS_WRITE || size == I2C_SMBUS_QUICK) { + adapdata->unlock(adapdata, &adap->dev); + return 0; + } + + switch (size) { + case I2C_SMBUS_QUICK: + case I2C_SMBUS_BYTE_DATA: + data->byte = inb_p(SMBHSTDAT); + break; + case I2C_SMBUS_WORD_DATA: + data->word = inb_p(SMBHSTDAT) + (inb_p(SMBHSTDAT) << 8); + break; + } + + adapdata->unlock(adapdata, &adap->dev); + return 0; +} + +static u32 nct6775_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .smbus_xfer = nct6775_i2c_access, + .functionality = nct6775_i2c_func, +}; + +static int nct6775_i2c_add_adapter(acpi_handle acpi_wmi_mutex, struct nct6775_data *adapdata, struct i2c_adapter **padap) +{ + struct i2c_adapter *adap; + int retval; + + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + adap->algo = &smbus_algorithm; + + snprintf(adap->name, sizeof(adap->name), + "SMBus NCT67xx adapter at %04x", adapdata->addr); + + i2c_set_adapdata(adap, adapdata); + + retval = i2c_add_adapter(adap); + if (retval) { + kfree(adap); + return retval; + } + + *padap = adap; + return 0; +} + +static void nct6775_remove_adapter(struct i2c_adapter *adap) +{ + i2c_del_adapter(adap); + kfree(adap); +} + static int nct6775_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -3970,6 +4326,7 @@ static int nct6775_probe(struct platform_device *pdev) data->kind = sio_data->kind; data->sioreg = sio_data->sioreg; + data->acpi_wmi_mutex = sio_data->acpi_wmi_mutex; if (sio_data->access == access_direct) { data->addr = res->start; @@ -3980,11 +4337,34 @@ static int nct6775_probe(struct platform_device *pdev) data->write_value = nct6775_wmi_write_value; } - mutex_init(&data->update_lock); + if (data->acpi_wmi_mutex) { + data->lock = nct6775_wmi_lock; + data->unlock = nct6775_wmi_unlock; + } else { + mutex_init(&data->update_lock); + data->lock = nct6775_lock; + data->unlock = nct6775_unlock; + } + data->name = nct6775_device_names[data->kind]; data->bank = 0xff; /* Force initial bank selection */ platform_set_drvdata(pdev, data); + if (sio_data->i2c_enabled) { + switch (sio_data->kind) { + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6798: + nct6775_i2c_add_adapter(data->acpi_wmi_mutex, data, &data->i2c_adapter); + break; + default: + pr_err("i2c have not found"); + } + } + switch (data->kind) { case nct6106: data->in_num = 9; @@ -4775,6 +5155,16 @@ static int nct6775_probe(struct platform_device *pdev) return PTR_ERR_OR_ZERO(hwmon_dev); } +static int nct6775_remove(struct platform_device *pdev) +{ + struct nct6775_data *data = platform_get_drvdata(pdev); + + if (data->i2c_adapter) + nct6775_remove_adapter(data->i2c_adapter); + + return 0; +} + static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) { int val; @@ -4790,14 +5180,18 @@ static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) static int __maybe_unused nct6775_suspend(struct device *dev) { struct nct6775_data *data = nct6775_update_device(dev); + int err; + + err = data->lock(data); + if (err) + return err; - mutex_lock(&data->update_lock); data->vbat = data->read_value(data, data->REG_VBAT); if (data->kind == nct6775) { data->fandiv1 = data->read_value(data, NCT6775_REG_FANDIV1); data->fandiv2 = data->read_value(data, NCT6775_REG_FANDIV2); } - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return 0; } @@ -4806,10 +5200,13 @@ static int __maybe_unused nct6775_resume(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); struct nct6775_sio_data *sio_data = dev_get_platdata(dev); - int i, j, err = 0; + int i, j, err; u8 reg; - mutex_lock(&data->update_lock); + err = data->lock(data); + if (err) + return err; + data->bank = 0xff; /* Force initial bank selection */ err = sio_data->sio_enter(sio_data); @@ -4868,7 +5265,7 @@ static int __maybe_unused nct6775_resume(struct device *dev) abort: /* Force re-reading all values */ data->valid = false; - mutex_unlock(&data->update_lock); + data->unlock(data, dev); return err; } @@ -4881,6 +5278,7 @@ static struct platform_driver nct6775_driver = { .pm = &nct6775_dev_pm_ops, }, .probe = nct6775_probe, + .remove = nct6775_remove, }; /* nct6775_find() looks for a '627 in the Super-I/O config space */ @@ -4985,33 +5383,112 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) */ static struct platform_device *pdev[2]; -static const char * const asus_wmi_boards[] = { - "ProArt X570-CREATOR WIFI", - "Pro WS X570-ACE", - "PRIME B360-PLUS", - "PRIME B460-PLUS", - "PRIME X570-PRO", - "ROG CROSSHAIR VIII DARK HERO", - "ROG CROSSHAIR VIII FORMULA", - "ROG CROSSHAIR VIII HERO", - "ROG CROSSHAIR VIII IMPACT", - "ROG STRIX B550-E GAMING", - "ROG STRIX B550-F GAMING", - "ROG STRIX B550-F GAMING (WI-FI)", - "ROG STRIX B550-I GAMING", - "ROG STRIX X570-F GAMING", - "ROG STRIX Z390-E GAMING", - "ROG STRIX Z490-I GAMING", - "TUF GAMING B550M-PLUS", - "TUF GAMING B550M-PLUS (WI-FI)", - "TUF GAMING B550-PLUS", - "TUF GAMING B550-PRO", - "TUF GAMING X570-PLUS", - "TUF GAMING X570-PLUS (WI-FI)", - "TUF GAMING X570-PRO (WI-FI)", - "TUF GAMING Z490-PLUS", - "TUF GAMING Z490-PLUS (WI-FI)", +struct acpi_board_info { + enum sensor_access access; + char* acpi_mutex_name; + bool i2c; +}; + +#define DMI_ASUS_BOARD_INFO(name, type, adapter, mutex_name) \ +static struct acpi_board_info name = { \ + .access = type, \ + .acpi_mutex_name = mutex_name, \ + .i2c = adapter, \ +}; + +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_B360_PLUS, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_B450M_GAMING, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_B460_PLUS, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_X370_PRO, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_X399_A, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_X470_PRO, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_X570_PRO, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_Z270_A, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRIME_Z370_A, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PRO_WS_X570_ACE, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_PROART_X570_CREATOR_WIFI, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_CROSSHAIR_VI_HERO, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_CROSSHAIR_VIII_DARK_HERO, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_CROSSHAIR_VIII_FORMULA, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_CROSSHAIR_VIII_HERO, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_CROSSHAIR_VIII_IMPACT, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_MAXIMUS_VII_HERO, access_asuswmi, false, "\\_SB_.PCI0.LPCB.SIO1.MUT0") +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_B350_F_GAMING, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_B450_F_GAMING, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_B550_E_GAMING, access_asuswmi, true, "\\_SB.PCI0.SBRG.SIO1.MUT0") +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_B550_F_GAMING, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_B550_F_GAMING_WI_FI, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_B550_I_GAMING, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_X399_E_GAMING, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_X570_F_GAMING, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_Z270_E, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_Z370_E, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_Z390_E_GAMING, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_Z490_E_GAMING, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_ROG_STRIX_Z490_I_GAMING, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_B450_PLUS_GAMING, access_direct, true, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_B550_PLUS, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_B550_PRO, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_B550M_PLUS, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_B550M_PLUS_WI_FI, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_X570_PLUS, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_X570_PLUS_WI_FI, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_X570_PRO_WI_FI, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_Z490_PLUS, access_asuswmi, false, NULL) +DMI_ASUS_BOARD_INFO(acpi_board_TUF_GAMING_Z490_PLUS_WI_FI, access_asuswmi, false, NULL) + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, info) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = info, \ +} + +static const struct dmi_system_id asus_wmi_info_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME B360-PLUS", &acpi_board_PRIME_B360_PLUS), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME B450M-GAMING", &acpi_board_PRIME_B450M_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME B460-PLUS", &acpi_board_PRIME_B460_PLUS), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X370-PRO", &acpi_board_PRIME_X370_PRO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A", &acpi_board_PRIME_X399_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO", &acpi_board_PRIME_X470_PRO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &acpi_board_PRIME_X570_PRO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME Z270-A", &acpi_board_PRIME_Z270_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME Z370-A", &acpi_board_PRIME_Z370_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &acpi_board_PRO_WS_X570_ACE), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI", &acpi_board_PROART_X570_CREATOR_WIFI), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO", &acpi_board_ROG_CROSSHAIR_VI_HERO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &acpi_board_ROG_CROSSHAIR_VIII_DARK_HERO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &acpi_board_ROG_CROSSHAIR_VIII_FORMULA), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &acpi_board_ROG_CROSSHAIR_VIII_HERO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT", &acpi_board_ROG_CROSSHAIR_VIII_IMPACT), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS VII HERO", &acpi_board_ROG_MAXIMUS_VII_HERO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B350-F GAMING", &acpi_board_ROG_STRIX_B350_F_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING", &acpi_board_ROG_STRIX_B450_F_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &acpi_board_ROG_STRIX_B550_E_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-F GAMING", &acpi_board_ROG_STRIX_B550_F_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-F GAMING (WI-FI)", &acpi_board_ROG_STRIX_B550_F_GAMING_WI_FI), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &acpi_board_ROG_STRIX_B550_I_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING", &acpi_board_ROG_STRIX_X399_E_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-F GAMING", &acpi_board_ROG_STRIX_X570_F_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z270-E", &acpi_board_ROG_STRIX_Z270_E), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z370-E", &acpi_board_ROG_STRIX_Z370_E), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", &acpi_board_ROG_STRIX_Z390_E_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-E GAMING", &acpi_board_ROG_STRIX_Z490_E_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-I GAMING", &acpi_board_ROG_STRIX_Z490_I_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF B450 PLUS GAMING", &acpi_board_TUF_B450_PLUS_GAMING), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING B550-PLUS", &acpi_board_TUF_GAMING_B550_PLUS), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING B550-PRO", &acpi_board_TUF_GAMING_B550_PRO), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING B550M-PLUS", &acpi_board_TUF_GAMING_B550M_PLUS), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING B550M-PLUS (WI-FI)", &acpi_board_TUF_GAMING_B550M_PLUS_WI_FI), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X570-PLUS", &acpi_board_TUF_GAMING_X570_PLUS), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X570-PLUS (WI-FI)", &acpi_board_TUF_GAMING_X570_PLUS_WI_FI), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X570-PRO (WI-FI)", &acpi_board_TUF_GAMING_X570_PRO_WI_FI), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING Z490-PLUS", &acpi_board_TUF_GAMING_Z490_PLUS), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING Z490-PLUS (WI-FI)", &acpi_board_TUF_GAMING_Z490_PLUS_WI_FI), + {} }; +MODULE_DEVICE_TABLE(dmi, asus_wmi_info_table); static int __init sensors_nct6775_init(void) { @@ -5022,28 +5499,52 @@ static int __init sensors_nct6775_init(void) struct nct6775_sio_data sio_data; int sioaddr[2] = { 0x2e, 0x4e }; enum sensor_access access = access_direct; - const char *board_vendor, *board_name; - u8 tmp; + const struct dmi_system_id *dmi_id; + struct acpi_board_info *board_info; + bool i2c_enabled = false; + acpi_handle acpi_wmi_mutex = NULL; + acpi_status status; + u8 tmp = 0; err = platform_driver_register(&nct6775_driver); if (err) return err; - board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - board_name = dmi_get_system_info(DMI_BOARD_NAME); + dmi_id = dmi_first_match(asus_wmi_info_table); + if (dmi_id && dmi_id->driver_data) { + /* mutex is known */ + board_info = dmi_id->driver_data; + access = board_info->access; + i2c_enabled = board_info->i2c; + if (board_info->acpi_mutex_name) { + status = acpi_get_handle(NULL, board_info->acpi_mutex_name, &acpi_wmi_mutex); + if (ACPI_FAILURE(status)) { + pr_err("Could not get hardware access guard mutex.\n"); + i2c_enabled = false; + } else { + pr_info("Using Asus WMI mutex: %s\n", board_info->acpi_mutex_name); + access = access_direct; + } + } - if (board_name && board_vendor && - !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) { - err = match_string(asus_wmi_boards, ARRAY_SIZE(asus_wmi_boards), - board_name); - if (err >= 0) { + /* mutext is unknown or unavaible */ + if (access == access_asuswmi) { /* if reading chip id via WMI succeeds, use WMI */ - if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp)) { - pr_info("Using Asus WMI to access %#x chip.\n", tmp); - access = access_asuswmi; - } else { + if (nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp)) { + access = access_direct; pr_err("Can't read ChipID by Asus WMI.\n"); } + + if (!tmp) { + access = access_direct; + pr_err("Can't detect ChipID by Asus WMI.\n"); + } else { + pr_info("Using Asus WMI to access %#x chip.\n", tmp); + } + } + + if (i2c_enabled) { + pr_info("Asus i2c adapter can be enabled nct6775.\n"); } } @@ -5068,6 +5569,8 @@ static int __init sensors_nct6775_init(void) found = true; sio_data.access = access; + sio_data.acpi_wmi_mutex = acpi_wmi_mutex; + sio_data.i2c_enabled = i2c_enabled; if (access == access_asuswmi) { sio_data.sio_outb = superio_wmi_outb; @@ -5095,11 +5598,13 @@ static int __init sensors_nct6775_init(void) res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; res.flags = IORESOURCE_IO; - err = acpi_check_resource_conflict(&res); - if (err) { - platform_device_put(pdev[i]); - pdev[i] = NULL; - continue; + if (!acpi_wmi_mutex) { + err = acpi_check_resource_conflict(&res); + if (err) { + platform_device_put(pdev[i]); + pdev[i] = NULL; + continue; + } } err = platform_device_add_resources(pdev[i], &res, 1);