brintos

brintos / linux-shallow public Read only

0
0
Text · 10.6 KiB · 27487e2 Raw
431 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * hwmon driver for Gigabyte AORUS Waterforce AIO CPU coolers: X240, X280 and X360.4 *5 * Copyright 2023 Aleksa Savic <savicaleksa83@gmail.com>6 */7 8#include <linux/debugfs.h>9#include <linux/hid.h>10#include <linux/hwmon.h>11#include <linux/jiffies.h>12#include <linux/module.h>13#include <linux/spinlock.h>14#include <linux/unaligned.h>15 16#define DRIVER_NAME	"gigabyte_waterforce"17 18#define USB_VENDOR_ID_GIGABYTE		0x104419#define USB_PRODUCT_ID_WATERFORCE	0x7a4d	/* Gigabyte AORUS WATERFORCE X240, X280 and X360 */20 21#define STATUS_VALIDITY		(2 * 1000)	/* ms */22#define MAX_REPORT_LENGTH	614423 24#define WATERFORCE_TEMP_SENSOR	0xD25#define WATERFORCE_FAN_SPEED	0x0226#define WATERFORCE_PUMP_SPEED	0x0527#define WATERFORCE_FAN_DUTY	0x0828#define WATERFORCE_PUMP_DUTY	0x0929 30/* Control commands, inner offsets and lengths */31static const u8 get_status_cmd[] = { 0x99, 0xDA };32 33#define FIRMWARE_VER_START_OFFSET_1	234#define FIRMWARE_VER_START_OFFSET_2	335static const u8 get_firmware_ver_cmd[] = { 0x99, 0xD6 };36 37/* Command lengths */38#define GET_STATUS_CMD_LENGTH		239#define GET_FIRMWARE_VER_CMD_LENGTH	240 41static const char *const waterforce_temp_label[] = {42	"Coolant temp"43};44 45static const char *const waterforce_speed_label[] = {46	"Fan speed",47	"Pump speed"48};49 50struct waterforce_data {51	struct hid_device *hdev;52	struct device *hwmon_dev;53	struct dentry *debugfs;54	/* For locking access to buffer */55	struct mutex buffer_lock;56	/* For queueing multiple readers */57	struct mutex status_report_request_mutex;58	/* For reinitializing the completion below */59	spinlock_t status_report_request_lock;60	struct completion status_report_received;61	struct completion fw_version_processed;62 63	/* Sensor data */64	s32 temp_input[1];65	u16 speed_input[2];	/* Fan and pump speed in RPM */66	u8 duty_input[2];	/* Fan and pump duty in 0-100% */67 68	u8 *buffer;69	int firmware_version;70	unsigned long updated;	/* jiffies */71};72 73static umode_t waterforce_is_visible(const void *data,74				     enum hwmon_sensor_types type, u32 attr, int channel)75{76	switch (type) {77	case hwmon_temp:78		switch (attr) {79		case hwmon_temp_label:80		case hwmon_temp_input:81			return 0444;82		default:83			break;84		}85		break;86	case hwmon_fan:87		switch (attr) {88		case hwmon_fan_label:89		case hwmon_fan_input:90			return 0444;91		default:92			break;93		}94		break;95	case hwmon_pwm:96		switch (attr) {97		case hwmon_pwm_input:98			return 0444;99		default:100			break;101		}102		break;103	default:104		break;105	}106 107	return 0;108}109 110/* Writes the command to the device with the rest of the report filled with zeroes */111static int waterforce_write_expanded(struct waterforce_data *priv, const u8 *cmd, int cmd_length)112{113	int ret;114 115	mutex_lock(&priv->buffer_lock);116 117	memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);118	ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);119 120	mutex_unlock(&priv->buffer_lock);121	return ret;122}123 124static int waterforce_get_status(struct waterforce_data *priv)125{126	int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);127 128	if (ret < 0)129		return ret;130 131	if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {132		/* Data is up to date */133		goto unlock_and_return;134	}135 136	/*137	 * Disable raw event parsing for a moment to safely reinitialize the138	 * completion. Reinit is done because hidraw could have triggered139	 * the raw event parsing and marked the priv->status_report_received140	 * completion as done.141	 */142	spin_lock_bh(&priv->status_report_request_lock);143	reinit_completion(&priv->status_report_received);144	spin_unlock_bh(&priv->status_report_request_lock);145 146	/* Send command for getting status */147	ret = waterforce_write_expanded(priv, get_status_cmd, GET_STATUS_CMD_LENGTH);148	if (ret < 0)149		goto unlock_and_return;150 151	ret = wait_for_completion_interruptible_timeout(&priv->status_report_received,152							msecs_to_jiffies(STATUS_VALIDITY));153	if (ret == 0)154		ret = -ETIMEDOUT;155 156unlock_and_return:157	mutex_unlock(&priv->status_report_request_mutex);158	if (ret < 0)159		return ret;160 161	return 0;162}163 164static int waterforce_read(struct device *dev, enum hwmon_sensor_types type,165			   u32 attr, int channel, long *val)166{167	struct waterforce_data *priv = dev_get_drvdata(dev);168	int ret = waterforce_get_status(priv);169 170	if (ret < 0)171		return ret;172 173	switch (type) {174	case hwmon_temp:175		*val = priv->temp_input[channel];176		break;177	case hwmon_fan:178		*val = priv->speed_input[channel];179		break;180	case hwmon_pwm:181		switch (attr) {182		case hwmon_pwm_input:183			*val = DIV_ROUND_CLOSEST(priv->duty_input[channel] * 255, 100);184			break;185		default:186			return -EOPNOTSUPP;187		}188		break;189	default:190		return -EOPNOTSUPP;	/* unreachable */191	}192 193	return 0;194}195 196static int waterforce_read_string(struct device *dev, enum hwmon_sensor_types type,197				  u32 attr, int channel, const char **str)198{199	switch (type) {200	case hwmon_temp:201		*str = waterforce_temp_label[channel];202		break;203	case hwmon_fan:204		*str = waterforce_speed_label[channel];205		break;206	default:207		return -EOPNOTSUPP;	/* unreachable */208	}209 210	return 0;211}212 213static int waterforce_get_fw_ver(struct hid_device *hdev)214{215	struct waterforce_data *priv = hid_get_drvdata(hdev);216	int ret;217 218	ret = waterforce_write_expanded(priv, get_firmware_ver_cmd, GET_FIRMWARE_VER_CMD_LENGTH);219	if (ret < 0)220		return ret;221 222	ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed,223							msecs_to_jiffies(STATUS_VALIDITY));224	if (ret == 0)225		return -ETIMEDOUT;226	else if (ret < 0)227		return ret;228 229	return 0;230}231 232static const struct hwmon_ops waterforce_hwmon_ops = {233	.is_visible = waterforce_is_visible,234	.read = waterforce_read,235	.read_string = waterforce_read_string236};237 238static const struct hwmon_channel_info *waterforce_info[] = {239	HWMON_CHANNEL_INFO(temp,240			   HWMON_T_INPUT | HWMON_T_LABEL),241	HWMON_CHANNEL_INFO(fan,242			   HWMON_F_INPUT | HWMON_F_LABEL,243			   HWMON_F_INPUT | HWMON_F_LABEL),244	HWMON_CHANNEL_INFO(pwm,245			   HWMON_PWM_INPUT,246			   HWMON_PWM_INPUT),247	NULL248};249 250static const struct hwmon_chip_info waterforce_chip_info = {251	.ops = &waterforce_hwmon_ops,252	.info = waterforce_info,253};254 255static int waterforce_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,256				int size)257{258	struct waterforce_data *priv = hid_get_drvdata(hdev);259 260	if (data[0] == get_firmware_ver_cmd[0] && data[1] == get_firmware_ver_cmd[1]) {261		/* Received a firmware version report */262		priv->firmware_version =263		    data[FIRMWARE_VER_START_OFFSET_1] * 10 + data[FIRMWARE_VER_START_OFFSET_2];264 265		if (!completion_done(&priv->fw_version_processed))266			complete_all(&priv->fw_version_processed);267		return 0;268	}269 270	if (data[0] != get_status_cmd[0] || data[1] != get_status_cmd[1])271		return 0;272 273	priv->temp_input[0] = data[WATERFORCE_TEMP_SENSOR] * 1000;274	priv->speed_input[0] = get_unaligned_le16(data + WATERFORCE_FAN_SPEED);275	priv->speed_input[1] = get_unaligned_le16(data + WATERFORCE_PUMP_SPEED);276	priv->duty_input[0] = data[WATERFORCE_FAN_DUTY];277	priv->duty_input[1] = data[WATERFORCE_PUMP_DUTY];278 279	spin_lock(&priv->status_report_request_lock);280	if (!completion_done(&priv->status_report_received))281		complete_all(&priv->status_report_received);282	spin_unlock(&priv->status_report_request_lock);283 284	priv->updated = jiffies;285 286	return 0;287}288 289static int firmware_version_show(struct seq_file *seqf, void *unused)290{291	struct waterforce_data *priv = seqf->private;292 293	seq_printf(seqf, "%u\n", priv->firmware_version);294 295	return 0;296}297DEFINE_SHOW_ATTRIBUTE(firmware_version);298 299static void waterforce_debugfs_init(struct waterforce_data *priv)300{301	char name[64];302 303	if (!priv->firmware_version)304		return;	/* There's nothing to show in debugfs */305 306	scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));307 308	priv->debugfs = debugfs_create_dir(name, NULL);309	debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);310}311 312static int waterforce_probe(struct hid_device *hdev, const struct hid_device_id *id)313{314	struct waterforce_data *priv;315	int ret;316 317	priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);318	if (!priv)319		return -ENOMEM;320 321	priv->hdev = hdev;322	hid_set_drvdata(hdev, priv);323 324	/*325	 * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making326	 * the initial empty data invalid for waterforce_read() without the need for327	 * a special case there.328	 */329	priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);330 331	ret = hid_parse(hdev);332	if (ret) {333		hid_err(hdev, "hid parse failed with %d\n", ret);334		return ret;335	}336 337	/*338	 * Enable hidraw so existing user-space tools can continue to work.339	 */340	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);341	if (ret) {342		hid_err(hdev, "hid hw start failed with %d\n", ret);343		return ret;344	}345 346	ret = hid_hw_open(hdev);347	if (ret) {348		hid_err(hdev, "hid hw open failed with %d\n", ret);349		goto fail_and_stop;350	}351 352	priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);353	if (!priv->buffer) {354		ret = -ENOMEM;355		goto fail_and_close;356	}357 358	mutex_init(&priv->status_report_request_mutex);359	mutex_init(&priv->buffer_lock);360	spin_lock_init(&priv->status_report_request_lock);361	init_completion(&priv->status_report_received);362	init_completion(&priv->fw_version_processed);363 364	hid_device_io_start(hdev);365	ret = waterforce_get_fw_ver(hdev);366	if (ret < 0)367		hid_warn(hdev, "fw version request failed with %d\n", ret);368 369	priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "waterforce",370							  priv, &waterforce_chip_info, NULL);371	if (IS_ERR(priv->hwmon_dev)) {372		ret = PTR_ERR(priv->hwmon_dev);373		hid_err(hdev, "hwmon registration failed with %d\n", ret);374		goto fail_and_close;375	}376 377	waterforce_debugfs_init(priv);378 379	return 0;380 381fail_and_close:382	hid_hw_close(hdev);383fail_and_stop:384	hid_hw_stop(hdev);385	return ret;386}387 388static void waterforce_remove(struct hid_device *hdev)389{390	struct waterforce_data *priv = hid_get_drvdata(hdev);391 392	debugfs_remove_recursive(priv->debugfs);393	hwmon_device_unregister(priv->hwmon_dev);394 395	hid_hw_close(hdev);396	hid_hw_stop(hdev);397}398 399static const struct hid_device_id waterforce_table[] = {400	{ HID_USB_DEVICE(USB_VENDOR_ID_GIGABYTE, USB_PRODUCT_ID_WATERFORCE) },401	{ }402};403 404MODULE_DEVICE_TABLE(hid, waterforce_table);405 406static struct hid_driver waterforce_driver = {407	.name = "waterforce",408	.id_table = waterforce_table,409	.probe = waterforce_probe,410	.remove = waterforce_remove,411	.raw_event = waterforce_raw_event,412};413 414static int __init waterforce_init(void)415{416	return hid_register_driver(&waterforce_driver);417}418 419static void __exit waterforce_exit(void)420{421	hid_unregister_driver(&waterforce_driver);422}423 424/* When compiled into the kernel, initialize after the HID bus */425late_initcall(waterforce_init);426module_exit(waterforce_exit);427 428MODULE_LICENSE("GPL");429MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");430MODULE_DESCRIPTION("Hwmon driver for Gigabyte AORUS Waterforce AIO coolers");431