[PATCH] hwmon: (nct6683) Restore manual fan control after resume
From: Tomasz Bojanowski
Date: Mon Nov 09 2026 - 11:43:49 EST
On MSI boards with NCT6687D-R, the BIOS restores automatic fan control
on resume from S3. The manual mode and duty cycle set through
pwmN_enable and pwmN are lost, and pwmN_enable reports 2.
Remember the channels switched to manual mode through sysfs and their
duty cycle, and restore them on resume if the EC switched them back to
automatic mode. Other channels are not touched.
Tested on an MSI MAG B850M MORTAR WIFI (MS-7E61): with pwm16_enable set
to 1 and pwm16 to 153, pwm16_enable reads 2 after resume from S3 without
this patch. With this patch it stays 1 and the fan returns to the manual
duty cycle.
Fixes: 5410a1257e26 ("hwmon: (nct6683) Add fan control support for MSI boards with NCT6687D-R")
Signed-off-by: Tomasz Bojanowski <tomasz@xxxxxxxxxxxxx>
---
I used an AI assistant (Claude Code) during development. I reviewed all
of the changes, tested them on the hardware described above, and take
responsibility for them.
This addresses your comment on suspend/resume in [1]: on the MS-7E61,
the BIOS restores automatic fan control on resume from S3. The patch
does not depend on the fan curve patch; both apply in either order.
[1] https://lore.kernel.org/linux-hwmon/487b2f39-7e22-46db-9eaf-9b1dd9b31d31@xxxxxxxxxxxx/
drivers/hwmon/nct6683.c | 39 +++++++++++++++++++++++++++++++++++----
1 file changed, 35 insertions(+), 4 deletions(-)
diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c
index 65e374c64fb8..d88b981efb1f 100644
--- a/drivers/hwmon/nct6683.c
+++ b/drivers/hwmon/nct6683.c
@@ -365,6 +365,10 @@ struct nct6683_data {
/* Remember extra register values over suspend/resume */
u8 hwm_cfg;
#endif
+
+ /* Manual fan control set through sysfs, restored on resume */
+ u16 pwm_manual; /* channels set to manual mode */
+ u8 pwm_cmd[NCT6683_NUM_REG_FAN]; /* their duty cycle */
};
struct nct6683_sio_data {
@@ -1052,8 +1056,11 @@ static int nct6683_set_pwm(struct nct6683_data *data, int ch, u8 val)
err = nct6683_fan_ctrl_update(data, ch, true, val);
else
err = -EBUSY;
- if (!err)
+ if (!err) {
data->pwm[ch] = val;
+ data->pwm_manual |= BIT(ch);
+ data->pwm_cmd[ch] = val;
+ }
mutex_unlock(&data->update_lock);
return err;
@@ -1130,10 +1137,17 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr,
manual = val == 1;
mutex_lock(&data->update_lock);
- if (nct6683_pwm_manual(data, index) != manual) {
- /* Keep the current duty cycle when switching to manual mode */
- cmd = manual ? nct6683_read(data, nct6683_pwm_reg(index)) : -1;
+ /* Keep the current duty cycle when switching to manual mode */
+ cmd = manual ? nct6683_read(data, nct6683_pwm_reg(index)) : -1;
+ if (nct6683_pwm_manual(data, index) != manual)
err = nct6683_fan_ctrl_update(data, index, manual, cmd);
+ if (!err) {
+ if (manual) {
+ data->pwm_manual |= BIT(index);
+ data->pwm_cmd[index] = cmd;
+ } else {
+ data->pwm_manual &= ~BIT(index);
+ }
}
mutex_unlock(&data->update_lock);
@@ -1549,11 +1563,28 @@ static int nct6683_suspend(struct device *dev)
static int nct6683_resume(struct device *dev)
{
struct nct6683_data *data = dev_get_drvdata(dev);
+ unsigned long manual;
+ int ch, err;
mutex_lock(&data->update_lock);
nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg);
+ /* The BIOS may switch the fans back to automatic mode */
+ manual = data->pwm_manual;
+ for_each_set_bit(ch, &manual, NCT6683_NUM_REG_FAN) {
+ if (nct6683_pwm_manual(data, ch))
+ continue;
+ err = nct6683_fan_ctrl_update(data, ch, true, data->pwm_cmd[ch]);
+ if (err) {
+ dev_warn(dev, "Failed to restore manual mode of pwm%d: %d\n",
+ ch + 1, err);
+ /* The EC does not respond, skip the remaining channels */
+ if (err == -ETIMEDOUT)
+ break;
+ }
+ }
+
/* Force re-reading all values */
data->valid = false;
mutex_unlock(&data->update_lock);
base-commit: ec921ef8d81acf0cc41b3f3b6db54698e9a2917f
--
2.56.0