diff options
Diffstat (limited to 'drivers/platform/x86/gigabyte-wmi.c')
| -rw-r--r-- | drivers/platform/x86/gigabyte-wmi.c | 203 | 
1 files changed, 203 insertions, 0 deletions
| diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c new file mode 100644 index 000000000000..13d57434e60f --- /dev/null +++ b/drivers/platform/x86/gigabyte-wmi.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + *  Copyright (C) 2021 Thomas Weißschuh <thomas@weissschuh.net> + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/module.h> +#include <linux/wmi.h> + +#define GIGABYTE_WMI_GUID	"DEADBEEF-2001-0000-00A0-C90629100000" +#define NUM_TEMPERATURE_SENSORS	6 + +static bool force_load; +module_param(force_load, bool, 0444); +MODULE_PARM_DESC(force_load, "Force loading on unknown platform"); + +static u8 usable_sensors_mask; + +enum gigabyte_wmi_commandtype { +	GIGABYTE_WMI_BUILD_DATE_QUERY       =   0x1, +	GIGABYTE_WMI_MAINBOARD_TYPE_QUERY   =   0x2, +	GIGABYTE_WMI_FIRMWARE_VERSION_QUERY =   0x4, +	GIGABYTE_WMI_MAINBOARD_NAME_QUERY   =   0x5, +	GIGABYTE_WMI_TEMPERATURE_QUERY      = 0x125, +}; + +struct gigabyte_wmi_args { +	u32 arg1; +}; + +static int gigabyte_wmi_perform_query(struct wmi_device *wdev, +				      enum gigabyte_wmi_commandtype command, +				      struct gigabyte_wmi_args *args, struct acpi_buffer *out) +{ +	const struct acpi_buffer in = { +		.length = sizeof(*args), +		.pointer = args, +	}; + +	acpi_status ret = wmidev_evaluate_method(wdev, 0x0, command, &in, out); + +	if (ACPI_FAILURE(ret)) +		return -EIO; + +	return 0; +} + +static int gigabyte_wmi_query_integer(struct wmi_device *wdev, +				      enum gigabyte_wmi_commandtype command, +				      struct gigabyte_wmi_args *args, u64 *res) +{ +	union acpi_object *obj; +	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; +	int ret; + +	ret = gigabyte_wmi_perform_query(wdev, command, args, &result); +	if (ret) +		return ret; +	obj = result.pointer; +	if (obj && obj->type == ACPI_TYPE_INTEGER) +		*res = obj->integer.value; +	else +		ret = -EIO; +	kfree(result.pointer); +	return ret; +} + +static int gigabyte_wmi_temperature(struct wmi_device *wdev, u8 sensor, long *res) +{ +	struct gigabyte_wmi_args args = { +		.arg1 = sensor, +	}; +	u64 temp; +	acpi_status ret; + +	ret = gigabyte_wmi_query_integer(wdev, GIGABYTE_WMI_TEMPERATURE_QUERY, &args, &temp); +	if (ret == 0) { +		if (temp == 0) +			return -ENODEV; +		*res = (s8)temp * 1000; // value is a signed 8-bit integer +	} +	return ret; +} + +static int gigabyte_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, +				   u32 attr, int channel, long *val) +{ +	struct wmi_device *wdev = dev_get_drvdata(dev); + +	return gigabyte_wmi_temperature(wdev, channel, val); +} + +static umode_t gigabyte_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, +					     u32 attr, int channel) +{ +	return usable_sensors_mask & BIT(channel) ? 0444  : 0; +} + +static const struct hwmon_channel_info *gigabyte_wmi_hwmon_info[] = { +	HWMON_CHANNEL_INFO(temp, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT), +	NULL +}; + +static const struct hwmon_ops gigabyte_wmi_hwmon_ops = { +	.read = gigabyte_wmi_hwmon_read, +	.is_visible = gigabyte_wmi_hwmon_is_visible, +}; + +static const struct hwmon_chip_info gigabyte_wmi_hwmon_chip_info = { +	.ops = &gigabyte_wmi_hwmon_ops, +	.info = gigabyte_wmi_hwmon_info, +}; + +static u8 gigabyte_wmi_detect_sensor_usability(struct wmi_device *wdev) +{ +	int i; +	long temp; +	u8 r = 0; + +	for (i = 0; i < NUM_TEMPERATURE_SENSORS; i++) { +		if (!gigabyte_wmi_temperature(wdev, i, &temp)) +			r |= BIT(i); +	} +	return r; +} + +static const struct dmi_system_id gigabyte_wmi_known_working_platforms[] = { +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550 GAMING X V2"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550M AORUS PRO-P"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550M DS3H"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "Z390 I AORUS PRO WIFI-CF"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "X570 AORUS ELITE"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "X570 I AORUS PRO WIFI"), +	}}, +	{ } +}; + +static int gigabyte_wmi_probe(struct wmi_device *wdev, const void *context) +{ +	struct device *hwmon_dev; + +	if (!dmi_check_system(gigabyte_wmi_known_working_platforms)) { +		if (!force_load) +			return -ENODEV; +		dev_warn(&wdev->dev, "Forcing load on unknown platform"); +	} + +	usable_sensors_mask = gigabyte_wmi_detect_sensor_usability(wdev); +	if (!usable_sensors_mask) { +		dev_info(&wdev->dev, "No temperature sensors usable"); +		return -ENODEV; +	} + +	hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev, "gigabyte_wmi", wdev, +							 &gigabyte_wmi_hwmon_chip_info, NULL); + +	return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct wmi_device_id gigabyte_wmi_id_table[] = { +	{ GIGABYTE_WMI_GUID, NULL }, +	{ } +}; + +static struct wmi_driver gigabyte_wmi_driver = { +	.driver = { +		.name = "gigabyte-wmi", +	}, +	.id_table = gigabyte_wmi_id_table, +	.probe = gigabyte_wmi_probe, +}; +module_wmi_driver(gigabyte_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, gigabyte_wmi_id_table); +MODULE_AUTHOR("Thomas Weißschuh <thomas@weissschuh.net>"); +MODULE_DESCRIPTION("Gigabyte WMI temperature driver"); +MODULE_LICENSE("GPL"); | 
