Add fan control functionality
This commit is contained in:
parent
d42d95bb8c
commit
c7042fa487
4 changed files with 118 additions and 0 deletions
21
README.md
21
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.
|
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`.
|
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
|
## License
|
||||||
|
|
||||||
This is licensed under GNU GPLv3.
|
This is licensed under GNU GPLv3.
|
||||||
|
|
BIN
extras/ui.png
BIN
extras/ui.png
Binary file not shown.
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 151 KiB |
35
main.py
35
main.py
|
@ -3,6 +3,9 @@ import time
|
||||||
class Plugin:
|
class Plugin:
|
||||||
CPU_COUNT = 8
|
CPU_COUNT = 8
|
||||||
SCALING_FREQUENCIES = [1700000, 2400000, 2800000]
|
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)
|
# call from main_view.html with setCPUs(count, smt)
|
||||||
async def set_cpus(self, count, smt=True) -> int:
|
async def set_cpus(self, count, smt=True) -> int:
|
||||||
|
@ -66,6 +69,38 @@ class Plugin:
|
||||||
freq = int(freq_maybe)
|
freq = int(freq_maybe)
|
||||||
return self.SCALING_FREQUENCIES.index(freq)
|
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
|
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded
|
||||||
async def _main(self):
|
async def _main(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -34,6 +34,14 @@
|
||||||
return call_plugin_method("get_max_boost", {});
|
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
|
// other logic
|
||||||
|
|
||||||
async function onReady() {
|
async function onReady() {
|
||||||
|
@ -42,6 +50,7 @@
|
||||||
setToggleState(document.getElementById("smtToggle"), await getSMT());
|
setToggleState(document.getElementById("smtToggle"), await getSMT());
|
||||||
selectNotch("cpuThreadsNotch", await getCPUs() - 1, 8);
|
selectNotch("cpuThreadsNotch", await getCPUs() - 1, 8);
|
||||||
selectNotch("frequencyNotch", await getMaxBoost(), 3);
|
selectNotch("frequencyNotch", await getMaxBoost(), 3);
|
||||||
|
selectNotch("fanNotch", await getFanTick(), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setCPUNotch(index) {
|
async function setCPUNotch(index) {
|
||||||
|
@ -93,6 +102,12 @@
|
||||||
selectNotch(ROOT_ID, await getMaxBoost(), 3);
|
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) {
|
function selectNotch(rootId, index, elements) {
|
||||||
// WARNING: this yeets any style in div of slider
|
// WARNING: this yeets any style in div of slider
|
||||||
const ENABLED_CLASS = "gamepadslider_TickActive_j418S";
|
const ENABLED_CLASS = "gamepadslider_TickActive_j418S";
|
||||||
|
@ -236,5 +251,52 @@
|
||||||
WARNING: This will change the CPU governor.
|
WARNING: This will change the CPU governor.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Fan voltage selector -->
|
||||||
|
<div class="gamepaddialog_Field_eKmEX gamepaddialog_WithFirstRow_2bDqk gamepaddialog_WithChildrenBelow_37xzV gamepaddialog_InlineWrapShiftsChildrenBelow_3LCXh gamepaddialog_WithBottomSeparator_3YKpU gamepaddialog_ChildrenWidthFixed_ljcbL gamepaddialog_ExtraPaddingOnChildrenBelow_3nLNL gamepaddialog_StandardPadding_xIITX gamepaddialog_HighlightOnFocus_2HFrm Panel Focusable">
|
||||||
|
<div class="gamepaddialog_FieldLabelRow_2VcTl">
|
||||||
|
<div class="gamepaddialog_FieldLabel_3jMlJ">Fan Voltage</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepaddialog_FieldChildren_2rhav">
|
||||||
|
<div id="fanNotch" class="gamepadslider_SliderControlAndNotches_23hjX Focusable" tabindex="0" style="--normalized-slider-value:0.33;">
|
||||||
|
<div class="gamepadslider_SliderControl_1udlG">
|
||||||
|
<div class="gamepadslider_SliderTrack_2_vG6 gamepadslider_SliderHasNotches_1Lr71 "></div>
|
||||||
|
<div class="gamepadslider_SliderHandleContainer_8xNY6">
|
||||||
|
<div class="gamepadslider_SliderHandle_11PBf"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotchContainer_2yM7S Panel Focusable">
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch0" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(0)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">0V</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch1" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(1)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">1V</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch2" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(2)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">2V</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch3" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(3)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">3V</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch4" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(4)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">4V</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch5" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(5)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">5V</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamepadslider_SliderNotch_LYPXt">
|
||||||
|
<div id="fanNotch6" class="gamepadslider_SliderNotchTick_u8QEa gamepadslider_TickActive_j418S" onclick='onSetFanNotch(6)'></div>
|
||||||
|
<div class="gamepadslider_SliderNotchLabel_dbACW">Auto</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue