|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. | 
|  | * | 
|  | * Copyright 2024 Aleksa Savic <savicaleksa83@gmail.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/debugfs.h> | 
|  | #include <linux/hid.h> | 
|  | #include <linux/hwmon.h> | 
|  | #include <linux/jiffies.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/unaligned.h> | 
|  |  | 
|  | #define DRIVER_NAME	"asus_rog_ryujin" | 
|  |  | 
|  | #define USB_VENDOR_ID_ASUS_ROG		0x0b05 | 
|  | #define USB_PRODUCT_ID_RYUJIN_AIO	0x1988	/* ASUS ROG RYUJIN II 360 */ | 
|  |  | 
|  | #define STATUS_VALIDITY		1500	/* ms */ | 
|  | #define MAX_REPORT_LENGTH	65 | 
|  |  | 
|  | /* Cooler status report offsets */ | 
|  | #define RYUJIN_TEMP_SENSOR_1		3 | 
|  | #define RYUJIN_TEMP_SENSOR_2		4 | 
|  | #define RYUJIN_PUMP_SPEED		5 | 
|  | #define RYUJIN_INTERNAL_FAN_SPEED	7 | 
|  |  | 
|  | /* Cooler duty report offsets */ | 
|  | #define RYUJIN_PUMP_DUTY		4 | 
|  | #define RYUJIN_INTERNAL_FAN_DUTY	5 | 
|  |  | 
|  | /* Controller status (speeds) report offsets */ | 
|  | #define RYUJIN_CONTROLLER_SPEED_1	5 | 
|  | #define RYUJIN_CONTROLLER_SPEED_2	7 | 
|  | #define RYUJIN_CONTROLLER_SPEED_3	9 | 
|  | #define RYUJIN_CONTROLLER_SPEED_4	3 | 
|  |  | 
|  | /* Controller duty report offsets */ | 
|  | #define RYUJIN_CONTROLLER_DUTY		4 | 
|  |  | 
|  | /* Control commands and their inner offsets */ | 
|  | #define RYUJIN_CMD_PREFIX	0xEC | 
|  |  | 
|  | static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 }; | 
|  | static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A }; | 
|  | static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 }; | 
|  | static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 }; | 
|  |  | 
|  | #define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET	3 | 
|  | #define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET	4 | 
|  | static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 }; | 
|  |  | 
|  | #define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET	4 | 
|  | static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 }; | 
|  |  | 
|  | /* Command lengths */ | 
|  | #define GET_CMD_LENGTH	2	/* Same length for all get commands */ | 
|  | #define SET_CMD_LENGTH	5	/* Same length for all set commands */ | 
|  |  | 
|  | /* Command response headers */ | 
|  | #define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE		0x19 | 
|  | #define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE		0x1A | 
|  | #define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE	0x20 | 
|  | #define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE		0x21 | 
|  |  | 
|  | static const char *const rog_ryujin_temp_label[] = { | 
|  | "Coolant temp" | 
|  | }; | 
|  |  | 
|  | static const char *const rog_ryujin_speed_label[] = { | 
|  | "Pump speed", | 
|  | "Internal fan speed", | 
|  | "Controller fan 1 speed", | 
|  | "Controller fan 2 speed", | 
|  | "Controller fan 3 speed", | 
|  | "Controller fan 4 speed", | 
|  | }; | 
|  |  | 
|  | struct rog_ryujin_data { | 
|  | struct hid_device *hdev; | 
|  | struct device *hwmon_dev; | 
|  | /* For locking access to buffer */ | 
|  | struct mutex buffer_lock; | 
|  | /* For queueing multiple readers */ | 
|  | struct mutex status_report_request_mutex; | 
|  | /* For reinitializing the completions below */ | 
|  | spinlock_t status_report_request_lock; | 
|  | struct completion cooler_status_received; | 
|  | struct completion controller_status_received; | 
|  | struct completion cooler_duty_received; | 
|  | struct completion controller_duty_received; | 
|  | struct completion cooler_duty_set; | 
|  | struct completion controller_duty_set; | 
|  |  | 
|  | /* Sensor data */ | 
|  | s32 temp_input[1]; | 
|  | u16 speed_input[6];	/* Pump, internal fan and four controller fan speeds in RPM */ | 
|  | u8 duty_input[3];	/* Pump, internal fan and controller fan duty in PWM */ | 
|  |  | 
|  | u8 *buffer; | 
|  | unsigned long updated;	/* jiffies */ | 
|  | }; | 
|  |  | 
|  | static int rog_ryujin_percent_to_pwm(u16 val) | 
|  | { | 
|  | return DIV_ROUND_CLOSEST(val * 255, 100); | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_pwm_to_percent(long val) | 
|  | { | 
|  | return DIV_ROUND_CLOSEST(val * 100, 255); | 
|  | } | 
|  |  | 
|  | static umode_t rog_ryujin_is_visible(const void *data, | 
|  | enum hwmon_sensor_types type, u32 attr, int channel) | 
|  | { | 
|  | switch (type) { | 
|  | case hwmon_temp: | 
|  | switch (attr) { | 
|  | case hwmon_temp_label: | 
|  | case hwmon_temp_input: | 
|  | return 0444; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case hwmon_fan: | 
|  | switch (attr) { | 
|  | case hwmon_fan_label: | 
|  | case hwmon_fan_input: | 
|  | return 0444; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case hwmon_pwm: | 
|  | switch (attr) { | 
|  | case hwmon_pwm_input: | 
|  | return 0644; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Writes the command to the device with the rest of the report filled with zeroes */ | 
|  | static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->buffer_lock); | 
|  |  | 
|  | memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00); | 
|  | ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH); | 
|  |  | 
|  | mutex_unlock(&priv->buffer_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Assumes priv->status_report_request_mutex is locked */ | 
|  | static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, | 
|  | struct completion *status_completion) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | /* | 
|  | * Disable raw event parsing for a moment to safely reinitialize the | 
|  | * completion. Reinit is done because hidraw could have triggered | 
|  | * the raw event parsing and marked the passed in completion as done. | 
|  | */ | 
|  | spin_lock_bh(&priv->status_report_request_lock); | 
|  | reinit_completion(status_completion); | 
|  | spin_unlock_bh(&priv->status_report_request_lock); | 
|  |  | 
|  | /* Send command for getting data */ | 
|  | ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = wait_for_completion_interruptible_timeout(status_completion, | 
|  | msecs_to_jiffies(STATUS_VALIDITY)); | 
|  | if (ret == 0) | 
|  | return -ETIMEDOUT; | 
|  | else if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_get_status(struct rog_ryujin_data *priv) | 
|  | { | 
|  | int ret = mutex_lock_interruptible(&priv->status_report_request_mutex); | 
|  |  | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { | 
|  | /* Data is up to date */ | 
|  | goto unlock_and_return; | 
|  | } | 
|  |  | 
|  | /* Retrieve cooler status */ | 
|  | ret = | 
|  | rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH, | 
|  | &priv->cooler_status_received); | 
|  | if (ret < 0) | 
|  | goto unlock_and_return; | 
|  |  | 
|  | /* Retrieve controller status (speeds) */ | 
|  | ret = | 
|  | rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH, | 
|  | &priv->controller_status_received); | 
|  | if (ret < 0) | 
|  | goto unlock_and_return; | 
|  |  | 
|  | /* Retrieve cooler duty */ | 
|  | ret = | 
|  | rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH, | 
|  | &priv->cooler_duty_received); | 
|  | if (ret < 0) | 
|  | goto unlock_and_return; | 
|  |  | 
|  | /* Retrieve controller duty */ | 
|  | ret = | 
|  | rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH, | 
|  | &priv->controller_duty_received); | 
|  | if (ret < 0) | 
|  | goto unlock_and_return; | 
|  |  | 
|  | priv->updated = jiffies; | 
|  |  | 
|  | unlock_and_return: | 
|  | mutex_unlock(&priv->status_report_request_mutex); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel, long *val) | 
|  | { | 
|  | struct rog_ryujin_data *priv = dev_get_drvdata(dev); | 
|  | int ret = rog_ryujin_get_status(priv); | 
|  |  | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | switch (type) { | 
|  | case hwmon_temp: | 
|  | *val = priv->temp_input[channel]; | 
|  | break; | 
|  | case hwmon_fan: | 
|  | *val = priv->speed_input[channel]; | 
|  | break; | 
|  | case hwmon_pwm: | 
|  | switch (attr) { | 
|  | case hwmon_pwm_input: | 
|  | *val = priv->duty_input[channel]; | 
|  | break; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | return -EOPNOTSUPP;	/* unreachable */ | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel, const char **str) | 
|  | { | 
|  | switch (type) { | 
|  | case hwmon_temp: | 
|  | *str = rog_ryujin_temp_label[channel]; | 
|  | break; | 
|  | case hwmon_fan: | 
|  | *str = rog_ryujin_speed_label[channel]; | 
|  | break; | 
|  | default: | 
|  | return -EOPNOTSUPP;	/* unreachable */ | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val) | 
|  | { | 
|  | u8 set_cmd[SET_CMD_LENGTH]; | 
|  | int ret; | 
|  |  | 
|  | if (channel < 2) { | 
|  | /* | 
|  | * Retrieve cooler duty since both pump and internal fan are set | 
|  | * together, then write back with one of them modified. | 
|  | */ | 
|  | ret = mutex_lock_interruptible(&priv->status_report_request_mutex); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | ret = | 
|  | rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH, | 
|  | &priv->cooler_duty_received); | 
|  | if (ret < 0) | 
|  | goto unlock_and_return; | 
|  |  | 
|  | memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH); | 
|  |  | 
|  | /* Cooler duties are set as 0-100% */ | 
|  | val = rog_ryujin_pwm_to_percent(val); | 
|  |  | 
|  | if (channel == 0) { | 
|  | /* Cooler pump duty */ | 
|  | set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val; | 
|  | set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = | 
|  | rog_ryujin_pwm_to_percent(priv->duty_input[1]); | 
|  | } else if (channel == 1) { | 
|  | /* Cooler internal fan duty */ | 
|  | set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = | 
|  | rog_ryujin_pwm_to_percent(priv->duty_input[0]); | 
|  | set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val; | 
|  | } | 
|  |  | 
|  | ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set); | 
|  | unlock_and_return: | 
|  | mutex_unlock(&priv->status_report_request_mutex); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } else { | 
|  | /* | 
|  | * Controller fan duty (channel == 2). No need to retrieve current | 
|  | * duty, so just send the command. | 
|  | */ | 
|  | memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH); | 
|  | set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val; | 
|  |  | 
|  | ret = | 
|  | rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, | 
|  | &priv->controller_duty_set); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Lock onto this value until next refresh cycle */ | 
|  | priv->duty_input[channel] = val; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, | 
|  | long val) | 
|  | { | 
|  | struct rog_ryujin_data *priv = dev_get_drvdata(dev); | 
|  | int ret; | 
|  |  | 
|  | switch (type) { | 
|  | case hwmon_pwm: | 
|  | switch (attr) { | 
|  | case hwmon_pwm_input: | 
|  | if (val < 0 || val > 255) | 
|  | return -EINVAL; | 
|  |  | 
|  | ret = rog_ryujin_write_fixed_duty(priv, channel, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | break; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct hwmon_ops rog_ryujin_hwmon_ops = { | 
|  | .is_visible = rog_ryujin_is_visible, | 
|  | .read = rog_ryujin_read, | 
|  | .read_string = rog_ryujin_read_string, | 
|  | .write = rog_ryujin_write | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_channel_info *rog_ryujin_info[] = { | 
|  | HWMON_CHANNEL_INFO(temp, | 
|  | HWMON_T_INPUT | HWMON_T_LABEL), | 
|  | HWMON_CHANNEL_INFO(fan, | 
|  | HWMON_F_INPUT | HWMON_F_LABEL, | 
|  | HWMON_F_INPUT | HWMON_F_LABEL, | 
|  | HWMON_F_INPUT | HWMON_F_LABEL, | 
|  | HWMON_F_INPUT | HWMON_F_LABEL, | 
|  | HWMON_F_INPUT | HWMON_F_LABEL, | 
|  | HWMON_F_INPUT | HWMON_F_LABEL), | 
|  | HWMON_CHANNEL_INFO(pwm, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT), | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_chip_info rog_ryujin_chip_info = { | 
|  | .ops = &rog_ryujin_hwmon_ops, | 
|  | .info = rog_ryujin_info, | 
|  | }; | 
|  |  | 
|  | static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, | 
|  | int size) | 
|  | { | 
|  | struct rog_ryujin_data *priv = hid_get_drvdata(hdev); | 
|  |  | 
|  | if (data[0] != RYUJIN_CMD_PREFIX) | 
|  | return 0; | 
|  |  | 
|  | if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { | 
|  | /* Received coolant temp and speeds of pump and internal fan */ | 
|  | priv->temp_input[0] = | 
|  | data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100; | 
|  | priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED); | 
|  | priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED); | 
|  |  | 
|  | if (!completion_done(&priv->cooler_status_received)) | 
|  | complete_all(&priv->cooler_status_received); | 
|  | } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { | 
|  | /* Received speeds of four fans attached to the controller */ | 
|  | priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); | 
|  | priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2); | 
|  | priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3); | 
|  | priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4); | 
|  |  | 
|  | if (!completion_done(&priv->controller_status_received)) | 
|  | complete_all(&priv->controller_status_received); | 
|  | } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { | 
|  | /* Received report for pump and internal fan duties (in %) */ | 
|  | if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { | 
|  | /* | 
|  | * We received a report with zeroes for duty in both places. | 
|  | * The device returns this as a confirmation that setting values | 
|  | * is successful. If we initiated a write, mark it as complete. | 
|  | */ | 
|  | if (!completion_done(&priv->cooler_duty_set)) | 
|  | complete_all(&priv->cooler_duty_set); | 
|  | else if (!completion_done(&priv->cooler_duty_received)) | 
|  | /* | 
|  | * We didn't initiate a write, but received both zeroes. | 
|  | * This means that either both duties are actually zero, | 
|  | * or that we received a success report caused by userspace. | 
|  | * We're expecting a report, so parse it. | 
|  | */ | 
|  | goto read_cooler_duty; | 
|  | return 0; | 
|  | } | 
|  | read_cooler_duty: | 
|  | priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); | 
|  | priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]); | 
|  |  | 
|  | if (!completion_done(&priv->cooler_duty_received)) | 
|  | complete_all(&priv->cooler_duty_received); | 
|  | } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { | 
|  | /* Received report for controller duty for fans (in PWM) */ | 
|  | if (data[RYUJIN_CONTROLLER_DUTY] == 0) { | 
|  | /* | 
|  | * We received a report with a zero for duty. The device returns this as | 
|  | * a confirmation that setting the controller duty value was successful. | 
|  | * If we initiated a write, mark it as complete. | 
|  | */ | 
|  | if (!completion_done(&priv->controller_duty_set)) | 
|  | complete_all(&priv->controller_duty_set); | 
|  | else if (!completion_done(&priv->controller_duty_received)) | 
|  | /* | 
|  | * We didn't initiate a write, but received a zero for duty. | 
|  | * This means that either the duty is actually zero, or that | 
|  | * we received a success report caused by userspace. | 
|  | * We're expecting a report, so parse it. | 
|  | */ | 
|  | goto read_controller_duty; | 
|  | return 0; | 
|  | } | 
|  | read_controller_duty: | 
|  | priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; | 
|  |  | 
|  | if (!completion_done(&priv->controller_duty_received)) | 
|  | complete_all(&priv->controller_duty_received); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id) | 
|  | { | 
|  | struct rog_ryujin_data *priv; | 
|  | int ret; | 
|  |  | 
|  | priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | priv->hdev = hdev; | 
|  | hid_set_drvdata(hdev, priv); | 
|  |  | 
|  | /* | 
|  | * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making | 
|  | * the initial empty data invalid for rog_ryujin_read() without the need for | 
|  | * a special case there. | 
|  | */ | 
|  | priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY); | 
|  |  | 
|  | ret = hid_parse(hdev); | 
|  | if (ret) { | 
|  | hid_err(hdev, "hid parse failed with %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Enable hidraw so existing user-space tools can continue to work */ | 
|  | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); | 
|  | if (ret) { | 
|  | hid_err(hdev, "hid hw start failed with %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = hid_hw_open(hdev); | 
|  | if (ret) { | 
|  | hid_err(hdev, "hid hw open failed with %d\n", ret); | 
|  | goto fail_and_stop; | 
|  | } | 
|  |  | 
|  | priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL); | 
|  | if (!priv->buffer) { | 
|  | ret = -ENOMEM; | 
|  | goto fail_and_close; | 
|  | } | 
|  |  | 
|  | mutex_init(&priv->status_report_request_mutex); | 
|  | mutex_init(&priv->buffer_lock); | 
|  | spin_lock_init(&priv->status_report_request_lock); | 
|  | init_completion(&priv->cooler_status_received); | 
|  | init_completion(&priv->controller_status_received); | 
|  | init_completion(&priv->cooler_duty_received); | 
|  | init_completion(&priv->controller_duty_received); | 
|  | init_completion(&priv->cooler_duty_set); | 
|  | init_completion(&priv->controller_duty_set); | 
|  |  | 
|  | priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin", | 
|  | priv, &rog_ryujin_chip_info, NULL); | 
|  | if (IS_ERR(priv->hwmon_dev)) { | 
|  | ret = PTR_ERR(priv->hwmon_dev); | 
|  | hid_err(hdev, "hwmon registration failed with %d\n", ret); | 
|  | goto fail_and_close; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fail_and_close: | 
|  | hid_hw_close(hdev); | 
|  | fail_and_stop: | 
|  | hid_hw_stop(hdev); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void rog_ryujin_remove(struct hid_device *hdev) | 
|  | { | 
|  | struct rog_ryujin_data *priv = hid_get_drvdata(hdev); | 
|  |  | 
|  | hwmon_device_unregister(priv->hwmon_dev); | 
|  |  | 
|  | hid_hw_close(hdev); | 
|  | hid_hw_stop(hdev); | 
|  | } | 
|  |  | 
|  | static const struct hid_device_id rog_ryujin_table[] = { | 
|  | { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) }, | 
|  | { } | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(hid, rog_ryujin_table); | 
|  |  | 
|  | static struct hid_driver rog_ryujin_driver = { | 
|  | .name = "rog_ryujin", | 
|  | .id_table = rog_ryujin_table, | 
|  | .probe = rog_ryujin_probe, | 
|  | .remove = rog_ryujin_remove, | 
|  | .raw_event = rog_ryujin_raw_event, | 
|  | }; | 
|  |  | 
|  | static int __init rog_ryujin_init(void) | 
|  | { | 
|  | return hid_register_driver(&rog_ryujin_driver); | 
|  | } | 
|  |  | 
|  | static void __exit rog_ryujin_exit(void) | 
|  | { | 
|  | hid_unregister_driver(&rog_ryujin_driver); | 
|  | } | 
|  |  | 
|  | /* When compiled into the kernel, initialize after the HID bus */ | 
|  | late_initcall(rog_ryujin_init); | 
|  | module_exit(rog_ryujin_exit); | 
|  |  | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>"); | 
|  | MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); |