realtek: use autonomous LM63 fan control on DGS-1210-28P/MP

Previously, fan control on D-Link DGS-1210-28P/MP switches was handled
by a user-space script (`fan_ctrl.sh`) triggered via cron every 5
minutes due to missing kernel sysfs writable attributes for PWM
frequency and hysteresis.

The `fan_ctrl.sh` contained a string-truncation comparison logic bug
(temp1_input was truncated while PSU_THRESH was not) that kept the fan
locked at a static 156 PWM (61%) rate regardless of temperature.

Also, since hwmon0 (cpu_thermal) appeared, it changed the order of hwmon
devices and broke fan_ctrl.sh PWM control, keeping it always stopped.

With commit 89ef8aaf3e ("kernel: backport lm63 enhancements"),
`pwm1_freq` and `pwm1_auto_point_temp_hyst` are now writable, enabling
full autonomous LUT hardware fan management on the LM63 chip.

Transition to autonomous hardware fan control:
- Configure LM63 Look-up Table (LUT) via `/etc/init.d/hwmon_fancontrol`
  using the OEM register settings:
  * PWM Frequency: ~7826 Hz (PFR divisor 23)
  * Point 1: > 0°C -> PWM 28/46, 155/255 (~60.8% duty cycle)
  * Point 2: > 51°C -> PWM 46/46, 255/255 (100% duty cycle)
  * Points 3-8: > 127°C -> PWM 255/255 (chip defaults for unused points)
  * Hysteresis: 3°C (3000 mC)
- Add `04_dlinkfan_migration` migration script to purge `fan_ctrl.sh` from
  crontab during sysupgrade.

The LM63 PWM resolution depends on the PFR divisor setting (2 * PFR).
With PFR set to 23 (matching OEM ~7826 Hz), full scale is 46 (0x2e). The
sysfs interface maps 0-255 to 0-46, meaning the OEM defaults 28 (0x1c)
and 46 (0x2e) translate to sysfs values 155 (~60.8% duty cycle) and 255
(100% duty cycle). Those values match the old fan_ctrl.sh temperature range
and duty cycle.

Fixes: ab3f92e86481 ("realtek: add fan controller support to D-Link DGS-1210-28MP")
Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/24869
Signed-off-by: Jonas Jelonek <jonas@jonasjelonek.de>
This commit is contained in:
Luiz Angelo Daros de Luca authored and Jonas Jelonek committed 2026-09-09 12:11:22 +02:00
1 parent 7a1684be88
commit d3290efad9
4 files changed
+68 -39

No files matched your search

@@ -35,6 +35,41 @@ plasmacloud_common() {
echo 2 > "$path_to_hwmon/pwm2_enable"
}
dlink_dgs_1210() {
local path_to_hwmon="$(find -L /sys/bus/i2c/devices/4-004c -type d -maxdepth 2 -mindepth 2 -name 'hwmon*' || true)"
if [ -z "$path_to_hwmon" ]; then
logger -t hwmon_fancontrol "LM63 hwmon interface not found on i2c 4-004c (kmod lm63 loaded?)"
return 1
fi
# Set to manual mode temporarily to allow LUT and frequency setup
echo 1 > "$path_to_hwmon/pwm1_enable"
# Configure PWM frequency (~7826 Hz / PFR divisor 23) matching OEM defaults
echo 7826 > "$path_to_hwmon/pwm1_freq"
# Point 1: If temp > 0 C, set fan speed to PWM 155/255 (OEM default 0x1c)
echo 0 > "$path_to_hwmon/pwm1_auto_point1_temp"
echo $((0x1c * 255 / 46)) > "$path_to_hwmon/pwm1_auto_point1_pwm"
# Point 2: If temp > 51 C, set fan speed to PWM 255/255 (OEM default 0x2e)
echo 51000 > "$path_to_hwmon/pwm1_auto_point2_temp"
echo $((0x2e * 255 / 46)) > "$path_to_hwmon/pwm1_auto_point2_pwm"
# Point 3-8: Set the chip default values for unused points as reboot does not clean them
for p in 3 4 5 6 7 8; do
echo 127000 > "$path_to_hwmon/pwm1_auto_point${p}_temp"
echo 255 > "$path_to_hwmon/pwm1_auto_point${p}_pwm"
done
# Hysteresis: Set 3 C (3000 mC) hysteresis for LUT (OEM default 0x03)
echo 3000 > "$path_to_hwmon/pwm1_auto_point_temp_hyst"
# Switch to hardware auto mode
echo 2 > "$path_to_hwmon/pwm1_enable"
}
dlink_dgs_1250() {
local path_temp_hwmon="$(find /sys/devices/platform/i2c-gpio-4/i2c-4/4-0048/hwmon/ -type d -maxdepth 1 -mindepth 1 -name 'hwmon*')"
local path_fan_hwmon="$(find /sys/devices/platform/gpio-fan/hwmon/ -type d -maxdepth 1 -mindepth 1 -name 'hwmon*')"
@@ -92,6 +127,10 @@ boot() {
plasmacloud,psx28)
plasmacloud_common
;;
d-link,dgs-1210-28p-f|\
d-link,dgs-1210-28mp-f)
dlink_dgs_1210
;;
d-link,dgs-1250-28x)
dlink_dgs_1250
;;
@@ -1,26 +0,0 @@
#
# Copyright (C) 2024 openwrt.org
#
. /lib/functions.sh
board=$(board_name)
case "$board" in
d-link,dgs-1210-28p-f|d-link,dgs-1210-28mp-f)
# Enable fan control
FAN_CTRL='/sys/class/hwmon/hwmon0'
echo 1 > "$FAN_CTRL/pwm1_enable"
# Set fan script execution in crontab
grep -s -q fan_ctrl.sh /etc/crontabs/root && exit 0
echo "# dlink fan script runs every 5 minutes" >> /etc/crontabs/root
echo "*/5 * * * * /sbin/fan_ctrl.sh" >> /etc/crontabs/root
# Execute one time after initial flash (instead of waiting 5 min for cron)
/sbin/fan_ctrl.sh
;;
esac
exit 0
@@ -0,0 +1,29 @@
#!/bin/sh
. /lib/functions.sh
CRON_FILE="/etc/crontabs/root"
LINE1="# dlink fan script runs every 5 minutes"
LINE2="*/5 * * * * /sbin/fan_ctrl.sh"
case "$(board_name)" in
d-link,dgs-1210-28p-f|d-link,dgs-1210-28mp-f)
[ -f "$CRON_FILE" ] || exit 0
# Remove legacy user-space fan_ctrl script from crontab
if grep -x -F -q "$LINE1" "$CRON_FILE" && grep -x -F -q "$LINE2" "$CRON_FILE"; then
grep -v -x -F "$LINE1" "$CRON_FILE" | grep -v -x -F "$LINE2" > "${CRON_FILE}.tmp"
mv "${CRON_FILE}.tmp" "$CRON_FILE"
[ -s "$CRON_FILE" ] || rm -f "$CRON_FILE"
/etc/init.d/cron restart 2>/dev/null
# if the user changed it, just warn (fan_ctrl.sh is gone anyway).
elif grep -E -q '^[[:space:]]*[^#].*fan_ctrl\.sh' "$CRON_FILE"; then
logger -t dlinkfan_migration -p user.err "Custom fan_ctrl.sh entry found in $CRON_FILE. Leaving file untouched!"
exit 1
fi
;;
esac
exit 0
@@ -1,13 +0,0 @@
#!/bin/sh
PSU_TEMP=$(cut -c1-2 /sys/class/hwmon/hwmon0/temp1_input)
FAN_CTRL='/sys/class/hwmon/hwmon0/pwm1'
PSU_THRESH=51000
if [ "$PSU_TEMP" -ge "$PSU_THRESH" ];then
echo "250" > $FAN_CTRL
else
echo "156" > $FAN_CTRL
fi