git: a11b4f757e97 - stable/15 - asmc: add per-fan manual mode control via sysctl

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 26 Jan 2026 00:52:30 UTC
The branch stable/15 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=a11b4f757e97397e9d61c7236dd6dd5b6e4be8a7

commit a11b4f757e97397e9d61c7236dd6dd5b6e4be8a7
Author:     Abdelkader Boudih <oss@seuros.com>
AuthorDate: 2026-01-05 18:03:23 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-01-26 00:51:44 +0000

    asmc: add per-fan manual mode control via sysctl
    
    Add per-fan manual mode control via dev.asmc.0.fan.N.manual sysctl.
    
    Apple SMCs support manual fan control via the FS! SMC key,
    a 16-bit bitmask where each bit controls one fan (0=auto, 1=manual).
    
    This change adds a new sysctl per fan:
        dev.asmc.0.fan.N.manual (0=auto, 1=manual)
    
    When set to manual mode (1), the fan runs at the speed set via
    dev.asmc.0.fan.N.targetspeed instead of automatic thermal control.  When
    set to auto mode (0), the SMC controls fan speed automatically.
    
    The FS! key was already defined in asmcvar.h but not accessible.
    This exposes it for debugging, testing, and advanced fan control.
    
    Implementation uses read-modify-write to allow independent control of
    each fan without affecting others.
    
    Reviewed by:    adrian, markj
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D54437
    
    (cherry picked from commit 1ecac45cfc5f0886907c34ec0da2c1b32618665d)
---
 sys/dev/asmc/asmc.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/sys/dev/asmc/asmc.c b/sys/dev/asmc/asmc.c
index d99c1d56e67c..5a1074abde83 100644
--- a/sys/dev/asmc/asmc.c
+++ b/sys/dev/asmc/asmc.c
@@ -97,6 +97,7 @@ static int 	asmc_mb_sysctl_fansafespeed(SYSCTL_HANDLER_ARGS);
 static int 	asmc_mb_sysctl_fanminspeed(SYSCTL_HANDLER_ARGS);
 static int 	asmc_mb_sysctl_fanmaxspeed(SYSCTL_HANDLER_ARGS);
 static int 	asmc_mb_sysctl_fantargetspeed(SYSCTL_HANDLER_ARGS);
+static int 	asmc_mb_sysctl_fanmanual(SYSCTL_HANDLER_ARGS);
 static int 	asmc_temp_sysctl(SYSCTL_HANDLER_ARGS);
 static int 	asmc_mb_sysctl_sms_x(SYSCTL_HANDLER_ARGS);
 static int 	asmc_mb_sysctl_sms_y(SYSCTL_HANDLER_ARGS);
@@ -656,6 +657,13 @@ asmc_attach(device_t dev)
 		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
 		    dev, j, model->smc_fan_targetspeed, "I",
 		    "Fan target speed in RPM");
+
+		SYSCTL_ADD_PROC(sysctlctx,
+		    SYSCTL_CHILDREN(sc->sc_fan_tree[i]),
+		    OID_AUTO, "manual",
+		    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
+		    dev, j, asmc_mb_sysctl_fanmanual, "I",
+		    "Fan manual mode (0=auto, 1=manual)");
 	}
 
 	/*
@@ -1338,6 +1346,53 @@ asmc_mb_sysctl_fantargetspeed(SYSCTL_HANDLER_ARGS)
 	return (error);
 }
 
+static int
+asmc_mb_sysctl_fanmanual(SYSCTL_HANDLER_ARGS)
+{
+	device_t dev = (device_t) arg1;
+	int fan = arg2;
+	int error;
+	int32_t v;
+	uint8_t buf[2];
+	uint16_t val;
+
+	/* Read current FS! bitmask (asmc_key_read locks internally) */
+	error = asmc_key_read(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf));
+	if (error != 0)
+		return (error);
+
+	/* Extract manual bit for this fan (big-endian) */
+	val = (buf[0] << 8) | buf[1];
+	v = (val >> fan) & 0x01;
+
+	/* Let sysctl handle the value */
+	error = sysctl_handle_int(oidp, &v, 0, req);
+
+	if (error == 0 && req->newptr != NULL) {
+		/* Validate input (0 = auto, 1 = manual) */
+		if (v != 0 && v != 1)
+			return (EINVAL);
+		/* Read-modify-write of FS! bitmask */
+		error = asmc_key_read(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf));
+		if (error == 0) {
+			val = (buf[0] << 8) | buf[1];
+
+			/* Modify single bit */
+			if (v)
+				val |= (1 << fan);   /* Set to manual */
+			else
+				val &= ~(1 << fan);  /* Set to auto */
+
+			/* Write back */
+			buf[0] = val >> 8;
+			buf[1] = val & 0xff;
+			error = asmc_key_write(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf));
+		}
+	}
+
+	return (error);
+}
+
 /*
  * Temperature functions.
  */