diff --git a/README.md b/README.md index 4d52803..d0d236e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,27 @@ Use `cpupower` (usage: `cpupower --help`). This isn't strictly how PowerTools does it, but it's a multi-step process which can involve changing the CPU governor. All that can be done automatically by `cpupower frequency-set --freq {frequency}` where `{frequency}` is `1.7G`, `2.4G` or `2.8G`. +### Set Fan speed + +Enable automatic control: `echo 0 > /sys/class/hwmon/hwmon5/recalculate` enables automatic fan control. + +Disable automatic control: `echo 1 > /sys/class/hwmon/hwmon5/recalculate` disables automatic (temperature-based) fan control and starts using the set fan voltage instead. + +Set the fan voltage: `echo {voltage} > /sys/class/hwmon/hwmon5/fan1_target` where `{voltage}` is a value from 0 to 4000 mV\*. + +Read the actual fan voltage: `cat /sys/class/hwmon/hwmon5/fan1_input` gives the fan voltage in mV\*. + +\*WARNING: I'm not sure if that's actually fan voltage. I've set it to 6000 (1V higher!!! than the fan's rated 5V) and the fan responded, so it appears to not be limited. Blow up your fans at your own risk; read the license. + +NOTE: There's a bug in the fan controller; if you enable automatic fan control it will forget any previously-set target despite it appearing to be set correctly (i.e. `cat /sys/class/hwmon/hwmon5/fan1_target` will display the correct value). +When you disable automatic fan control, you will need to set the fan voltage again. + +### Steam Deck kernel patches + +This is how I figured out how the fan stuff works. +I've only scratched the surface of what this code allows, I'm sure it has more useful information. +https://lkml.org/lkml/2022/2/5/391 + ## License This is licensed under GNU GPLv3. diff --git a/extras/ui.png b/extras/ui.png index a282a17..7ef50da 100644 Binary files a/extras/ui.png and b/extras/ui.png differ diff --git a/main.py b/main.py index bf2d8d7..952e3e5 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,9 @@ import time class Plugin: CPU_COUNT = 8 SCALING_FREQUENCIES = [1700000, 2400000, 2800000] + FAN_VOLTAGES = [0, 1000, 2000, 3000, 4000, 5000] + + set_fan_voltage = None # call from main_view.html with setCPUs(count, smt) async def set_cpus(self, count, smt=True) -> int: @@ -66,6 +69,38 @@ class Plugin: freq = int(freq_maybe) return self.SCALING_FREQUENCIES.index(freq) + async def set_fan_tick(self, tick: int): + self.set_fan_voltage = tick # cache voltage set to echo back in get_fan_tick() + if tick >= len(self.FAN_VOLTAGES): + # automatic mode + write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 0) + write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", 4099) # 4099 is default + else: + # manual voltage + write_to_sys("/sys/class/hwmon/hwmon5/recalculate", 1) + write_to_sys("/sys/class/hwmon/hwmon5/fan1_target", self.FAN_VOLTAGES[tick]) + + async def get_fan_tick(self) -> int: + fan_target = int(read_from_sys("/sys/class/hwmon/hwmon5/fan1_target", amount=-1).strip()) + fan_input = int(read_from_sys("/sys/class/hwmon/hwmon5/fan1_input", amount=-1).strip()) + fan_target_v = float(fan_target) / 1000 + fan_input_v = float(fan_input) / 1000 + if self.set_fan_voltage is not None: + x = self.set_fan_voltage + self.set_fan_voltage = None + return x + elif fan_target == 4099 or (int(round(fan_target_v)) != int(round(fan_input_v))): + # cannot read /sys/class/hwmon/hwmon5/recalculate, so guess based on available fan info + # NOTE: the fan takes time to ramp up, so fan_target will never approximately equal fan_input + # when fan_target was changed recently (hence set voltage caching) + return len(self.FAN_VOLTAGES) + else: + # quantize voltage to nearest tick (price is right rules; closest without going over) + for i in range(len(self.FAN_VOLTAGES)-1): + if fan_target <= self.FAN_VOLTAGES[i]: + return i + return len(self.FAN_VOLTAGES)-1 # any higher value is considered as highest manual setting + # Asyncio-compatible long-running code, executed in a task when the plugin is loaded async def _main(self): pass diff --git a/main_view.html b/main_view.html index 294f330..b8d099e 100644 --- a/main_view.html +++ b/main_view.html @@ -33,6 +33,14 @@ function getMaxBoost() { return call_plugin_method("get_max_boost", {}); } + + function setFanTick(tick) { + return call_plugin_method("set_fan_tick", {"tick": tick}); + } + + function getFanTick() { + return call_plugin_method("get_fan_tick", {}); + } // other logic @@ -42,6 +50,7 @@ setToggleState(document.getElementById("smtToggle"), await getSMT()); selectNotch("cpuThreadsNotch", await getCPUs() - 1, 8); selectNotch("frequencyNotch", await getMaxBoost(), 3); + selectNotch("fanNotch", await getFanTick(), 7); } async function setCPUNotch(index) { @@ -93,6 +102,12 @@ selectNotch(ROOT_ID, await getMaxBoost(), 3); } + async function onSetFanNotch(index) { + const ROOT_ID = "fanNotch"; + await setFanTick(index); + selectNotch(ROOT_ID, index, 7); + } + function selectNotch(rootId, index, elements) { // WARNING: this yeets any style in div of slider const ENABLED_CLASS = "gamepadslider_TickActive_j418S"; @@ -236,5 +251,52 @@ WARNING: This will change the CPU governor. + + +
+
+
Fan Voltage
+
+
+
+
+
+
+
+
+
+
+
+
+
0V
+
+
+
+
1V
+
+
+
+
2V
+
+
+
+
3V
+
+
+
+
4V
+
+
+
+
5V
+
+
+
+
Auto
+
+
+
+
+