Skip to content

odin2: manual fan control from stock firmware not possible (vendor rscom/MCU interface missing, userspace alternatives not viable) #676

Description

@drewano

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):

  1. OdinSettings writes system settings fan_mode (0/1/2/3/4/5/6) and fan_speed (duty cycle in ns, 0-50000)
  2. The TouchMapping system app picks those up and calls into librsinput.so (JNI, set_fan_data)
  3. The library writes frames to /dev/rscom, a char device provided by a driver built into AYN's stock kernel
  4. 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):

  1. 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.
  2. hwmon pwm1 is read-only in this build (the pwm_fan module exports no store function).
  3. 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.
  4. 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.
  5. The userspace thermal HAL never writes the fan directly (verified by freezing the process; reverts continue).

Possible Solution

  1. 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.
  2. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions