brintos

brintos / linux-shallow public Read only

0
0
Text · 16.6 KiB · e5e93a2 Raw
610 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * hwmon driver for Asus ROG Ryujin II 360 AIO cooler.4 *5 * Copyright 2024 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	"asus_rog_ryujin"17 18#define USB_VENDOR_ID_ASUS_ROG		0x0b0519#define USB_PRODUCT_ID_RYUJIN_AIO	0x1988	/* ASUS ROG RYUJIN II 360 */20 21#define STATUS_VALIDITY		1500	/* ms */22#define MAX_REPORT_LENGTH	6523 24/* Cooler status report offsets */25#define RYUJIN_TEMP_SENSOR_1		326#define RYUJIN_TEMP_SENSOR_2		427#define RYUJIN_PUMP_SPEED		528#define RYUJIN_INTERNAL_FAN_SPEED	729 30/* Cooler duty report offsets */31#define RYUJIN_PUMP_DUTY		432#define RYUJIN_INTERNAL_FAN_DUTY	533 34/* Controller status (speeds) report offsets */35#define RYUJIN_CONTROLLER_SPEED_1	536#define RYUJIN_CONTROLLER_SPEED_2	737#define RYUJIN_CONTROLLER_SPEED_3	938#define RYUJIN_CONTROLLER_SPEED_4	339 40/* Controller duty report offsets */41#define RYUJIN_CONTROLLER_DUTY		442 43/* Control commands and their inner offsets */44#define RYUJIN_CMD_PREFIX	0xEC45 46static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 };47static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A };48static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 };49static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 };50 51#define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET	352#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET	453static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 };54 55#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET	456static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 };57 58/* Command lengths */59#define GET_CMD_LENGTH	2	/* Same length for all get commands */60#define SET_CMD_LENGTH	5	/* Same length for all set commands */61 62/* Command response headers */63#define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE		0x1964#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE		0x1A65#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE	0x2066#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE		0x2167 68static const char *const rog_ryujin_temp_label[] = {69	"Coolant temp"70};71 72static const char *const rog_ryujin_speed_label[] = {73	"Pump speed",74	"Internal fan speed",75	"Controller fan 1 speed",76	"Controller fan 2 speed",77	"Controller fan 3 speed",78	"Controller fan 4 speed",79};80 81struct rog_ryujin_data {82	struct hid_device *hdev;83	struct device *hwmon_dev;84	/* For locking access to buffer */85	struct mutex buffer_lock;86	/* For queueing multiple readers */87	struct mutex status_report_request_mutex;88	/* For reinitializing the completions below */89	spinlock_t status_report_request_lock;90	struct completion cooler_status_received;91	struct completion controller_status_received;92	struct completion cooler_duty_received;93	struct completion controller_duty_received;94	struct completion cooler_duty_set;95	struct completion controller_duty_set;96 97	/* Sensor data */98	s32 temp_input[1];99	u16 speed_input[6];	/* Pump, internal fan and four controller fan speeds in RPM */100	u8 duty_input[3];	/* Pump, internal fan and controller fan duty in PWM */101 102	u8 *buffer;103	unsigned long updated;	/* jiffies */104};105 106static int rog_ryujin_percent_to_pwm(u16 val)107{108	return DIV_ROUND_CLOSEST(val * 255, 100);109}110 111static int rog_ryujin_pwm_to_percent(long val)112{113	return DIV_ROUND_CLOSEST(val * 100, 255);114}115 116static umode_t rog_ryujin_is_visible(const void *data,117				     enum hwmon_sensor_types type, u32 attr, int channel)118{119	switch (type) {120	case hwmon_temp:121		switch (attr) {122		case hwmon_temp_label:123		case hwmon_temp_input:124			return 0444;125		default:126			break;127		}128		break;129	case hwmon_fan:130		switch (attr) {131		case hwmon_fan_label:132		case hwmon_fan_input:133			return 0444;134		default:135			break;136		}137		break;138	case hwmon_pwm:139		switch (attr) {140		case hwmon_pwm_input:141			return 0644;142		default:143			break;144		}145		break;146	default:147		break;148	}149 150	return 0;151}152 153/* Writes the command to the device with the rest of the report filled with zeroes */154static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length)155{156	int ret;157 158	mutex_lock(&priv->buffer_lock);159 160	memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);161	ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);162 163	mutex_unlock(&priv->buffer_lock);164	return ret;165}166 167/* Assumes priv->status_report_request_mutex is locked */168static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length,169				  struct completion *status_completion)170{171	int ret;172 173	/*174	 * Disable raw event parsing for a moment to safely reinitialize the175	 * completion. Reinit is done because hidraw could have triggered176	 * the raw event parsing and marked the passed in completion as done.177	 */178	spin_lock_bh(&priv->status_report_request_lock);179	reinit_completion(status_completion);180	spin_unlock_bh(&priv->status_report_request_lock);181 182	/* Send command for getting data */183	ret = rog_ryujin_write_expanded(priv, cmd, cmd_length);184	if (ret < 0)185		return ret;186 187	ret = wait_for_completion_interruptible_timeout(status_completion,188							msecs_to_jiffies(STATUS_VALIDITY));189	if (ret == 0)190		return -ETIMEDOUT;191	else if (ret < 0)192		return ret;193 194	return 0;195}196 197static int rog_ryujin_get_status(struct rog_ryujin_data *priv)198{199	int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);200 201	if (ret < 0)202		return ret;203 204	if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {205		/* Data is up to date */206		goto unlock_and_return;207	}208 209	/* Retrieve cooler status */210	ret =211	    rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH,212				   &priv->cooler_status_received);213	if (ret < 0)214		goto unlock_and_return;215 216	/* Retrieve controller status (speeds) */217	ret =218	    rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,219				   &priv->controller_status_received);220	if (ret < 0)221		goto unlock_and_return;222 223	/* Retrieve cooler duty */224	ret =225	    rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,226				   &priv->cooler_duty_received);227	if (ret < 0)228		goto unlock_and_return;229 230	/* Retrieve controller duty */231	ret =232	    rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,233				   &priv->controller_duty_received);234	if (ret < 0)235		goto unlock_and_return;236 237	priv->updated = jiffies;238 239unlock_and_return:240	mutex_unlock(&priv->status_report_request_mutex);241	if (ret < 0)242		return ret;243 244	return 0;245}246 247static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type,248			   u32 attr, int channel, long *val)249{250	struct rog_ryujin_data *priv = dev_get_drvdata(dev);251	int ret = rog_ryujin_get_status(priv);252 253	if (ret < 0)254		return ret;255 256	switch (type) {257	case hwmon_temp:258		*val = priv->temp_input[channel];259		break;260	case hwmon_fan:261		*val = priv->speed_input[channel];262		break;263	case hwmon_pwm:264		switch (attr) {265		case hwmon_pwm_input:266			*val = priv->duty_input[channel];267			break;268		default:269			return -EOPNOTSUPP;270		}271		break;272	default:273		return -EOPNOTSUPP;	/* unreachable */274	}275 276	return 0;277}278 279static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type,280				  u32 attr, int channel, const char **str)281{282	switch (type) {283	case hwmon_temp:284		*str = rog_ryujin_temp_label[channel];285		break;286	case hwmon_fan:287		*str = rog_ryujin_speed_label[channel];288		break;289	default:290		return -EOPNOTSUPP;	/* unreachable */291	}292 293	return 0;294}295 296static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val)297{298	u8 set_cmd[SET_CMD_LENGTH];299	int ret;300 301	if (channel < 2) {302		/*303		 * Retrieve cooler duty since both pump and internal fan are set304		 * together, then write back with one of them modified.305		 */306		ret = mutex_lock_interruptible(&priv->status_report_request_mutex);307		if (ret < 0)308			return ret;309		ret =310		    rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,311					   &priv->cooler_duty_received);312		if (ret < 0)313			goto unlock_and_return;314 315		memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);316 317		/* Cooler duties are set as 0-100% */318		val = rog_ryujin_pwm_to_percent(val);319 320		if (channel == 0) {321			/* Cooler pump duty */322			set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val;323			set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] =324			    rog_ryujin_pwm_to_percent(priv->duty_input[1]);325		} else if (channel == 1) {326			/* Cooler internal fan duty */327			set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] =328			    rog_ryujin_pwm_to_percent(priv->duty_input[0]);329			set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;330		}331 332		ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);333unlock_and_return:334		mutex_unlock(&priv->status_report_request_mutex);335		if (ret < 0)336			return ret;337	} else {338		/*339		 * Controller fan duty (channel == 2). No need to retrieve current340		 * duty, so just send the command.341		 */342		memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH);343		set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val;344 345		ret =346		    rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH,347					   &priv->controller_duty_set);348		if (ret < 0)349			return ret;350	}351 352	/* Lock onto this value until next refresh cycle */353	priv->duty_input[channel] = val;354 355	return 0;356}357 358static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,359			    long val)360{361	struct rog_ryujin_data *priv = dev_get_drvdata(dev);362	int ret;363 364	switch (type) {365	case hwmon_pwm:366		switch (attr) {367		case hwmon_pwm_input:368			if (val < 0 || val > 255)369				return -EINVAL;370 371			ret = rog_ryujin_write_fixed_duty(priv, channel, val);372			if (ret < 0)373				return ret;374			break;375		default:376			return -EOPNOTSUPP;377		}378		break;379	default:380		return -EOPNOTSUPP;381	}382 383	return 0;384}385 386static const struct hwmon_ops rog_ryujin_hwmon_ops = {387	.is_visible = rog_ryujin_is_visible,388	.read = rog_ryujin_read,389	.read_string = rog_ryujin_read_string,390	.write = rog_ryujin_write391};392 393static const struct hwmon_channel_info *rog_ryujin_info[] = {394	HWMON_CHANNEL_INFO(temp,395			   HWMON_T_INPUT | HWMON_T_LABEL),396	HWMON_CHANNEL_INFO(fan,397			   HWMON_F_INPUT | HWMON_F_LABEL,398			   HWMON_F_INPUT | HWMON_F_LABEL,399			   HWMON_F_INPUT | HWMON_F_LABEL,400			   HWMON_F_INPUT | HWMON_F_LABEL,401			   HWMON_F_INPUT | HWMON_F_LABEL,402			   HWMON_F_INPUT | HWMON_F_LABEL),403	HWMON_CHANNEL_INFO(pwm,404			   HWMON_PWM_INPUT,405			   HWMON_PWM_INPUT,406			   HWMON_PWM_INPUT),407	NULL408};409 410static const struct hwmon_chip_info rog_ryujin_chip_info = {411	.ops = &rog_ryujin_hwmon_ops,412	.info = rog_ryujin_info,413};414 415static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,416				int size)417{418	struct rog_ryujin_data *priv = hid_get_drvdata(hdev);419 420	if (data[0] != RYUJIN_CMD_PREFIX)421		return 0;422 423	if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {424		/* Received coolant temp and speeds of pump and internal fan */425		priv->temp_input[0] =426		    data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100;427		priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED);428		priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED);429 430		if (!completion_done(&priv->cooler_status_received))431			complete_all(&priv->cooler_status_received);432	} else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) {433		/* Received speeds of four fans attached to the controller */434		priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1);435		priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2);436		priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3);437		priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4);438 439		if (!completion_done(&priv->controller_status_received))440			complete_all(&priv->controller_status_received);441	} else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) {442		/* Received report for pump and internal fan duties (in %) */443		if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) {444			/*445			 * We received a report with zeroes for duty in both places.446			 * The device returns this as a confirmation that setting values447			 * is successful. If we initiated a write, mark it as complete.448			 */449			if (!completion_done(&priv->cooler_duty_set))450				complete_all(&priv->cooler_duty_set);451			else if (!completion_done(&priv->cooler_duty_received))452				/*453				 * We didn't initiate a write, but received both zeroes.454				 * This means that either both duties are actually zero,455				 * or that we received a success report caused by userspace.456				 * We're expecting a report, so parse it.457				 */458				goto read_cooler_duty;459			return 0;460		}461read_cooler_duty:462		priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]);463		priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]);464 465		if (!completion_done(&priv->cooler_duty_received))466			complete_all(&priv->cooler_duty_received);467	} else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) {468		/* Received report for controller duty for fans (in PWM) */469		if (data[RYUJIN_CONTROLLER_DUTY] == 0) {470			/*471			 * We received a report with a zero for duty. The device returns this as472			 * a confirmation that setting the controller duty value was successful.473			 * If we initiated a write, mark it as complete.474			 */475			if (!completion_done(&priv->controller_duty_set))476				complete_all(&priv->controller_duty_set);477			else if (!completion_done(&priv->controller_duty_received))478				/*479				 * We didn't initiate a write, but received a zero for duty.480				 * This means that either the duty is actually zero, or that481				 * we received a success report caused by userspace.482				 * We're expecting a report, so parse it.483				 */484				goto read_controller_duty;485			return 0;486		}487read_controller_duty:488		priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY];489 490		if (!completion_done(&priv->controller_duty_received))491			complete_all(&priv->controller_duty_received);492	}493 494	return 0;495}496 497static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id)498{499	struct rog_ryujin_data *priv;500	int ret;501 502	priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);503	if (!priv)504		return -ENOMEM;505 506	priv->hdev = hdev;507	hid_set_drvdata(hdev, priv);508 509	/*510	 * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making511	 * the initial empty data invalid for rog_ryujin_read() without the need for512	 * a special case there.513	 */514	priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);515 516	ret = hid_parse(hdev);517	if (ret) {518		hid_err(hdev, "hid parse failed with %d\n", ret);519		return ret;520	}521 522	/* Enable hidraw so existing user-space tools can continue to work */523	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);524	if (ret) {525		hid_err(hdev, "hid hw start failed with %d\n", ret);526		return ret;527	}528 529	ret = hid_hw_open(hdev);530	if (ret) {531		hid_err(hdev, "hid hw open failed with %d\n", ret);532		goto fail_and_stop;533	}534 535	priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);536	if (!priv->buffer) {537		ret = -ENOMEM;538		goto fail_and_close;539	}540 541	mutex_init(&priv->status_report_request_mutex);542	mutex_init(&priv->buffer_lock);543	spin_lock_init(&priv->status_report_request_lock);544	init_completion(&priv->cooler_status_received);545	init_completion(&priv->controller_status_received);546	init_completion(&priv->cooler_duty_received);547	init_completion(&priv->controller_duty_received);548	init_completion(&priv->cooler_duty_set);549	init_completion(&priv->controller_duty_set);550 551	priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin",552							  priv, &rog_ryujin_chip_info, NULL);553	if (IS_ERR(priv->hwmon_dev)) {554		ret = PTR_ERR(priv->hwmon_dev);555		hid_err(hdev, "hwmon registration failed with %d\n", ret);556		goto fail_and_close;557	}558 559	return 0;560 561fail_and_close:562	hid_hw_close(hdev);563fail_and_stop:564	hid_hw_stop(hdev);565	return ret;566}567 568static void rog_ryujin_remove(struct hid_device *hdev)569{570	struct rog_ryujin_data *priv = hid_get_drvdata(hdev);571 572	hwmon_device_unregister(priv->hwmon_dev);573 574	hid_hw_close(hdev);575	hid_hw_stop(hdev);576}577 578static const struct hid_device_id rog_ryujin_table[] = {579	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) },580	{ }581};582 583MODULE_DEVICE_TABLE(hid, rog_ryujin_table);584 585static struct hid_driver rog_ryujin_driver = {586	.name = "rog_ryujin",587	.id_table = rog_ryujin_table,588	.probe = rog_ryujin_probe,589	.remove = rog_ryujin_remove,590	.raw_event = rog_ryujin_raw_event,591};592 593static int __init rog_ryujin_init(void)594{595	return hid_register_driver(&rog_ryujin_driver);596}597 598static void __exit rog_ryujin_exit(void)599{600	hid_unregister_driver(&rog_ryujin_driver);601}602 603/* When compiled into the kernel, initialize after the HID bus */604late_initcall(rog_ryujin_init);605module_exit(rog_ryujin_exit);606 607MODULE_LICENSE("GPL");608MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");609MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler");610