All of lore.kernel.org
 help / color / mirror / Atom feed
From: Zac Lanzon <lanzonza@gmail.com>
To: linux-hwmon@vger.kernel.org
Cc: Guenter Roeck <linux@roeck-us.net>,
	Aleksa Savic <savicaleksa83@gmail.com>,
	Edward Blair <edward.blair@gmail.com>,
	Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	Zac Lanzon <lanzonza@gmail.com>,
	stable@vger.kernel.org
Subject: [PATCH 1/2] hwmon: (asus_rog_ryujin) Fix pump control source on the Ryujin III
Date: Sun, 27 Sep 2026 08:09:26 -0400	[thread overview]
Message-ID: <20260927120927.72810-2-lanzonza@gmail.com> (raw)
In-Reply-To: <20260927120927.72810-1-lanzonza@gmail.com>

The RYUJIN III pump has a 4-pin cable for a motherboard fan header in
addition to USB. Byte 2 of the set cooler duty command selects the pump
control source: 0 hands the pump to its internal controller, which
follows the PWM of the header its 4-pin cable is plugged into, and 1
makes it follow the pump duty in byte 3. Byte 3 of the cooler duty
report returns the current source.

The driver fills byte 2 with duty_channel, which is 1 for the RYUJIN
III, and a pwm2 write re-sends the reported pump duty with the new fan
duty. So writing only the internal fan duty takes the pump away from
its internal controller.

Measured on a RYUJIN III 360 (firmware AURJ2-S750-0108) with the pump
cable on AIO_PUMP and the header PWM set through nct6775, after loading
the driver with the pump on its internal controller (pump rpm):

                                   header 50%   header 80%   source
  no write yet                        2160         3090         0
  pwm2 = 40%, before this patch       1740         1740         1
  pwm2 = 40%, with this patch         2130         3090         0

Send the reported source with pwm2 writes. The fan duty applies with
either source (40%: 1545 to 1590 rpm, 30%: 870 rpm). A pwm2 write sent
right after a pwm1 write that took the pump over, before the report
could show the change, left the pump on USB and applied its fan duty
(2 of 2 trials, and 7 of 7 with raw commands up to 0.9 s after).

pwm1 writes keep selecting source 1. The cooler reports the pump duty of
the last write, whichever source it selected, and 40% after power-on.
Byte 9 of the status report follows the duty the pump runs at. Measured
from power-on (PSU switched off) once per driver, with the header at
100%:

                                  before this patch   with this patch
                                  source duty  rpm    source duty  rpm
  no write yet                       0    100  3570      0    100  3570
  pwm2 = 40%                         1    100  3570      0    100  3570
  pwm1 = 40%, the reported duty      1    100  3570      1     40  1710
  pwm1 = 60%                         1     60  2430
  pwm1 = 40%                         1     40  1725

So a pwm1 write of the reported duty did not reach the pump, while
other duties applied in one write, also when taking the pump from its
internal controller (60%: 2460 rpm). When a pwm1 write requests the
reported duty, write a duty 1% away first (below at 100%), then the
requested one. Sent back to back, the two writes left the pump at the
requested duty in all 19 trials, 9 with raw commands and 10 through
this driver.

Tested on the RYUJIN III 360 only. The EXTREME, EVA and WHITE EDITION
use the same rog_ryujin_iii_info and send the same command, and Edward
Blair's analysis of the WHITE EDITION firmware shows the same pump
control source. The RYUJIN II, whose duty_channel is 0, is unchanged.

Fixes: cd258aca0dc4 ("hwmon: (asus_rog_ryujin) Add ROG Ryujin III support")
Closes: https://github.com/aleksamagicka/asus_rog_ryujin-hwmon/issues/12
Suggested-by: Edward Blair <edward.blair@gmail.com>
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Zac Lanzon <lanzonza@gmail.com>
---
 Documentation/hwmon/asus_rog_ryujin.rst |  6 +++
 drivers/hwmon/asus_rog_ryujin.c         | 54 ++++++++++++++++++++++++-
 2 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/Documentation/hwmon/asus_rog_ryujin.rst b/Documentation/hwmon/asus_rog_ryujin.rst
index 3ebab230e8c..fe072e94d87 100644
--- a/Documentation/hwmon/asus_rog_ryujin.rst
+++ b/Documentation/hwmon/asus_rog_ryujin.rst
@@ -34,6 +34,12 @@ Usage notes
 As these are USB HIDs, the driver can be loaded automatically by the kernel and
 supports hot swapping.
 
+The pump of the RYUJIN III also has a 4-pin cable for a motherboard fan header.
+The cooler either hands the pump to its internal controller, which follows the
+PWM of the header its 4-pin cable is plugged into, or runs it at the pump duty
+set over USB. Writing pwm1 selects the duty set over USB. Writing pwm2 sets
+only the internal fan duty and leaves the pump control as it is.
+
 Sysfs entries
 -------------
 
diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c
index 5ced69e94ea..ba188e86793 100644
--- a/drivers/hwmon/asus_rog_ryujin.c
+++ b/drivers/hwmon/asus_rog_ryujin.c
@@ -50,6 +50,7 @@ static const struct rog_ryujin_device_info rog_ryujin_iii_info = {
 #define MAX_REPORT_LENGTH	65
 
 /* Cooler duty report offsets */
+#define RYUJIN_PUMP_SOURCE		3
 #define RYUJIN_PUMP_DUTY		4
 #define RYUJIN_INTERNAL_FAN_DUTY	5
 
@@ -70,10 +71,15 @@ 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_CHANNEL_OFFSET	2
 #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 };
 
+/* Pump control sources in the set cooler duty command and the cooler duty report */
+#define RYUJIN_SOURCE_HEADER	0
+#define RYUJIN_SOURCE_USB	1
+
 #define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET	4
 static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 };
 
@@ -117,6 +123,7 @@ struct rog_ryujin_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 pump_source;		/* Pump control source from the cooler duty report */
 
 	u8 *buffer;
 	unsigned long updated;	/* jiffies */
@@ -306,6 +313,44 @@ static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types ty
 	return 0;
 }
 
+/*
+ * On devices with a non-zero duty_channel (the RYUJIN III), the channel byte of
+ * the set cooler duty command selects the pump control source.
+ * RYUJIN_SOURCE_HEADER hands the pump to its internal controller, which follows
+ * the PWM of the header its 4-pin cable is plugged into. RYUJIN_SOURCE_USB
+ * makes the pump follow the pump duty in the command. The cooler duty report
+ * returns the current source.
+ *
+ * A fan duty write sends the reported source back, so that it leaves the pump
+ * control source as it is. A pump duty write selects USB. The cooler does not
+ * pass on a pump duty equal to the one it reports, and after power-on it
+ * reports 40% while a pump handed over at that duty runs at full speed. So
+ * when a pump duty write asks for the reported duty, write a duty 1% away
+ * first.
+ */
+static int rog_ryujin_select_pump_source(struct rog_ryujin_data *priv, u8 *set_cmd,
+					 int channel)
+{
+	u8 duty = set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET];
+	int ret;
+
+	if (channel == 1) {
+		set_cmd[RYUJIN_SET_COOLER_CHANNEL_OFFSET] = priv->pump_source;
+		return 0;
+	}
+
+	set_cmd[RYUJIN_SET_COOLER_CHANNEL_OFFSET] = RYUJIN_SOURCE_USB;
+
+	if (duty != rog_ryujin_pwm_to_percent(priv->duty_input[0]))
+		return 0;
+
+	set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = duty < 100 ? duty + 1 : duty - 1;
+	ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
+	set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = duty;
+
+	return ret;
+}
+
 static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val)
 {
 	u8 set_cmd[SET_CMD_LENGTH];
@@ -323,7 +368,7 @@ static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel
 			return ret;
 
 		memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);
-		set_cmd[2] = priv->info->duty_channel;
+		set_cmd[RYUJIN_SET_COOLER_CHANNEL_OFFSET] = priv->info->duty_channel;
 
 		/* Cooler duties are set as 0-100% */
 		val = rog_ryujin_pwm_to_percent(val);
@@ -340,6 +385,12 @@ static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel
 			set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;
 		}
 
+		if (priv->info->duty_channel) {
+			ret = rog_ryujin_select_pump_source(priv, set_cmd, channel);
+			if (ret < 0)
+				return ret;
+		}
+
 		return rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
 	} else {
 		/*
@@ -482,6 +533,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo
 			goto unlock;
 		}
 read_cooler_duty:
+		priv->pump_source = data[RYUJIN_PUMP_SOURCE];
 		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]);
 
-- 
2.53.0


  reply	other threads:[~2026-09-27 12:09 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-27 12:09 [PATCH 0/2] hwmon: (asus_rog_ryujin) Ryujin III pump control source Zac Lanzon
2026-09-27 12:09 ` Zac Lanzon [this message]
2026-09-27 12:16   ` [PATCH 1/2] hwmon: (asus_rog_ryujin) Fix pump control source on the Ryujin III sashiko-bot
2026-09-27 12:09 ` [PATCH 2/2] hwmon: (asus_rog_ryujin) Add pwm1_enable for the Ryujin III pump Zac Lanzon
2026-09-27 12:19   ` sashiko-bot
2026-09-27 12:47     ` Zac Lanzon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260927120927.72810-2-lanzonza@gmail.com \
    --to=lanzonza@gmail.com \
    --cc=corbet@lwn.net \
    --cc=edward.blair@gmail.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=rdunlap@infradead.org \
    --cc=savicaleksa83@gmail.com \
    --cc=skhan@linuxfoundation.org \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.