Last active
January 4, 2024 04:39
-
-
Save ikus060/26a33ce1e82092b4d2dbdf18c3610fde to your computer and use it in GitHub Desktop.
Python script to edit the X8DTL-iF Registry to control fan speed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Licensed to the Apache Software Foundation (ASF) under one | |
# or more contributor license agreements. See the NOTICE file | |
# distributed with this work for additional information | |
# regarding copyright ownership. The ASF licenses this file | |
# to you under the Apache License, Version 2.0 (the | |
# "License"); you may not use this file except in compliance | |
# with the License. You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, | |
# software distributed under the License is distributed on an | |
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
# KIND, either express or implied. See the License for the | |
# specific language governing permissions and limitations | |
# under the License. | |
import sys | |
import argparse | |
from smbus import SMBus | |
from contextlib import contextmanager | |
# | |
# All register ceom from | |
# https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/drivers/hwmon/w83795.c | |
# | |
# | |
ADDRESS=0x2f | |
NUVOTON_VENDOR_ID = 0xa3 | |
CHIP_ID = 0x79 | |
W83795_REG_BANKSEL = 0x00 | |
# Fan Control Mode Selection Registers (FCMS) | |
W83795_REG_FCMS1 = 0x201 | |
W83795_REG_FCMS2 = 0x208 | |
W83795_REG_TFMR = lambda index: 0x202 + index | |
W83795_REG_TSS = lambda index: 0x209 + index | |
W83795_REG_SFIV_TEMP = lambda index: range(0x280 + index * 0x10, 0x280 + index * 0x10 + 7) | |
W83795_REG_SFIV_DCPWM = lambda index: range(0x288 + index * 0x10, 0x288 + index * 0x10 + 7) | |
W83795_REG_CTFS = lambda index: 0x268 + index | |
#Fan Output PWM Frequency Prescalar (FOPFP) | |
W83795_REG_FOPFP = lambda index: 0x218 + index | |
#Fan Output Nonstop Value (FONV) | |
W83795_REG_FONV = lambda index: 0x228 + index | |
#Hystersis of Temperature (HT) | |
W83795_REG_HT = lambda index: 0x270 + index | |
#Target Temperature of Temperature Inputs | |
W83795_TTTI = lambda index: 0x260 + index | |
#Read temperature | |
W83795_REG_VRLSB = 0x3C | |
W83795_REG_TEMP_READ = [0x21, 0x22, 0x23, 0x24, 0x1f, 0x20] | |
FANS = range(0,8) | |
TEMPS = range(0,6) | |
@contextmanager | |
def bank(bus, value): | |
prev_value = w83795_set_bank(bus, value) | |
yield | |
w83795_set_bank(bus, prev_value) | |
def w83795_set_bank(bus, bank): | |
assert bank in [0,1,2,3] | |
# Read current bank value | |
cur_bank = bus.read_byte_data(ADDRESS, W83795_REG_BANKSEL) | |
# If the bank is already set, nothing to do | |
if cur_bank == bank: | |
return cur_bank | |
# Change the bank | |
bus.write_byte_data(ADDRESS, W83795_REG_BANKSEL, bank) | |
# Return previous bank value | |
return cur_bank | |
def w83795_write(bus, reg, value): | |
""" | |
Write into the given registry. | |
""" | |
with bank(bus, reg >> 8): | |
return bus.write_byte_data(ADDRESS, reg & 0xff, value & 0xff) | |
def w83795_read(bus, reg): | |
""" | |
Read the given registry. | |
""" | |
if hasattr(reg, '__iter__'): | |
with bank(bus, reg[0] >> 8): | |
return map(lambda r: bus.read_byte_data(ADDRESS, r & 0xff), reg) | |
with bank(bus, reg >> 8): | |
return bus.read_byte_data(ADDRESS, reg & 0xff) | |
def to_degree(val, low=0, hi=127): | |
"""Convert hex value to degree.""" | |
return val | |
def to_perc(value): | |
"""Convert hex value to percentage.""" | |
return value * 100 / 255 | |
def from_degree(value): | |
"""Convert degree to hex""" | |
value = max(min(int(value) + 1,127),0) | |
return value | |
def from_perc(value): | |
"""Convert perc to hex""" | |
value = max(min(int(value) + 1,100),0) | |
return (value * 255 / 100) & 0xff | |
def main(): | |
# Read arguments | |
parser = argparse.ArgumentParser(description='Change SuperMicro X8 Fan Control.') | |
parser.add_argument('-m', '--mode', type=str, choices=['smart', 'cruise'], help='Set the fan mode: smart or cruise. smart: to use Smart Fan mode. cruise: to use Thermal Cruise mode.') | |
parser.add_argument('-p', '--pwm', type=str, help='Set Fan Duty (in percentage).') | |
parser.add_argument('-t', '--temp', type=str, help='Set Temperature (in Celsius) associated to pwm.') | |
parser.add_argument('-H', '--hystersis', type=int, help='Set Hystersis value in degree (0-15)') | |
args = parser.parse_args() | |
# Open SMBus | |
try: | |
bus = SMBus(0) | |
except: | |
print("Failed to open i2c bus (/dev/i2d-0). Make sure i2c-dev module is loaded.") | |
return | |
#Check if we have the right device. | |
try: | |
vendor = w83795_read(bus, 0xfd) | |
chipid = w83795_read(bus, 0xfe) | |
#debug("vendor %s, chipid %s" % (vendor, chipid)) | |
if vendor != NUVOTON_VENDOR_ID or chipid != CHIP_ID: | |
print("unexpected vendor %s, chipid %s" % (vendor, chipid)) | |
return | |
# Check if Smarts Fan Control is enabled | |
if args.mode == 'smart': | |
# Check arguments | |
if not args.pwm or not args.temp: | |
print('pwm and temp are required') | |
return | |
pwm = args.pwm.split(',') | |
temp = args.temp.split(',') | |
if len(pwm) != len(temp): | |
print("pwm and temp must have the same number of values") | |
return | |
# Change Smart Fan Control value | |
for i in range(0,7): | |
p = pwm[i] if i < len(pwm) else pwm[-1] | |
tt = temp[i] if i < len(temp) else temp[-1] | |
print("Set Smart Fan Control %s%% - %sC" % (p, tt)) | |
for t in TEMPS: | |
w83795_write(bus, W83795_REG_SFIV_DCPWM(t)[i], from_perc(p)) | |
w83795_write(bus, W83795_REG_SFIV_TEMP(t)[i], from_degree(tt)) | |
# Change Minimum PWM | |
for f in FANS: | |
w83795_write(bus, W83795_REG_FONV(f), from_perc(pwm[0])) | |
# Change critical Temp | |
for t in TEMPS: | |
w83795_write(bus, W83795_REG_CTFS(t), from_degree(temp[-1])) | |
# Set Smart Fan Control Mode T6FC - T1FC | |
w83795_write(bus, W83795_REG_FCMS1, 0x0) | |
w83795_write(bus, W83795_REG_FCMS2, 0x3f) | |
elif args.mode == 'cruise': | |
# Check arguments | |
if not args.temp: | |
print('temp is required') | |
return | |
temp = int(args.temp) | |
print("Set Thermal Cruise %sC" % (temp,)) | |
for t in TEMPS: | |
w83795_write(bus, W83795_TTTI(t), from_degree(temp)) | |
# Change critical Temp | |
for t in TEMPS: | |
w83795_write(bus, W83795_REG_CTFS(t), from_degree(80)) | |
# Set Thermal Cruise Mode. | |
w83795_write(bus, W83795_REG_FCMS1, 0x0) | |
w83795_write(bus, W83795_REG_FCMS2, 0x0) | |
# Set hystersis | |
if args.hystersis: | |
ht = max(min(args.hystersis,15),0) | |
print("Set hystersis %sC" % ht) | |
# Change Smart Fan Control value | |
for t in range(0,6): | |
w83795_write(bus, W83795_REG_HT(t), ht) | |
if args.mode or args.pwm or args.temp or args.hystersis: | |
return | |
# Check if Smarts Fan Control is enabled | |
fcms1 = w83795_read(bus, W83795_REG_FCMS1) | |
fcms2 = w83795_read(bus, W83795_REG_FCMS2) & 0xff | |
# Default to show all data. | |
for t in TEMPS: | |
print("Temp%s to Fan mapping Relationships (T%sFMR)" % (t+1, t+1)) | |
tfmr = w83795_read(bus, W83795_REG_TFMR(t)) | |
fans= [i+1 for i in FANS if tfmr & (0x1<<i)] | |
print(' '.join(['Fan%s' % i for i in fans])) | |
print("Smart Fan Control Table (SFIV)") | |
temp = w83795_read(bus, W83795_REG_SFIV_TEMP(t)) | |
print(''.join([("%sC" % to_degree(v)).rjust(6) for v in temp])) | |
dcpwm = w83795_read(bus, W83795_REG_SFIV_DCPWM(t)) | |
print(''.join([("%s%%" % to_perc(v)).rjust(6) for v in dcpwm])) | |
ttti = w83795_read(bus, W83795_TTTI(t)) | |
print("Thermal Cruise (TTTI): %sC" % (ttti,)) | |
ctfs = w83795_read(bus, W83795_REG_CTFS(t)) | |
print("Critical Temperature (T%sCTFS): %sC" % (t, to_degree(ctfs))) | |
ht = w83795_read(bus, W83795_REG_HT(t)) & 0b1111 | |
print("Hysteresis (HT%s): %sC" % (t, ht)) | |
tmp = w83795_read(bus, W83795_REG_TEMP_READ[t]) | |
print("Current Temperature (TEMP%s): %sC" % (t, to_degree(tmp),)) | |
print('---') | |
for f in FANS: | |
fonv = w83795_read(bus, W83795_REG_FONV(f)) | |
print("Fan%s Output Nonstop Value (F%sONV): %s%%" % (f+1, f+1, to_perc(fonv))) | |
#for f in range(0,6): | |
# w83795_write(bus, W83795_REG_FONV(f), 50) | |
#w83795_write(bus, W83795_REG_SFIV_DCPWM(0)[0], 50) | |
#w83795_write(bus, W83795_REG_SFIV_TEMP(0)[0], 85) | |
#w83795_write(bus, W83795_REG_SFIV_TEMP(1)[0], 85) | |
finally: | |
bus.close() | |
if __name__ == "__main__": | |
main() |
IOError: [Errno 16] Device or resource busy
This means the kernel cannot talk to the device.
Depending on the X8 board you are running this. I know some X8 boards hug
the i2c device and don't allow to read/write from the OS.
…On Fri, Jul 16, 2021 at 8:54 AM Alex Dodd ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
I was having a look at this myself today in an effort to have a little
better fan control. I've got an X8DTH motherboard for reference which may
or may not be a contributing factor of below.
I have this working on a x8DTE-F and x8DTI-F with Proxmox as Host. A few
key things to ensure:
1. Verify you have the w83795 chip, you can do this by installing
lm-sensors and running sensors-detect
2. Install i2c-tools
3. Install python-smbus
4. Install python3-smbus
5. Install libi2c-dev
6. Comment out (#) w83795 from /etc/modules and reboot (thanks
@bennet-esyoil <https://github.com/bennet-esyoil>)
I'm running unraid, which itself is based on slackware 14.2.
1.
Run sensor-detect, and verified.
2.
I've installed i2c-tools.
3-5) I don't find precompiled packages, so maybe i'll have to look into
compiling, but I can't be the first to do this on slackware i'd have
thought.
1. Probably a slackware thing again, but I don't have an /etc/modules
If i run:
# python smx8fancontrol.py
Traceback (most recent call last): File "smx8fancontrol.py", line 515, in
<module> main() File "smx8fancontrol.py", line 281, in main vendor =
w83795_read(bus, 0xfd) File "smx8fancontrol.py", line 197, in w83795_read
with bank(bus, reg >> 8): File "/usr/lib64/python2.7/contextlib.py", line
17, in __enter__ return self.gen.next() File "smx8fancontrol.py", line 129,
in bank prev_value = w83795_set_bank(bus, value) File "smx8fancontrol.py",
line 145, in w83795_set_bank cur_bank = bus.read_byte_data(ADDRESS,
W83795_REG_BANKSEL) IOError: [Errno 16] Device or resource busy
I tried installing smbus and smbus2 with pip, but then i think i need to
figure out a virtual env to wrap it all together, and my knowledge starts
to get more shaky than it already is.
I'm going to have a look at getting packages installed first then look at
virtual envs as a last resort.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<https://gist.github.com/26a33ce1e82092b4d2dbdf18c3610fde#gistcomment-3815368>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHA5I42QE2LRTAUVI6VGWDTYATXDANCNFSM4JNSFHSA>
.
--
Patrik Dufresne ***@***.***>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I was having a look at this myself today in an effort to have a little better fan control. I've got an X8DTH motherboard for reference which may or may not be a contributing factor of below.
I'm running unraid, which itself is based on slackware 14.2.
Run sensor-detect, and verified.
I've installed i2c-tools.
3-5) I don't find precompiled packages, so maybe i'll have to look into compiling, but I can't be the first to do this on slackware i'd have thought.
/etc/modules
If i run:
# python smx8fancontrol.py
I tried installing smbus and smbus2 with pip, but then i think i need to figure out a virtual env to wrap it all together, and my knowledge starts to get more shaky than it already is.
I'm going to have a look at getting packages installed first then look at virtual envs as a last resort.