aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbdelkader Boudih <oss@seuros.com>2026-01-05 18:03:23 +0000
committerMark Johnston <markj@FreeBSD.org>2026-01-05 18:25:47 +0000
commit1ecac45cfc5f0886907c34ec0da2c1b32618665d (patch)
tree44f552a63bdc2de7e454e0ee12ebb32ded62636f
parent161db3a910a41dfae0eea93f49b08eb7e6253617 (diff)
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
-rw-r--r--sys/dev/asmc/asmc.c55
1 files changed, 55 insertions, 0 deletions
diff --git a/sys/dev/asmc/asmc.c b/sys/dev/asmc/asmc.c
index bdb8ca7831c4..0572c8e663a7 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)");
}
/*
@@ -1347,6 +1355,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.
*/