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 <asm/unaligned.h>
15
16#define DRIVER_NAME "asus_rog_ryujin"
17
18#define USB_VENDOR_ID_ASUS_ROG 0x0b05
19#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */
20
21#define STATUS_VALIDITY 1500 /* ms */
22#define MAX_REPORT_LENGTH 65
23
24/* Cooler status report offsets */
25#define RYUJIN_TEMP_SENSOR_1 3
26#define RYUJIN_TEMP_SENSOR_2 4
27#define RYUJIN_PUMP_SPEED 5
28#define RYUJIN_INTERNAL_FAN_SPEED 7
29
30/* Cooler duty report offsets */
31#define RYUJIN_PUMP_DUTY 4
32#define RYUJIN_INTERNAL_FAN_DUTY 5
33
34/* Controller status (speeds) report offsets */
35#define RYUJIN_CONTROLLER_SPEED_1 5
36#define RYUJIN_CONTROLLER_SPEED_2 7
37#define RYUJIN_CONTROLLER_SPEED_3 9
38#define RYUJIN_CONTROLLER_SPEED_4 3
39
40/* Controller duty report offsets */
41#define RYUJIN_CONTROLLER_DUTY 4
42
43/* Control commands and their inner offsets */
44#define RYUJIN_CMD_PREFIX 0xEC
45
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 3
52#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4
53static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 };
54
55#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4
56static 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 0x19
64#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE 0x1A
65#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE 0x20
66#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE 0x21
67
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(dest: priv->buffer, MAX_REPORT_LENGTH, src: cmd, count: cmd_length, pad: 0x00);
161 ret = hid_hw_output_report(hdev: priv->hdev, buf: priv->buffer, MAX_REPORT_LENGTH);
162
163 mutex_unlock(lock: &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 the
175 * completion. Reinit is done because hidraw could have triggered
176 * the raw event parsing and marked the passed in completion as done.
177 */
178 spin_lock_bh(lock: &priv->status_report_request_lock);
179 reinit_completion(x: status_completion);
180 spin_unlock_bh(lock: &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(x: status_completion,
188 timeout: 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, cmd: get_cooler_status_cmd, GET_CMD_LENGTH,
212 status_completion: &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, cmd: get_controller_speed_cmd, GET_CMD_LENGTH,
219 status_completion: &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, cmd: get_cooler_duty_cmd, GET_CMD_LENGTH,
226 status_completion: &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, cmd: get_controller_duty_cmd, GET_CMD_LENGTH,
233 status_completion: &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(lock: &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 set
304 * 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, cmd: get_cooler_duty_cmd, GET_CMD_LENGTH,
311 status_completion: &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(val: 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(val: priv->duty_input[0]);
329 set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;
330 }
331
332 ret = rog_ryujin_execute_cmd(priv, cmd: set_cmd, SET_CMD_LENGTH, status_completion: &priv->cooler_duty_set);
333unlock_and_return:
334 mutex_unlock(lock: &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 current
340 * 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, cmd: set_cmd, SET_CMD_LENGTH,
347 status_completion: &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_write
391};
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 NULL
408};
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(p: data + RYUJIN_PUMP_SPEED);
428 priv->speed_input[1] = get_unaligned_le16(p: data + RYUJIN_INTERNAL_FAN_SPEED);
429
430 if (!completion_done(x: &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(p: data + RYUJIN_CONTROLLER_SPEED_1);
435 priv->speed_input[3] = get_unaligned_le16(p: data + RYUJIN_CONTROLLER_SPEED_2);
436 priv->speed_input[4] = get_unaligned_le16(p: data + RYUJIN_CONTROLLER_SPEED_3);
437 priv->speed_input[5] = get_unaligned_le16(p: data + RYUJIN_CONTROLLER_SPEED_4);
438
439 if (!completion_done(x: &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 values
447 * is successful. If we initiated a write, mark it as complete.
448 */
449 if (!completion_done(x: &priv->cooler_duty_set))
450 complete_all(&priv->cooler_duty_set);
451 else if (!completion_done(x: &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(val: data[RYUJIN_PUMP_DUTY]);
463 priv->duty_input[1] = rog_ryujin_percent_to_pwm(val: data[RYUJIN_INTERNAL_FAN_DUTY]);
464
465 if (!completion_done(x: &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 as
472 * 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(x: &priv->controller_duty_set))
476 complete_all(&priv->controller_duty_set);
477 else if (!completion_done(x: &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 that
481 * 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(x: &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(dev: &hdev->dev, size: sizeof(*priv), GFP_KERNEL);
503 if (!priv)
504 return -ENOMEM;
505
506 priv->hdev = hdev;
507 hid_set_drvdata(hdev, data: priv);
508
509 /*
510 * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
511 * the initial empty data invalid for rog_ryujin_read() without the need for
512 * 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(dev: &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(x: &priv->cooler_status_received);
545 init_completion(x: &priv->controller_status_received);
546 init_completion(x: &priv->cooler_duty_received);
547 init_completion(x: &priv->controller_duty_received);
548 init_completion(x: &priv->cooler_duty_set);
549 init_completion(x: &priv->controller_duty_set);
550
551 priv->hwmon_dev = hwmon_device_register_with_info(dev: &hdev->dev, name: "rog_ryujin",
552 drvdata: priv, info: &rog_ryujin_chip_info, NULL);
553 if (IS_ERR(ptr: priv->hwmon_dev)) {
554 ret = PTR_ERR(ptr: 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(dev: 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

source code of linux/drivers/hwmon/asus_rog_ryujin.c