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