[Guide] How to make the Rock 5b fan work properly - Armbian & Joshua Riek's Ubuntu 24.04

Let’s be frank, the default fan curve in the rockchip kernel is abysmal.

To make the official Radxa fan spin in the right situations and not make much noise, do this:

  1. Go to a folder (Documents, let’s say), create a file called fanctrl-my.dts with this content:
/dts-v1/;
/plugin/;
/ {
        fragment@0 {
                target = <&fan0>;
                __overlay__ {
                        cooling-levels = <141 141 141 141 141 141 146 146 151>;
                };
        };
};
  1. save the file, open terminal in that directory and write dtc -O dtb -o fanctrl-my.dtbo fanctrl-my.dts
  2. sudo cp fanctrl-my.dtbo /lib/firmware/6.1.0-*-rockchip/device-tree/rockchip/overlay/
  3. edit the file /etc/default/u-boot as explained here: Noble/6.1 Kernel stability · Joshua-Riek/ubuntu-rockchip · Discussion #686 · GitHub
  4. validate that the overlay is detected
  5. reboot

The constant speed changing on the default curve is responsible for the terrible coil whine. This overlay keeps the CPU almost always below 65 °C during any torture tests (ambient ~25 °C) and makes little noise.

Update: there is a better way which actually survives your kernel updates. Instead of saving to the kernel overlays dir, use this for example:

dtc -O dtb -o /boot/overlay-user/rock-5b-fanctrl-my.dtbo ~/fan-control/fanctrl-my.dts

Then, edit your boot/armbianEnv.txt this way:

overlay_prefix=rock-5b rock-5 rockchip-rk3588 rk3588
overlays=
user_overlays=fanctrl-my

It should be persistent. If you’re still using ubuntu-rockchip, I am not sure if an analogous method can be used.

4 Likes

I’ve updated the overlay to deal with higher ambient temperatures. This can be thought of as a “Summertime” fan profile. Still quite silent.

/dts-v1/;
/plugin/;
/ {
        fragment@0 {
                target = <&fan0>;
                __overlay__ {
                        cooling-levels = <146 146 146 146 146 146 149 149 151>;
                };
        };
};
2 Likes

It seems something changed again and we are even more limited in the speeds that don’t cause coil whine.
Only 123, 158 and 230 and above are usable. Here is my current overlay:

/dts-v1/;
/plugin/;
/ {
        fragment@0 {
                target = <&fan0>;
                __overlay__ {
                        cooling-levels = <158 158 158 158 158 158 158 158 230>;
                };
        };
};

If you want to find the speeds that work for you, use these commands:

echo user_space | sudo tee /sys/class/thermal/thermal_zone0/policy #switch to manual mode
for ((i=255; i>=80; i--)); do echo $i | sudo tee /sys/devices/platform/pwm-fan/hwmon/hwmon?/pwm1; sleep 2; done #decrease the pwm value by 1 every 2 seconds to find values that don't cause hissing

I have created script + systemd for my Rock 5B+ with Radxa Heatsink 6240B, maybe someone finds it usefull

  1. Fan controler

sudo tee /usr/local/bin/rock5bplus-fan.sh >/dev/null <<‘EOF’
#!/usr/bin/env bash
# ROCK 5B+ fan control: 0 (off), 40 (quiet), 255 (full) with hysteresis.
# Because on this board with this fan 100…255 all sound like a hair dryer anyway.
set -euo pipefail

# Find the pwmfan hwmon

find_pwmfan() {
for d in /sys/class/hwmon/hwmon*; do
[[ -r “$d/name” ]] || continue
if [[ “$(cat “$d/name”)” == “pwmfan” ]]; then
echo “$d”
return 0
fi
done
echo “pwmfan hwmon not found” >&2
exit 1
}

HWMON="$(find_pwmfan)"
PWM="$HWMON/pwm1"
MODE="$HWMON/pwm1_enable"

# Manual mode (1)

echo 1 > “$MODE” || true

# Read temp (°C) from all useful sensors; return the max.

read_maxtemp() {
local max=-273 cur d n f
for d in /sys/class/hwmon/hwmon*; do
f="$d/name"
n="$(cat “$f” 2>/dev/null || true)"
case “$n” in
soc_thermal|bigcore0_thermal|bigcore1_thermal|littlecore_thermal|center_thermal|gpu_thermal|npu_thermal)
cur=$(awk ‘{printf “%.0f”, $1/1000}’ “$d/temp1_input” 2>/dev/null || echo 0)
(( cur > max )) && max="$cur"
;;
esac
done
echo “${max:-0}”
}

