Skip to content

Instantly share code, notes, and snippets.

@timemaster67
Forked from ikus060/smx8fancontrol
Last active February 24, 2022 15:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save timemaster67/3d8b703d15e65063c61d194c0d357e1d to your computer and use it in GitHub Desktop.
Save timemaster67/3d8b703d15e65063c61d194c0d357e1d to your computer and use it in GitHub Desktop.
Python script to edit the X8DTL-iF Registry to control fan speed.
#!/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