| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- // 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 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)
- {
- memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
- return hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
- }
- 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;
- if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
- /* Data is up to date */
- return 0;
- }
- /* Retrieve cooler status */
- ret =
- rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH,
- &priv->cooler_status_received);
- if (ret < 0)
- return ret;
- /* Retrieve controller status (speeds) */
- ret =
- rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,
- &priv->controller_status_received);
- if (ret < 0)
- return ret;
- /* Retrieve cooler duty */
- ret =
- rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
- &priv->cooler_duty_received);
- if (ret < 0)
- return ret;
- /* Retrieve controller duty */
- ret =
- rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,
- &priv->controller_duty_received);
- if (ret < 0)
- return ret;
- priv->updated = jiffies;
- 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 =
- rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
- &priv->cooler_duty_received);
- if (ret < 0)
- return ret;
- 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;
- }
- return rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
- } 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;
- }
- 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");
|