-
-
Save timemaster67/3d8b703d15e65063c61d194c0d357e1d to your computer and use it in GitHub Desktop.
Python script to edit the X8DTL-iF Registry to control fan speed.
This file contains hidden or 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 | |
# | |
#chip i2c address to use | |
ADDRESS=0x2f | |
NUVOTON_VENDOR_ID = 0x5ca3 | |
CHIP_ID = 0x79 | |
W83795_REG_VENDOR_ID = 0X0FD | |
W83795_REG_CHIP_ID = 0X0FE | |
#the registery is banked. To access the full data, user must switch the bank, like a page, in this register. | |
#when this register lower 2 bit are set, the rest of the data change according to it. | |
#bank0 show sensors related config bank 1 show nothing interesting bank 2 show fan related config. bank 3 show peci related config | |
W83795_REG_BANKSEL = 0x00 | |
# Fan Control Mode Selection Registers (FCMS) | |
W83795_REG_FCMS1 = 0x201 | |
W83795_REG_FCMS2 = 0x208 | |
#Temperature to Fan mapping Relationships Register (TFMR) | |
#W83795_REG_TFMR = lambda index: 0x202 + index | |
#There is 6 temperature source thus 6 entry, Each entry contains a binary representation of if one of the 8 pwm is linked to that temperature | |
W83795_REG_TFMR = lambda index: 0x202 + index | |
#Temperature Source Selection Register (TSS) | |
#data for pwm1 and 2 are mixed in the first byte, pwm3 and 4, second... | |
W83795_REG_TSS = lambda index: 0x209 + floor((index/2)) | |
#Default Fan Speed at Power-on (DFSP) | |
W83795_REG_DFSP = 0x20c | |
#the range is used to access all element from 0x80 to 0xde without declaring everything. | |
#temp1 is index 0, thus 0 * 0x10 is 0. we access 0x80 to 0x87 straight. | |
#temp3 is index 1, thus 1* 0x10 is 0x10, so we access 0x80 + 0x10 = 0x9x to 0x96 | |
W83795_REG_SFIV_TEMP = lambda index: range(0x280 + index * 0x10, 0x280 + index * 0x10 + 6) | |
W83795_REG_SFIV_DCPWM = lambda index: range(0x288 + index * 0x10, 0x288 + index * 0x10 + 6) | |
#Fan Output Value (FOV) | |
W83795_REG_FOV = lambda index: 0x210 + index | |
#Fan Output Nonstop Value (FONV) | |
W83795_REG_FONV = lambda index: 0x228 + index | |
#Fan Output Stop Time (FOST) | |
W83795_REG_FOST = lambda index: 0x230 + index | |
#SmartFan Output Step Up Time (SFOSUT) | |
W83795_REG_SFOSUT = 0x20D | |
#SmartFan Output Step Down Time (SFOSDT) | |
W83795_REG_SFOSDT = 0x20E | |
#Target Temperature of Temperature Inputs (TTTI) | |
W83795_REG_TTTI = lambda index: 0x260 + index | |
#Critical Temperature to Full Speed all fan (CTFS) | |
W83795_REG_CTFS = lambda index: 0x268 + index | |
#Hystersis of Temperature (HT) | |
W83795_REG_HT = lambda index: 0x270 + index | |
#not in use | |
#Fan Output PWM Frequency Prescalar (FOPFP) | |
W83795_REG_FOPFP = lambda index: 0x218 + index | |
@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, ignoring reserved bit and high byte access fields. | |
field = bus.read_byte_data(ADDRESS, W83795_REG_BANKSEL) | |
cur_bank = field & 0b00000011 | |
# If the bank is already set, nothing to do | |
if cur_bank == bank: | |
return cur_bank | |
# Change the bank, preserving reserved value | |
bus.write_byte_data(ADDRESS, W83795_REG_BANKSEL, field | 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 CheckChipset(bus): | |
#Check that we are dealing with the W83795 chipset | |
#set the register to get the high bit value, while keeping the reserved bit and the bank intact | |
multidata = w83795_read(bus, W83795_REG_BANKSEL) | |
w83795_write(bus, W83795_REG_BANKSEL, (multidata & 0b01111111) | 0b10000000) | |
#get the high bit value | |
vendor = w83795_read(bus, W83795_REG_VENDOR_ID) | |
#shift vendor part to the left | |
vendor = vendor << 8 | |
#set the register to get the low bit value, while keeping the reserved bit and the bank intact | |
w83795_write(bus, W83795_REG_BANKSEL, multidata & 0b01111111) | |
vendor = vendor | w83795_read(bus, W83795_REG_VENDOR_ID) | |
chipid = w83795_read(bus, W83795_REG_CHIP_ID) | |
#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 False | |
return True | |
def ShowInfo(bus): | |
#get fan configuration | |
fcms1 = w83795_read(bus, W83795_REG_FCMS1) | |
fcms2 = w83795_read(bus, W83795_REG_FCMS2) & 0b00111111 | |
#get the Temperature to Fan mapping Relationships Register (TFMR) and keep a cache for multiple reuse | |
tfmr = [] | |
for t in range(0,6): | |
tfmr.append(w83795_read(bus, W83795_REG_TFMR(t))) | |
for f in range(0,6): | |
#for each controllable pwm | |
print("Fan%s" % (f+1)) | |
#binary fan value, used for binary and | |
#shift number binary 00000001 f bytes to the left, | |
#00000001 shifted 1 time to the left equals 00000010. | |
fbin = 1 << f | |
#show the calculated binary number | |
#print(fbin) | |
# get the operating mode of the fan (manual, speed cruise, thermal cruise, smart fan) | |
#Check the mode of the fan by looking at the | |
#print(fcms1 & fbin) | |
#print(fcms2 & fbin) | |
isSpeedCruise = fcms1 & fbin | |
#wrong wrong wrong. fcms2 affiche les ttemps, pas les fans. !! 8 fans, 6 temp!! il faut checker les temps to fans relationship, | |
#ensuite checker le mode des temps. | |
isThermalCruise = not isSpeedCruise and not(fcms2 & fbin) | |
isSmartFan = not isSpeedCruise and (fcms2 & fbin) | |
tempToFanMapping = [] | |
#Check if the fan is in manual mode | |
if (not isSpeedCruise): | |
#if the fan is marked not in speed cruise but there is no temp relationship to this fan, it's in manual mode | |
hasAtLeastOneRelationship = False | |
#there is 6 different temperature | |
for t in range(0,6): | |
#check if the current fan (fbin) has a relationship with any of the 6 temperature | |
if (tfmr[t] & fbin > 0): | |
hasAtLeastOneRelationship = True | |
#save the temperature sensor related to this fan | |
tempToFanMapping.append("Temp %s" % t) | |
if (not hasAtLeastOneRelationship): | |
isSmartFan = False | |
isThermalCruise = False | |
if isSpeedCruise: | |
print(" Mode : Speed Cruise mode") | |
elif isThermalCruise: | |
print(" Mode : Thermal Cruise mode") | |
elif isSmartFan: | |
print(" Mode : Smart Fan mode") | |
else: | |
print(" Mode : Manual mode") | |
pwm = w83795_read(bus, W83795_REG_FOV(f)) | |
print(" Fan Output Value (FOV): %s%%" % round(to_perc(pwm), 2)) | |
if isSmartFan: | |
print(" Temperature to Fan mapping Relationships Register (TFMR): fan linked to : %s" % ",".join(tempToFanMapping)) | |
#not sure if useful | |
#print(" Temperature Source Selection Register (TSS)") | |
#temp = w83795_read(bus, W83795_REG_TSS(f)) | |
print(" Smart Fan Control Table (SFIV)") | |
temp = w83795_read(bus, W83795_REG_SFIV_TEMP(f)) | |
print(''.join([("%dC" % to_degree(v)).rjust(6) for v in temp])) | |
dcpwm = w83795_read(bus, W83795_REG_SFIV_DCPWM(f)) | |
print(''.join([("%d%%" % to_perc(v)).rjust(6) for v in dcpwm])) | |
print('') | |
if isSpeedCruise or isThermalCruise or isSmartFan : | |
fonv = w83795_read(bus, W83795_REG_FONV(f)) | |
print(" Fan Output Nonstop Value (FONV): %d%%" % to_perc(fonv)) | |
fost = w83795_read(bus, W83795_REG_FOST(f)) | |
if (fost != 0): | |
print(" Fan Output Stop Time (FOST):", fost * 0.1, "sec") | |
else: | |
print(" Fan Output Stop Time (FOST): never stop") | |
ctfs = w83795_read(bus, W83795_REG_CTFS(t)) | |
#todo : probably in peci unit | |
print(" Critical Temperature to Full Speed all fan (CTFS): %d" % (ctfs)) | |
if isThermalCruise or isSmartFan: | |
#Hystersis of Temperature | |
ht = w83795_read(bus, W83795_REG_HT(t)) | |
#The critical hystersis might be used to lower a cpu even in manual or speed mode. Documentation is unclear. | |
ht_critical = ht & 0b11110000 | |
ht_operation = ht & 0b00001111 | |
print(" Hystersis of critical temperature: %dC" % ht_critical) | |
print(" hystersis of operation temperature: %dC" % ht_operation) | |
if isThermalCruise : | |
ttti = w83795_read(bus, W83795_REG_TTTI(f)) | |
#ignore the 8th bit as it's reserved | |
ttti = ttti & 0b111111 | |
print(" Target Temperature of Temperature Inputs (TTTI):", ttti, "in PECI unit") | |
print("") | |
#generic parameters | |
#Default Fan Speed at Power-on (DFSP) | |
defpwm = w83795_read(bus, W83795_REG_DFSP) | |
print("Default Fan Speed at Power-on (DFSP): %d%%" % to_perc(defpwm)) | |
#SmartFan Output Step Up Time (SFOSUT) | |
stepuptime = w83795_read(bus, W83795_REG_SFOSDT) | |
print("Fan output step up time: %g sec" % (float(stepuptime) * 0.1)) | |
##SmartFan Output Step Down Time (SFOSDT) | |
stepdowntime = w83795_read(bus, W83795_REG_SFOSDT) | |
print("Fan output step down time: %g sec" % (float(stepdowntime) * 0.1)) | |
def to_degree(val, low=0, hi=127): | |
"""Convert hex value to degree.""" | |
return 127 * val / 255 | |
def to_perc(value): | |
"""Convert hex value to percentage.""" | |
return value * 100 / 255 | |
def from_perc(value): | |
"""Convert perc to hex""" | |
return (int(value * 255 / 100) & 0xff) | |
def from_degree(value): | |
#keven | |
return (int(value * 255 / 127) & 0xff) | |
def WriteSettings(bus): | |
# Read arguments | |
args = sys.argv | |
pwm_value = None | |
if len(args)>1: | |
pwm_value = int(args[1]) | |
# Check if Smarts Fan Control is enabled | |
fcms1 = w83795_read(bus, W83795_REG_FCMS1) | |
fcms2 = w83795_read(bus, W83795_REG_FCMS2) & 0xf | |
#debug("FCMDS1: %s, FCMS2: %s" % (fcms1, fcms2)) | |
if fcms1 !=0 and fcms2 == 0: | |
print("Smart Fan Control is not enabled") | |
return | |
# Extract TEMP with Smart Fan Enabled | |
temps = [i for i in range(0,6) if fcms2 & (0x1<<i)] | |
# Set the registry value | |
if pwm_value: | |
print("Set minimum PWM to %s%%" % pwm_value) | |
# Change Smart Fan Control value | |
for t in temps: | |
w83795_write(bus, W83795_REG_SFIV_DCPWM(t)[0], from_perc(pwm_value)) | |
# Change Minimum PWM | |
for f in range(0,6): | |
w83795_write(bus, W83795_REG_FONV(f), from_perc(pwm_value)) | |
def main(): | |
#Check if we have the right device. | |
try: | |
# Open SMBus | |
try: | |
bus = SMBus(0) | |
except: | |
print("Failed to open i2c bus (/dev/i2d-0). Make sure i2c-dev module is loaded.") | |
return | |
booContinue = CheckChipset(bus) | |
if not booContinue: | |
#if it's not the correct chipset, stop program | |
return | |
#ShowInfo(bus) | |
parser = argparse.ArgumentParser(description="Utility to show and configure settings of the Nuvoton w83793 chipset") | |
parser.add_argument("-s", "--show", help="Show current configuration", action="store_true") | |
parser.add_argument("-f", "--fan", type=int, choices=[1, 2, 3, 4, 5, 6, 7, 8], default = 0, help="Select fan to modify") | |
parser.add_argument("--sfiv", default=[], nargs = '*', help = "Specify a new set of smartfan values. Specify the temperature then the fan speed for all 6 steps.") | |
parser.add_argument("--fcms", choices=['manual','thermal cruise','smart fan']) | |
args = parser.parse_args() | |
if (args.fan > 0 and args.fcms == "thermal cruise"): | |
#get fan configuration | |
fcms1 = w83795_read(bus, W83795_REG_FCMS1) | |
fcms2 = w83795_read(bus, W83795_REG_FCMS2) & 0b00111111 | |
#binary fan value, used for binary and | |
#shift number binary 00000001 f bytes to the left, | |
#00000001 shifted 1 time to the left equals 00000010. | |
fbin = 1 << args.fan-1 | |
inverse = 0xff - fbin | |
fcms1 = fcms1 & inverse | |
print (fcms1) | |
if (args.fan > 0 and len(args.sfiv) == 12): | |
print (args.sfiv) | |
#set to zero based | |
fan = args.fan-1 | |
temp = [] | |
dcpwm = [] | |
#from 0 to 5, step by 2 | |
for index in range(0,11,2): | |
#from degree and to degree is weird. from don't work. | |
temp.append(from_degree(int(args.sfiv[index]))) | |
#from perc does work correclty | |
dcpwm.append(from_perc(int(args.sfiv[index+1]))) | |
print (temp) | |
print (dcpwm) | |
#print(" Smart Fan Control Table (SFIV)") | |
#temp = w83795_read(bus, W83795_REG_SFIV_TEMP(f)) | |
#print(''.join([("%dC" % to_degree(v)).rjust(6) for v in temp])) | |
#dcpwm = w83795_read(bus, W83795_REG_SFIV_DCPWM(f)) | |
#print(''.join([("%d%%" % to_perc(v)).rjust(6) for v in dcpwm])) | |
#print('') | |
if (args.show): | |
ShowInfo(bus) | |
#old stuff from original developper | |
#for t in range(0,6): | |
# ctfs = w83795_read(bus, W83795_REG_CTFS(t)) | |
# print("T%sCTFS: %s" % (t, to_degree(ctfs))) | |
#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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment