Device Codename
odin2
LineageOS Version
23.2
Build Date
20260924
Kernel Version
5.15.211-g0c12897d2988
Baseband Version
apq
System Modifications
MindTheGapps. No Magisk, no other system modifications.
Expected Behavior
The stock firmware ships manual fan control (Off / Quiet / Smart / Sport / custom speed, with per-app overrides in AYN's settings app). The fan hardware is identical, so a way to control it manually should exist on LineageOS as well.
Current Behavior
The fan only runs in automatic mode (kernel pwm-fan driven by QMI/thermal). There is no way to set the speed manually.
(Note: the findings below very likely apply to odin2portal and odin2mini as well, since they share the same qcs8550 platform, kernel and vendor — I could only verify on odin2.)
I traced the stock implementation and tested every userspace route on LineageOS; both are documented below in case they help pick an approach.
How stock implements it (from stock v1.0.0.208 firmware, Dec 2023):
OdinSettings writes system settings fan_mode (0/1/2/3/4/5/6) and fan_speed (duty cycle in ns, 0-50000)
- The
TouchMapping system app picks those up and calls into librsinput.so (JNI, set_fan_data)
- The library writes frames to
/dev/rscom, a char device provided by a driver built into AYN's stock kernel
- That driver talks to the device MCU over uart15 (reg 0x89c000); the MCU drives the fan
The MCU node is visible in AYN's open-source mainline tree (AYNTechnologies/linux, branch ayn/v7.0, arch/arm64/boot/dts/qcom/qcs8550-ayn-common.dtsi):
&uart15 {
gamepad {
compatible = "gamepad,rsinput";
...
};
};
What is missing on LineageOS:
android_kernel_ayn_qcs8550 contains no rscom/rsinput/rstouch driver (the mainline drivers/input/joystick/rsinput.c covers the gamepad only, no fan). The stock driver is built into AYN's kernel and its source has not been published.
- Our DT has no uart15/gamepad node. The gamepad works via gpio-keys, but the MCU is unreachable.
Why userspace workarounds are not viable (all tested on the 20260924 build):
cooling_device34 (pwm-fan, pm8550 PWM, DT cooling-levels [0,40,65,75,90,100,120,150,175]): writes to cur_state are reverted within ~11 ms by kworker/0:1 (QMI votes via qti_qmi_cdev) and threaded IRQs irq/197-c271000 / irq/201-c273000, together ~90 writes/sec.
- hwmon
pwm1 is read-only in this build (the pwm_fan module exports no store function).
- Unbinding pwm-fan to drive the PWM through /sys/class/pwm instead cuts fan power: the
fan-supply fixed regulator (fan_reg, tlmm GPIO 109) loses its last consumer and disables.
thermal-engine-v2 (vendor binary) starts fine and creates its sockets, but the partition has no config for it. The stock v208 configs (/vendor/etc/thermal_config/thermal-engine-{normal,high}.conf) only contain CPU/GPU/battery step-wise rules — no fan rule. The libthermalclient request whitelist (spkr, override, config_set, config_query, camera_bw, display_bw) has no fan client either.
- The userspace thermal HAL never writes the fan directly (verified by freezing the process; reverts continue).
Possible Solution
- Minimal: a module parameter on
drivers/hwmon/pwm-fan.c (e.g. manual_state) that takes precedence over external set_cur_state requests when set, with -1 meaning auto. I'm happy to submit a Gerrit change for this if there is interest.
- Complete: port the rscom/uart15 MCU interface. This restores stock parity and is what community tooling ultimately needs — OdinTools implements the stock fan modes via the settings above and is non-functional on LineageOS today (it also depends on the stock-only
PServerBinder service for privileged execution).
Steps to Reproduce
As root, try to hold a manual fan level:
echo 4 > /sys/class/thermal/cooling_device34/cur_state
cat /sys/class/thermal/cooling_device34/cur_state # already overwritten (<11 ms)
To see the writers, use a kprobe:
echo 'p:fanwrite pwm_fan:pwm_fan_set_cur_state' > /sys/kernel/tracing/kprobe_events
echo 1 > /sys/kernel/tracing/events/kprobes/fanwrite/enable
echo 1 > /sys/kernel/tracing/tracing_on
sleep 8
cat /sys/kernel/tracing/trace | grep fanwrite | awk '{print $1}' | sort | uniq -c
Result: irq/197-c271000, irq/201-c273000 and kworker/0:1 writing continuously (~90/s combined).
Confirmation
Device Codename
odin2
LineageOS Version
23.2
Build Date
20260924
Kernel Version
5.15.211-g0c12897d2988
Baseband Version
apq
System Modifications
MindTheGapps. No Magisk, no other system modifications.
Expected Behavior
The stock firmware ships manual fan control (Off / Quiet / Smart / Sport / custom speed, with per-app overrides in AYN's settings app). The fan hardware is identical, so a way to control it manually should exist on LineageOS as well.
Current Behavior
The fan only runs in automatic mode (kernel pwm-fan driven by QMI/thermal). There is no way to set the speed manually.
(Note: the findings below very likely apply to odin2portal and odin2mini as well, since they share the same qcs8550 platform, kernel and vendor — I could only verify on odin2.)
I traced the stock implementation and tested every userspace route on LineageOS; both are documented below in case they help pick an approach.
How stock implements it (from stock v1.0.0.208 firmware, Dec 2023):
OdinSettingswrites system settingsfan_mode(0/1/2/3/4/5/6) andfan_speed(duty cycle in ns, 0-50000)TouchMappingsystem app picks those up and calls intolibrsinput.so(JNI,set_fan_data)/dev/rscom, a char device provided by a driver built into AYN's stock kernelThe MCU node is visible in AYN's open-source mainline tree (AYNTechnologies/linux, branch ayn/v7.0,
arch/arm64/boot/dts/qcom/qcs8550-ayn-common.dtsi):What is missing on LineageOS:
android_kernel_ayn_qcs8550contains no rscom/rsinput/rstouch driver (the mainlinedrivers/input/joystick/rsinput.ccovers the gamepad only, no fan). The stock driver is built into AYN's kernel and its source has not been published.Why userspace workarounds are not viable (all tested on the 20260924 build):
cooling_device34(pwm-fan, pm8550 PWM, DT cooling-levels[0,40,65,75,90,100,120,150,175]): writes tocur_stateare reverted within ~11 ms bykworker/0:1(QMI votes via qti_qmi_cdev) and threaded IRQsirq/197-c271000/irq/201-c273000, together ~90 writes/sec.pwm1is read-only in this build (the pwm_fan module exports no store function).fan-supplyfixed regulator (fan_reg, tlmm GPIO 109) loses its last consumer and disables.thermal-engine-v2(vendor binary) starts fine and creates its sockets, but the partition has no config for it. The stock v208 configs (/vendor/etc/thermal_config/thermal-engine-{normal,high}.conf) only contain CPU/GPU/battery step-wise rules — no fan rule. The libthermalclient request whitelist (spkr,override,config_set,config_query,camera_bw,display_bw) has no fan client either.Possible Solution
drivers/hwmon/pwm-fan.c(e.g.manual_state) that takes precedence over externalset_cur_staterequests when set, with -1 meaning auto. I'm happy to submit a Gerrit change for this if there is interest.PServerBinderservice for privileged execution).Steps to Reproduce
As root, try to hold a manual fan level:
To see the writers, use a kprobe:
Result:
irq/197-c271000,irq/201-c273000andkworker/0:1writing continuously (~90/s combined).Confirmation