# Fan levels

OFF=0 # off
QUIET=40 # your board: “very quiet”
FULL=255 # sounds like max from 100…255, so skip the pretense

# Hysteresis thresholds (°C)
# Raise:   quiet→full at >= 70; off→quiet at >= 50
# Lower:   full→quiet at <= 64; quiet→off at <= 45

HI_FULL=70; LO_FULL=64
HI_QUIET=50; LO_QUIET=45

# Start conservative

current=$OFF
echo “$current” > “$PWM”

while true; do
t=$(read_maxtemp)

case $current in
$OFF)
if (( t >= HI_QUIET )); then current=$QUIET; fi
;;
$QUIET)
if (( t >= HI_FULL )); then current=$FULL
elif (( t <= LO_QUIET )); then current=$OFF
fi
;;
$FULL)
if (( t <= LO_FULL )); then current=$QUIET; fi
;;
esac

echo “$current” > “$PWM” 2>/dev/null || true

printf “T=%s°C pwm=%s mode=%s\n” “$t” “$(cat “$PWM”)” “$(cat “$MODE”)” 1>&2
sleep 2
done
EOF

sudo chmod +x /usr/local/bin/rock5bplus-fan.sh

  1. This is systemd entry
    sudo tee /etc/systemd/system/rock5bplus-fan.service >/dev/null <<'EOF' [Unit] Description=ROCK 5B+ fan curve (pwm-fan) After=multi-user.target StartLimitIntervalSec=0
    [Service] Type=simple ExecStart=/usr/local/bin/rock5bplus-fan.sh Restart=always RestartSec=2
    [Install] WantedBy=multi-user.target EOF
    sudo systemctl daemon-reload
    sudo systemctl enable --now rock5bplus-fan.service
    systemctl status rock5bplus-fan.service --no-pager

  2. One-liner watcher to monitor temps and PWM in real time

watch -n1 ' for d in /sys/class/hwmon/hwmon*; do n=$(cat "$d/name" 2>/dev/null || true) case "$n" in soc_thermal|bigcore0_thermal|bigcore1_thermal|littlecore_thermal|center_thermal|gpu_thermal|npu_thermal) t=$(awk "{printf \"%.0f\", \$1/1000}" "$d/temp1_input" 2>/dev/null); printf "%-18s %3s°C\n" "$n" "$t" ;; pwmfan) printf "%-18s pwm=%s mode=%s\n" "pwmfan" "$(cat "$d/pwm1")" "$(cat "$d/pwm1_enable")" ;; esac done'

The Radxa 6240B heatsink fan header is two-wire (power + ground, with PWM modulation) . There’s no tachometer line (the third yellow wire you’d normally see on a PC fan), so the driver cannot measure actual RPM so watcher script line will always return rpm=n/a.

So we are reverting from a kernel driver to users pace scripts :joy: kinda ironic, but makes sense, as the kernel driver is a bit shite. But I think it should be possible to do this more easily.

By the way, do you also experience the coil whine? For me, the coil whine is worse, and the fan itself is silent up to about 180 maybe.

Which model of the fan do You use?
Mine is 6240B and it whines on anything above 50.
Im just looking for some fans which I could use to replace 6240 but keep heatsink…
Since there is no tachometer line header on the board it’s pointless to use any of those and I don’t want to connect fan to GPIO since Im trying to keep it simple as much as possible.

Not sure, it looks like the one in the picture here:

Please try to use the script above to find out if there are speeds without the whine. I am curious if it’s just my bad luck or a general thing (I think it’s general because the rock 5a fan also produces coil whine). It’s probably connected to the 2-pin pwm regulation.

You have different type of fan, mine is with the heatsink and it seems that my fan is little bit smaller.
This is 6240B which Im using.
Mine is silent until 50, after that it starts being very loud.

OK, but is the noise it makes genuine fan sound or the PWM coil whine? Coil whine is high pitched and screechy, fan sound is… You know what.

It seems like the fan “curve” is getting more and more broken (spinning up and down all the time despite hysteresis programmed in). Recently I resorted to using this type of curve:

/dts-v1/;
/plugin/;
/ {
        fragment@0 {
                target = <&fan0>;
                __overlay__ {
                        cooling-levels = <223 223 223 223 223 223 223 223 223>;
                };
        };
};

Hope this helps someone. The driver seems to be broken even after a few years of development.