Userspace fan control for ThinkPads whose embedded controller (EC) exposes the
fan through non-standard registers (the kernel's thinkpad_acpi
TPACPI_FAN_NS quirk). On these machines the mainline driver can only read
the fan speed, not control it — so the fan often runs constantly at idle with
no way to slow it down.
tpnsfand talks to the EC registers directly: it keeps the fan quiet (or off)
while the machine is cool and hands control back to the EC once things get warm.
Version: 0.2 · License: GPL-2.0-only · Changelog
This tool writes directly to Embedded Controller registers. Misuse can let your machine overheat.
- While
tpnsfandholds host control, the EC does not regulate the fan — the only remaining hardware safety net is the CPU's own thermal throttle (~100 °C).tpnsfandmitigates this with a 0.5-second watchdog loop and by handing control back to the EC above a temperature threshold, but there is no independent failsafe. - The register layout, speed range, and EC floor may differ between models. Values validated on one machine are not guaranteed correct on another.
- Disabling Secure Boot (a prerequisite, see below) reduces your system's security posture.
This program is distributed without any warranty (see GPL-2.0-only). Use at your own risk.
The affected ThinkPads use an EC firmware that places the fan status/control and
tacho bytes at non-standard addresses. The kernel recognizes these models and
reports the fan speed correctly, but provides no write path — the standard
fan_control=1 / /proc/acpi/ibm/fan interface does nothing, pwm1_enable
returns EPERM, and the fan keeps spinning at idle (often ~3800–4200 RPM)
regardless of how cool the machine is.
tpnsfand fills that gap from userspace.
Models below are the ones the kernel flags as non-standard (TPACPI_FAN_NS).
dmidecode -s bios-version prints the full BIOS version string (e.g.
R1FET64W (1.38)); its leading three characters are the LNV3 code (here
R1F), which is what the kernel matches your machine against. Models that share
an LNV3 code run the same EC firmware, so they share fan behaviour.
| Model | Type(s) | LNV3 | Support |
|---|---|---|---|
| ThinkPad L13 Gen 2 | 20VH, 20VJ | R1F | ✅ |
| ThinkPad L13 Yoga Gen 2 | 20VK, 20VL | R1F | ✅ |
| ThinkPad S2 Gen 6 | 20VM | R1F | ❔ |
| ThinkPad S2 Yoga Gen 6 | 20VN | R1F | ❔ |
| ThinkPad L13 Gen 1 | 20R3, 20R4 | R15 | ❔ |
| ThinkPad L13 Yoga Gen 1 | 20R5, 20R6 | R15 | ❔ |
| ThinkPad S2 Gen 5 | 20R7 | R15 | ❔ |
| ThinkPad S2 Yoga Gen 5 | 20R8 | R15 | ❔ |
| ThinkPad X13 Yoga Gen 2 | 20W8, 20W9 | N39 | ❔ |
| ThinkPad X13 Yoga Gen 1 | 20SX, 20SY | N2U | ✅ |
| ThinkPad L390 | 20NR, 20NS | R10 | ❔ |
| ThinkPad L390 Yoga | 20NT, 20NU | R10 | ❔ |
| ThinkPad S2 Gen 4 | 20NV | R10 | ❔ |
| ThinkPad S2 Yoga Gen 4 | 20NW | R10 | ❔ |
| ThinkPad L380 | 20M5, 20M6 | R0R | ❔ |
| ThinkPad L380 Yoga | 20M7, 20M8 | R0R | ❔ |
| ThinkPad S2 Gen 3 | 20L1 | R0R | ❔ |
| ThinkPad S2 Yoga Gen 3 | 20L2 | R0R | ❔ |
| ThinkPad 11e Gen 5 GL | 20LR, 20LQ | R0T | ❔ |
| ThinkPad Yoga 11e Gen 5 GL | 20LN, 20LM | R0T | ❔ |
| ThinkPad 11e Gen 5 GL-R | 20LR, 20LQ | R1D | ❔ |
| ThinkPad Yoga 11e Gen 5 GL-R | 20LN, 20LM | R1D | ❔ |
| ThinkPad 11e Gen 5 KL-Y | 20LR, 20LQ | R0V | ❔ |
| ThinkPad Yoga 11e Gen 5 KL-Y | 20LN, 20LM | R0V | ❔ |
Legend:
- ✅ verified — fan control tested and working.
- ❔ no report yet — suspected compatible (in the kernel NS list), but no community report submitted.
- ❌ incompatible — confirmed not working.
Your model shows ❔? Please contribute a report.
It uses three EC bytes, writing two of them:
| Register | Role |
|---|---|
0x93 |
control byte — 0x04 = EC controls, 0x14 = host (us) controls |
0x94 |
fan speed command (inverse scale, roughly RPM ≈ 491520 / value) |
0x95 |
tacho, read-only (RPM ≈ 491520 / value, saturates near 1927 RPM) |
Who currently owns the fan is visible without root via the kernel driver:
cat /proc/acpi/ibm/fan reports level: auto while the EC controls the fan
and level: unknown while tpnsfand does (the driver does not recognize the
host-control value). A speed: of 1927 is the tacho's saturation floor and
means the fan is not running.
The daemon regulates on max(CPU package, NVMe) temperature and runs a small
state machine with hysteresis:
- OFF — below
TEMP_FAN_OFF: host control, fan commanded off. - RAMP — between the thresholds: host control, fan speed ramped linearly
from
RPM_LOW(atTEMP_FAN_ON) up toRPM_HIGH(atTEMP_CTRL_EC). - EC — at/above
TEMP_CTRL_EC: control handed back to the EC, which runs its own (safe) auto curve. Control is taken back belowTEMP_CTRL_HOST.
The control byte is checked every loop and re-asserted only when it changed under us (self-heals across suspend/resume), and on exit the daemon returns control to the EC.
Detailed register semantics, measured access costs, and EC quirks are collected in EC.md.
- A ThinkPad with the non-standard EC fan layout (see Compatibility above).
ec_syswith write support enabled: kernel command lineec_sys.write_support=1.ec_sysmay need loading (sudo modprobe ec_sys) if it is not built in.- Secure Boot disabled. Secure Boot triggers kernel lockdown (integrity),
which blocks EC writes even with
write_support=1. coretemp(CPU package temperature) and, optionally,nvmehwmon sensors.
Enabling ec_sys.write_support=1 is distro-specific. Add it to your kernel
command line via your bootloader's mechanism (e.g. /etc/default/grub +
update-grub, or /etc/kernel/cmdline + the appropriate regeneration step) and
reboot. Verify:
cat /sys/module/ec_sys/parameters/write_support # expect Ygit clone https://github.com/h4fnp/tpnsfand
cd tpnsfand
# make sure the prerequisites above are met first
sudo make installmake install copies the daemon to /usr/local/sbin, installs the service,
enables it, and starts it immediately if write_support is already active
(otherwise it is enabled for the next boot). Verify:
journalctl -u tpnsfand -fAll values are read from environment variables with built-in defaults. Override
them by uncommenting the matching Environment= lines in the service unit
(/etc/systemd/system/tpnsfand.service), then:
sudo systemctl daemon-reload
sudo systemctl restart tpnsfand| Variable | Default | Meaning |
|---|---|---|
TPNSFAND_TEMP_FAN_ON |
65 | °C at/above which the fan turns on |
TPNSFAND_TEMP_FAN_OFF |
55 | °C below which the fan turns off |
TPNSFAND_TEMP_CTRL_EC |
75 | °C at/above which control is handed to the EC |
TPNSFAND_TEMP_CTRL_HOST |
70 | °C below which control is taken back from the EC |
TPNSFAND_RPM_LOW |
2000 | target RPM at TEMP_FAN_ON (ramp start) |
TPNSFAND_RPM_HIGH |
4165 | target RPM at TEMP_CTRL_EC (ramp end) |
TPNSFAND_INTERVAL |
0.5 | poll interval in seconds (also the watchdog) |
Hardware-level values are also overridable (TPNSFAND_EC_IO, TPNSFAND_REG_*,
TPNSFAND_CTRL_*, TPNSFAND_SPEED_*) for adapting to a different model, as are
the sysfs lookup paths (TPNSFAND_HWMON_PATH, TPNSFAND_WS_PATH — mainly used
by the test suite). Change these only if you know your EC layout differs. No
validation is performed on any value.
- Below
TEMP_FAN_ONthe fan is off; between the thresholds it ramps; aboveTEMP_CTRL_ECthe EC takes over and runs its own curve. Under sustained heavy load the fan will spin — no setting keeps a hot CPU silent. - On start the daemon assumes the EC owns the fan (it does) and only takes
over once the temperature crosses a threshold. Starting between
TEMP_FAN_OFFandTEMP_FAN_ONtherefore runs the fan at the ramp floor until the machine cools belowTEMP_FAN_OFF; starting betweenTEMP_CTRL_HOSTandTEMP_CTRL_ECleaves the EC in control until the temperature drops belowTEMP_CTRL_HOST. - At high ambient temperatures the fanless idle plateau can rise above
TEMP_FAN_ON, producing a periodic on/off cycle over several minutes. This is physics, not a bug; it is still far quieter than the EC's constant idle fan. RaisingTEMP_FAN_ON(if your machine stays cool enough) or reducing heat (e.g. capping turbo) mitigates it. - Stopping the service or uninstalling returns fan control to the EC.
-
ec_sys.write_supportnotY— the kernel command line did not take effect, or Secure Boot/lockdown is active. Recheck both and reboot. -
Service crash-loops — almost always the above: the daemon exits when it cannot write the EC. Fix the prerequisites first.
-
Fan stuck off/on after a hard kill —
tpnsfandreturns control to the EC on normal stop, but not if it isSIGKILLed. Restart the service, or set the control byte back to EC mode manually:printf '\x04' | sudo dd of=/sys/kernel/debug/ec/ec0/io bs=1 seek=147 count=1 conv=notrunc status=none
sudo make uninstallThis stops the service (which returns control to the EC) and removes the files.
Help confirm your model. The report tool is read-only (it never writes EC registers); it records identifying info plus how your EC drives the fan through a short idle / load / idle cycle.
The test only yields useful data with the CPU unthrottled, so set the
performance platform profile first — the tool aborts on any other profile
(skip if your machine has no platform_profile):
echo performance | sudo tee /sys/firmware/acpi/platform_profileThen:
git clone https://github.com/h4fnp/tpnsfand
cd tpnsfand
sudo modprobe ec_sys # if not already loaded
sudo systemctl stop tpnsfand # only if installed
sudo ./tools/tpnsfand-report # requires stress-ngThen submit the generated file one of two ways:
- open a pull request adding it under
reports/, or - post it in the compatibility reports discussion.
The daemon script is sourceable, so its functions are testable in isolation. The test suite runs unprivileged against fixtures (a fake EC file, a fake hwmon tree, a mocked control loop) and never touches real hardware:
make testPlease run it before submitting changes to src/tpnsfand.
The current approach requires ec_sys.write_support=1 and Secure Boot
disabled, because kernel lockdown blocks EC writes through the ec_sys
debugfs interface. Two longer-term goals aim to remove that constraint so the
machine can run with Secure Boot enabled again:
- Upstream
thinkpad_acpiwrite support. The driver already reads the non-standard fan registers; adding a write/control path upstream would let the in-tree (signed) driver drive the fan through a standardpwminterface — noec_sys, no lockdown bypass, Secure Boot compatible. - A bundled DKMS module (interim). Ship a small out-of-tree kernel module
(built and installed via DKMS, signed with your own MOK key) that performs the
EC writes in kernel space and exposes a control interface for
tpnsfand. This keeps Secure Boot enabled without waiting for upstream, at the cost of a one-time MOK enrollment.
The problem in the wild (user reports of the constant/uncontrollable fan):
- Lenovo Forums — L13 Yoga gen 2 cannot control fan and sensors bad reading (Ubuntu): https://forums.lenovo.com/t5/Ubuntu/L13-Yoga-gen-2-cannot-control-fan-and-sensors-bad-reading/m-p/5251278
- iFixit — Why is the fan running constantly? (L13 Yoga Gen 2): https://www.ifixit.com/Answers/View/895532
- Lenovo Forums — ThinkPad L13 Yoga Gen 2 loud fan and heat: https://forums.lenovo.com/t5/ThinkPad-L-R-and-SL-series-Laptops/ThinkPad-L13-Yoga-Gen-2-loud-fan-and-heat/m-p/5069508
- Arch Linux BBS — Thinkpad Yoga and fan control (related EC class): https://bbs.archlinux.org/viewtopic.php?id=218564
- A L13 Gen 2 user reporting thinkfan has no effect (blog comments): https://blog.monosoul.dev/2021/10/17/how-to-control-thinkpad-p14s-fan-speed-in-linux/
senior-sigan/lenovo-yoga-fan-control— describes the same odd EC behavior on an older Yoga: https://github.com/senior-sigan/lenovo-yoga-fan-control
Upstream / kernel (the authoritative source for the NS layout):
- LKML —
thinkpad_acpifix for incorrect fan reporting on non-standard EC firmware (Dec 2023): https://lkml.org/lkml/2023/12/6/947 - LKML — adding more ThinkPads with non-standard register addresses (Feb 2024): https://lkml.org/lkml/2024/2/29/2
- The
ibm-acpi-develmailing list, where the NS read support was developed.
GPL-2.0-only. See LICENSE. Distributed without warranty.