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