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 | |
46 | static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 }; |
47 | static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A }; |
48 | static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 }; |
49 | static 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 |
53 | static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 }; |
54 | |
55 | #define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4 |
56 | static 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 | |
68 | static const char *const rog_ryujin_temp_label[] = { |
69 | "Coolant temp" |
70 | }; |
71 | |
72 | static 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 | |
81 | struct 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 | |
106 | static int rog_ryujin_percent_to_pwm(u16 val) |
107 | { |
108 | return DIV_ROUND_CLOSEST(val * 255, 100); |
109 | } |
110 | |
111 | static int rog_ryujin_pwm_to_percent(long val) |
112 | { |
113 | return DIV_ROUND_CLOSEST(val * 100, 255); |
114 | } |
115 | |
116 | static 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 */ |
154 | static 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 */ |
168 | static 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 | |
197 | static 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 | |
239 | unlock_and_return: |
240 | mutex_unlock(lock: &priv->status_report_request_mutex); |
241 | if (ret < 0) |
242 | return ret; |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static 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 | |
279 | static 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 | |
296 | static 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); |
333 | unlock_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 | |
358 | static 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 | |
386 | static 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 | |
393 | static 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 | |
410 | static const struct hwmon_chip_info rog_ryujin_chip_info = { |
411 | .ops = &rog_ryujin_hwmon_ops, |
412 | .info = rog_ryujin_info, |
413 | }; |
414 | |
415 | static 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 | } |
461 | read_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 | } |
487 | read_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 | |
497 | static 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 | |
561 | fail_and_close: |
562 | hid_hw_close(hdev); |
563 | fail_and_stop: |
564 | hid_hw_stop(hdev); |
565 | return ret; |
566 | } |
567 | |
568 | static 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 | |
578 | static 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 | |
583 | MODULE_DEVICE_TABLE(hid, rog_ryujin_table); |
584 | |
585 | static 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 | |
593 | static int __init rog_ryujin_init(void) |
594 | { |
595 | return hid_register_driver(&rog_ryujin_driver); |
596 | } |
597 | |
598 | static 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 */ |
604 | late_initcall(rog_ryujin_init); |
605 | module_exit(rog_ryujin_exit); |
606 | |
607 | MODULE_LICENSE("GPL" ); |
608 | MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>" ); |
609 | MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler" ); |
610 | |