Files
ReLibreHardwareMonitor/LibreHardwareMonitorLib/Hardware/Motherboard/Lpc/Nct677X.cs
T
namazsoandPhyxionNL eb5e1a20be Swap WinRing0 to PawnIO (#1857)
* Replace WinRing0 with PawnIO

* Update RAMSPDToolkit

* Add install check + cleanup

* Move folder + formatting

* Add back missing TryLoadDll

* Update PawnIo.cs

* Add third party notices file

* Remove the how to apply section

* Drop silent

* Don't crash if PawnIO isn't installed

* Formatting

---------

Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
2025-09-16 11:58:31 +02:00

1197 lines
44 KiB
C#

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Threading;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class Nct677X : ISuperIO
{
private readonly struct TemperatureSourceData
{
public TemperatureSourceData(Enum source, ushort register, ushort halfRegister = 0, int halfBit = -1, ushort sourceRegister = 0, ushort? alternateRegister = null)
{
Source = source;
Register = register;
HalfRegister = halfRegister;
HalfBit = halfBit;
SourceRegister = sourceRegister;
AlternateRegister = alternateRegister;
}
public readonly Enum Source;
public readonly ushort Register;
public readonly ushort HalfRegister;
public readonly int HalfBit;
public readonly ushort SourceRegister;
public readonly ushort? AlternateRegister;
}
private readonly ushort[] _fanCountRegister;
private readonly ushort[] _fanRpmRegister;
private readonly byte[] _initialFanControlMode = new byte[7];
private readonly byte[] _initialFanPwmCommand = new byte[7];
private readonly bool _isNuvotonVendor;
private readonly LpcPort _lpcPort;
private readonly int _maxFanCount;
private readonly int _minFanCount;
private readonly int _minFanRpm;
private readonly ushort _port;
private readonly bool[] _restoreDefaultFanControlRequired = new bool[7];
private readonly byte _revision;
private readonly TemperatureSourceData[] _temperaturesSource;
private readonly ushort _vBatMonitorControlRegister;
private readonly ushort[] _voltageRegisters;
private readonly ushort _voltageVBatRegister;
public Nct677X(LpcPort lpcPort, Chip chip, byte revision, ushort port)
{
Chip = chip;
_revision = revision;
_port = port;
_lpcPort = lpcPort;
if (chip == Chip.NCT610XD)
{
VENDOR_ID_HIGH_REGISTER = 0x80FE;
VENDOR_ID_LOW_REGISTER = 0x00FE;
FAN_PWM_OUT_REG = new ushort[] { 0x04A, 0x04B, 0x04C };
FAN_PWM_COMMAND_REG = new ushort[] { 0x119, 0x129, 0x139 };
FAN_CONTROL_MODE_REG = new ushort[] { 0x113, 0x123, 0x133 };
_vBatMonitorControlRegister = 0x0318;
}
else if (chip is Chip.NCT6683D or Chip.NCT6686D or Chip.NCT6687D) //These work on older NCT6687D boards, but only fans 0, 1 and 3 on newer (X870 and Z890) motherboards. Unsure of controls for "next pack of 8".
{
FAN_PWM_OUT_REG = new ushort[] { 0x160, 0x161, 0x162, 0x163, 0x164, 0x165, 0x166, 0x167 }; // Next 8 fans will be 0xE00, 0xE01, 0xE02, 0xE03, 0xE04, 0xE05, 0xE06, 0xE07
FAN_PWM_COMMAND_REG = new ushort[] { 0xA28, 0xA29, 0xA2A, 0xA2B, 0xA2C, 0xA2D, 0xA2E, 0xA2F }; // Possibly 0X260, 0X261, 0X262, 0X263, 0X264, 0X265, 0X266, 0X267 but can't confirm
FAN_CONTROL_MODE_REG = new ushort[] { 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00 }; // Not sure of next 8, MSI won't provide info
FAN_PWM_REQUEST_REG = new ushort[] { 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01 }; // Not sure of next 8, MSI won't provide info
}
else if (chip is Chip.NCT6687DR) // MSI AM5/LGA1851 Motherboards
{
FAN_PWM_OUT_REG = new ushort[] { 0x160, 0x161, 0xE05, 0xE04, 0xE03, 0xE02, 0xE01, 0xE00 }; // Duty Cycle Sensors
FAN_PWM_COMMAND_REG = new ushort[] { 0xA28, 0xA29, 0xC70, 0xC58, 0xC40, 0xC28, 0xC10, 0xBF8 }; // Control Registers for CPU/Pump, Initial Fan Curve Registers for System Fans
FAN_CONTROL_MODE_REG = new ushort[] { 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00 };
FAN_PWM_REQUEST_REG = new ushort[] { 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01 };
}
else
{
VENDOR_ID_HIGH_REGISTER = 0x804F;
VENDOR_ID_LOW_REGISTER = 0x004F;
FAN_PWM_OUT_REG = chip is Chip.NCT6797D or Chip.NCT6798D or Chip.NCT6799D
? new ushort[] { 0x001, 0x003, 0x011, 0x013, 0x015, 0xA09, 0xB09 }
: new ushort[] { 0x001, 0x003, 0x011, 0x013, 0x015, 0x017, 0x029 };
FAN_PWM_COMMAND_REG = new ushort[] { 0x109, 0x209, 0x309, 0x809, 0x909, 0xA09, 0xB09 };
FAN_CONTROL_MODE_REG = new ushort[] { 0x102, 0x202, 0x302, 0x802, 0x902, 0xA02, 0xB02 };
_vBatMonitorControlRegister = 0x005D;
}
_isNuvotonVendor = IsNuvotonVendor();
if (!_isNuvotonVendor)
return;
switch (chip)
{
case Chip.NCT6771F:
case Chip.NCT6776F:
if (chip == Chip.NCT6771F)
{
Fans = new float?[4];
// min value RPM value with 16-bit fan counter
_minFanRpm = (int)(1.35e6 / 0xFFFF);
}
else
{
Fans = new float?[5];
// min value RPM value with 13-bit fan counter
_minFanRpm = (int)(1.35e6 / 0x1FFF);
}
_fanRpmRegister = new ushort[5];
for (int i = 0; i < _fanRpmRegister.Length; i++)
_fanRpmRegister[i] = (ushort)(0x656 + (i << 1));
Controls = new float?[3];
Voltages = new float?[9];
_voltageRegisters = new ushort[] { 0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x550, 0x551 };
_voltageVBatRegister = 0x551;
_temperaturesSource = new TemperatureSourceData[]
{
new(chip == Chip.NCT6771F ? SourceNct6771F.PECI_0 : SourceNct6776F.PECI_0, 0x027, 0, -1, 0x621),
new(chip == Chip.NCT6771F ? SourceNct6771F.CPUTIN : SourceNct6776F.CPUTIN, 0x073, 0x074, 7, 0x100),
new(chip == Chip.NCT6771F ? SourceNct6771F.AUXTIN : SourceNct6776F.AUXTIN, 0x075, 0x076, 7, 0x200),
new(chip == Chip.NCT6771F ? SourceNct6771F.SYSTIN : SourceNct6776F.SYSTIN, 0x077, 0x078, 7, 0x300),
new(null, 0x150, 0x151, 7, 0x622),
new(null, 0x250, 0x251, 7, 0x623),
new(null, 0x62B, 0x62E, 0, 0x624),
new(null, 0x62C, 0x62E, 1, 0x625),
new(null, 0x62D, 0x62E, 2, 0x626)
};
Temperatures = new float?[4];
break;
case Chip.NCT6779D: // 15 voltages
case Chip.NCT6791D: // 15 voltages
case Chip.NCT6792D:
case Chip.NCT6792DA:
case Chip.NCT6793D: // 14 voltages
case Chip.NCT6795D:
case Chip.NCT6796D: // 16 voltages
case Chip.NCT6796DR: // 16 voltages
case Chip.NCT6797D:
case Chip.NCT6798D:
case Chip.NCT6799D:
case Chip.NCT6701D:
switch (chip)
{
case Chip.NCT6779D:
Fans = new float?[5];
Controls = new float?[5];
break;
case Chip.NCT6796DR:
case Chip.NCT6797D:
case Chip.NCT6798D:
case Chip.NCT6799D:
case Chip.NCT6701D:
Fans = new float?[7];
Controls = new float?[7];
break;
default:
Fans = new float?[6];
Controls = new float?[6];
break;
}
_fanCountRegister = new ushort[] { 0x4B0, 0x4B2, 0x4B4, 0x4B6, 0x4B8, 0x4BA, 0x4CC };
// max value for 13-bit fan counter
_maxFanCount = 0x1FFF;
// min value that could be transferred to 16-bit RPM registers
_minFanCount = 0x15;
Voltages = new float?[16];
_voltageRegisters = new ushort[] { 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, 0x488, 0x489, 0x48A, 0x48B, 0x48C, 0x48D, 0x48E, 0x48F };
_voltageVBatRegister = 0x488;
var temperaturesSources = new List<TemperatureSourceData>();
switch (chip)
{
case Chip.NCT6796D:
case Chip.NCT6796DR:
case Chip.NCT6797D:
case Chip.NCT6798D:
case Chip.NCT6799D:
case Chip.NCT6701D:
temperaturesSources.AddRange(new TemperatureSourceData[]
{
new(SourceNct67Xxd.PECI_0, 0x073, 0x074, 7, 0x100),
new(SourceNct67Xxd.CPUTIN, 0x075, 0x076, 7, 0x200, 0x491),
new(SourceNct67Xxd.SYSTIN, 0x077, 0x078, 7, 0x300, 0x490),
new(SourceNct67Xxd.AUXTIN0, 0x079, 0x07A, 7, 0x800, 0x492),
new(SourceNct67Xxd.AUXTIN1, 0x07B, 0x07C, 7, 0x900, 0x493),
new(SourceNct67Xxd.AUXTIN2, 0x07D, 0x07E, 7, 0xA00, 0x494),
new(SourceNct67Xxd.AUXTIN3, 0x4A0, 0x49E, 6, 0xB00, 0x495),
new(SourceNct67Xxd.AUXTIN4, 0x027, 0, -1, 0x621),
new(SourceNct67Xxd.TSENSOR, 0x4A2, 0x4A1, 7, 0xC00, 0x496),
new(SourceNct67Xxd.SMBUSMASTER0, 0x150, 0x151, 7, 0x622),
new(SourceNct67Xxd.SMBUSMASTER1, 0x670, 0, -1, 0xC26),
new(SourceNct67Xxd.PECI_1, 0x672, 0, -1, 0xC27),
new(SourceNct67Xxd.PCH_CHIP_CPU_MAX_TEMP, 0x674, 0, -1, 0xC28, 0x400),
new(SourceNct67Xxd.PCH_CHIP_TEMP, 0x676, 0, -1, 0xC29, 0x401),
new(SourceNct67Xxd.PCH_CPU_TEMP, 0x678, 0, -1, 0xC2A, 0x402),
new(SourceNct67Xxd.PCH_MCH_TEMP, 0x67A, 0, -1, 0xC2B, 0x404),
new(SourceNct67Xxd.AGENT0_DIMM0, 0),
new(SourceNct67Xxd.AGENT0_DIMM1, 0),
new(SourceNct67Xxd.AGENT1_DIMM0, 0),
new(SourceNct67Xxd.AGENT1_DIMM1, 0),
new(SourceNct67Xxd.BYTE_TEMP0, 0),
new(SourceNct67Xxd.BYTE_TEMP1, 0),
new(SourceNct67Xxd.PECI_0_CAL, 0),
new(SourceNct67Xxd.PECI_1_CAL, 0),
new(SourceNct67Xxd.VIRTUAL_TEMP, 0)
});
break;
default:
temperaturesSources.AddRange(new TemperatureSourceData[]
{
new(SourceNct67Xxd.PECI_0, 0x027, 0, -1, 0x621),
new(SourceNct67Xxd.CPUTIN, 0x073, 0x074, 7, 0x100, 0x491),
new(SourceNct67Xxd.SYSTIN, 0x075, 0x076, 7, 0x200, 0x490),
new(SourceNct67Xxd.AUXTIN0, 0x077, 0x078, 7, 0x300, 0x492),
new(SourceNct67Xxd.AUXTIN1, 0x079, 0x07A, 7, 0x800, 0x493),
new(SourceNct67Xxd.AUXTIN2, 0x07B, 0x07C, 7, 0x900, 0x494),
new(SourceNct67Xxd.AUXTIN3, 0x150, 0x151, 7, 0x622, 0x495)
});
break;
}
_temperaturesSource = temperaturesSources.ToArray();
Temperatures = new float?[_temperaturesSource.Length];
break;
case Chip.NCT610XD:
Fans = new float?[3];
Controls = new float?[3];
_fanRpmRegister = new ushort[3];
for (int i = 0; i < _fanRpmRegister.Length; i++)
_fanRpmRegister[i] = (ushort)(0x030 + (i << 1));
// min value RPM value with 13-bit fan counter
_minFanRpm = (int)(1.35e6 / 0x1FFF);
Voltages = new float?[9];
_voltageRegisters = new ushort[] { 0x300, 0x301, 0x302, 0x303, 0x304, 0x305, 0x307, 0x308, 0x309 };
_voltageVBatRegister = 0x308;
Temperatures = new float?[7];
_temperaturesSource = new TemperatureSourceData[] {
new(SourceNct610X.PECI_0, 0x06b, 0, -1, 0x621),
new(SourceNct610X.AUXTIN, 0x010, 0x016, 0),
new(SourceNct610X.CPUTIN, 0x011, 0x01B, 1),
new(SourceNct610X.SYSTIN0, 0x012, 0x01B, 2),
new(SourceNct610X.SYSTIN1, 0x013, 0x016, 3),
new(SourceNct610X.SYSTIN2, 0x014, 0x01B, 4),
new(SourceNct610X.SYSTIN3, 0x015, 0x01B, 5)
};
break;
case Chip.NCT6683D:
case Chip.NCT6686D:
case Chip.NCT6687D:
Fans = new float?[16];
Controls = new float?[8];
Voltages = new float?[14];
Temperatures = new float?[7];
// CPU
// System
// MOS
// PCH
// CPU Socket
// PCIE_1
// M2_1
_temperaturesSource = new TemperatureSourceData[] {
new(null, 0x100),
new(null, 0x102),
new(null, 0x104),
new(null, 0x106),
new(null, 0x108),
new(null, 0x10A),
new(null, 0x10C)
};
// VIN0 +12V
// VIN1 +5V
// VIN2 VCore
// VIN3 SIO
// VIN4 DRAM
// VIN5 CPU IO
// VIN6 CPU SA
// VIN7 SIO
// 3VCC I/O +3.3
// SIO VTT
// SIO VREF
// SIO VSB
// SIO AVSB
// SIO VBAT
_voltageRegisters = new ushort[] { 0x120, 0x122, 0x124, 0x126, 0x128, 0x12A, 0x12C, 0x12E, 0x130, 0x13A, 0x13E, 0x136, 0x138, 0x13C };
// CPU Fan
// PUMP Fan
// SYS Fan 1 on some older NCT6687Ds, Nil on others
// SYS Fan 2 on some older NCT6687Ds, EZConn on others
// SYS Fan 3 on some older NCT6687Ds
// SYS Fan 4 on some older NCT6687Ds
// SYS Fan 5 on some older NCT6687Ds
// SYS Fan 6 on some older NCT6687Ds
// SYS Fan 6 on newer NCT6687Ds
// SYS Fan 5 on newer NCT6687Ds
// SYS Fan 4 on newer NCT6687Ds
// SYS Fan 3 on newer NCT6687Ds
// SYS Fan 2 on newer NCT6687Ds
// SYS Fan 1 on newer NCT6687Ds
_fanRpmRegister = new ushort[] { 0x140, 0x142, 0x144, 0x146, 0x148, 0x14A, 0x14C, 0x14E, 0x150, 0x152, 0x154, 0x156, 0x158, 0x15A, 0x15C, 0x15E };
_restoreDefaultFanControlRequired = new bool[_fanRpmRegister.Length];
_initialFanControlMode = new byte[_fanRpmRegister.Length];
_initialFanPwmCommand = new byte[_fanRpmRegister.Length];
// initialize
const ushort initRegister = 0x180;
byte data = ReadByte(initRegister);
if ((data & 0x80) == 0)
{
WriteByte(initRegister, (byte)(data | 0x80));
}
// enable SIO voltage
WriteByte(0x1BB, 0x61);
WriteByte(0x1BC, 0x62);
WriteByte(0x1BD, 0x63);
WriteByte(0x1BE, 0x64);
WriteByte(0x1BF, 0x65);
break;
case Chip.NCT6687DR:
Fans = new float?[8];
Controls = new float?[8];
Voltages = new float?[14];
Temperatures = new float?[7];
_temperaturesSource = new TemperatureSourceData[] {
new(null, 0x100), // CPU
new(null, 0x102), // System
new(null, 0x104), // MOS
new(null, 0x106), // PCH
new(null, 0x108), // CPU Socket
new(null, 0x10A), // PCIE_1
new(null, 0x10C) // M2_1
};
// VIN0 +12V
// VIN1 +5V
// VIN2 VCore
// VIN3 SIO
// VIN4 DRAM
// VIN5 CPU IO
// VIN6 CPU SA
// VIN7 SIO
// 3VCC I/O +3.3
// SIO VTT
// SIO VREF
// SIO VSB
// SIO AVSB
// SIO VBAT
_voltageRegisters = new ushort[] { 0x120, 0x122, 0x124, 0x126, 0x128, 0x12A, 0x12C, 0x12E, 0x130, 0x13A, 0x13E, 0x136, 0x138, 0x13C };
// CPU Fan 0x140
// PUMP Fan 0x142
// SYS Fan 1 0x15E
// SYS Fan 2 0x15C
// SYS Fan 3 0x15A
// SYS Fan 4 0x158
// SYS Fan 5 0x156
// SYS Fan 6 0x154
_fanRpmRegister = new ushort[] { 0x140, 0x142, 0x15E, 0x15C, 0x15A, 0x158, 0x156, 0x154 };
_restoreDefaultFanControlRequired = new bool[_fanRpmRegister.Length];
_initialFanControlMode = new byte[_fanRpmRegister.Length];
_initialFanPwmCommand = new byte[_fanRpmRegister.Length];
// initialize
const ushort initRegisterNct6687DR = 0x180;
byte dataNct6687DR = ReadByte(initRegisterNct6687DR);
if ((dataNct6687DR & 0x80) == 0)
{
WriteByte(initRegisterNct6687DR, (byte)(dataNct6687DR | 0x80));
}
// enable SIO voltage -- address, value
WriteByte(0x1BB, 0x61);
WriteByte(0x1BC, 0x62);
WriteByte(0x1BD, 0x63);
WriteByte(0x1BE, 0x64);
WriteByte(0x1BF, 0x65);
break;
}
}
public Chip Chip { get; }
public float?[] Controls { get; } = Array.Empty<float?>();
public float?[] Fans { get; } = Array.Empty<float?>();
public float?[] Temperatures { get; } = Array.Empty<float?>();
public float?[] Voltages { get; } = Array.Empty<float?>();
public byte? ReadGpio(int index)
{
return null;
}
public void WriteGpio(int index, byte value)
{ }
public void SetControl(int index, byte? value)
{
if (!_isNuvotonVendor)
return;
if (index < 0 || index >= Controls.Length)
throw new ArgumentOutOfRangeException(nameof(index));
if (!Mutexes.WaitIsaBus(10))
return;
if (value.HasValue)
{
SaveDefaultFanControl(index);
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
// set manual mode
WriteByte(FAN_CONTROL_MODE_REG[index], 0);
// set output value
WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);
}
else
{
// Manual mode, bit(1 : set, 0 : unset)
// bit 0 : CPU Fan
// bit 1 : PUMP Fan
// bit 2 : SYS Fan 1
// bit 3 : SYS Fan 2
// bit 4 : SYS Fan 3
// bit 5 : SYS Fan 4
// bit 6 : SYS Fan 5
// bit 7 : SYS Fan 6
byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
byte bitMask = (byte)(0x01 << index);
mode = (byte)(mode | bitMask);
WriteByte(FAN_CONTROL_MODE_REG[index], mode);
WriteByte(FAN_PWM_REQUEST_REG[index], 0x80);
Thread.Sleep(50);
if (Chip is Chip.NCT6687DR) // For MSI AM5/LGA1851 NCT6687D functionality
{
Set6687DRControl(index, value.Value);
}
else // All other Nuvoton SIO controllers and motherboards that use NCT6683/6686/6687
{
WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);
}
WriteByte(FAN_PWM_REQUEST_REG[index], 0x40);
Thread.Sleep(50);
}
}
else
{
RestoreDefaultFanControl(index);
}
Mutexes.ReleaseIsaBus();
}
public void Update()
{
if (!_isNuvotonVendor)
return;
if (!Mutexes.WaitIsaBus(10))
return;
DisableIOSpaceLock();
for (int i = 0; i < Voltages.Length; i++)
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
float value = 0.008f * ReadByte(_voltageRegisters[i]);
bool valid = value > 0;
// check if battery voltage monitor is enabled
if (valid && _voltageRegisters[i] == _voltageVBatRegister)
valid = (ReadByte(_vBatMonitorControlRegister) & 0x01) > 0;
Voltages[i] = valid ? value : null;
}
else
{
float value = 0.001f * ((16 * ReadByte(_voltageRegisters[i])) + (ReadByte((ushort)(_voltageRegisters[i] + 1)) >> 4));
Voltages[i] = i switch
{
// 12V
0 => value * 12.0f,
// 5V
1 => value * 5.0f,
// DRAM
4 => value * 2.0f,
_ => value
};
}
}
System.Diagnostics.Debug.WriteLine("Updating temperatures.");
long temperatureSourceMask = 0;
for (int i = 0; i < _temperaturesSource.Length; i++)
{
TemperatureSourceData ts = _temperaturesSource[i];
int value;
SourceNct67Xxd source;
float? temperature;
switch (Chip)
{
case Chip.NCT610XD:
value = (sbyte)ReadByte(ts.Register);
int half = (ReadByte((ushort)(ts.HalfRegister)) >> ts.HalfBit) & 0x1;
temperature = value + (0.5f * half);
Temperatures[i] = temperature;
break;
case Chip.NCT6687D:
case Chip.NCT6687DR:
case Chip.NCT6686D:
case Chip.NCT6683D:
value = (sbyte)ReadByte(ts.Register);
half = (ReadByte((ushort)(ts.Register + 1)) >> 7) & 0x1;
Temperatures[i] = value + (0.5f * half);
break;
case Chip.NCT6796D:
case Chip.NCT6796DR:
case Chip.NCT6797D:
case Chip.NCT6798D:
case Chip.NCT6799D:
if (_temperaturesSource[i].Register == 0)
{
System.Diagnostics.Debug.WriteLine("Temperature register {0} skipped, address 0.", i);
continue;
}
value = (sbyte)ReadByte(_temperaturesSource[i].Register) << 1;
System.Diagnostics.Debug.WriteLine("Temperature register {0} at 0x{1:X3} value (integer): {2}/2", i, ts.Register, value);
if (_temperaturesSource[i].HalfBit > 0)
{
value |= (ReadByte(_temperaturesSource[i].HalfRegister) >> ts.HalfBit) & 0x1;
System.Diagnostics.Debug.WriteLine("Temperature register {0} value updated from 0x{1:X3} (fractional): {2}/2", i, ts.HalfRegister, value);
}
if (ts.SourceRegister > 0)
{
source = (SourceNct67Xxd)(ReadByte(ts.SourceRegister) & 0x1F);
System.Diagnostics.Debug.WriteLine("Temperature register {0} source at 0x{1:X3}: {2:G} ({2:D})", i, ts.SourceRegister, source);
}
else
{
source = (SourceNct67Xxd)ts.Source;
System.Diagnostics.Debug.WriteLine("Temperature register {0} source register is 0, source set to: {1:G} ({1:D})", i, source);
}
// Skip reading when already filled, because later values are without fractional
if ((temperatureSourceMask & (1L << (byte)source)) > 0)
{
System.Diagnostics.Debug.WriteLine("Temperature register {0} discarded, because source seen before.", i);
continue;
}
temperature = 0.5f * value;
System.Diagnostics.Debug.WriteLine("Temperature register {0} final temperature: {1}.", i, temperature);
if (temperature is > 125 or < -55)
{
temperature = null;
System.Diagnostics.Debug.WriteLine("Temperature register {0} discarded: Out of range.", i);
}
else
{
temperatureSourceMask |= 1L << (byte)source;
System.Diagnostics.Debug.WriteLine("Temperature register {0} accepted.", i);
}
for (int j = 0; j < Temperatures.Length; j++)
{
if ((SourceNct67Xxd)_temperaturesSource[j].Source == source)
{
Temperatures[j] = temperature;
System.Diagnostics.Debug.WriteLine("Temperature register {0}, value from source {1:G} ({1:D}), written at position {2}.", i, _temperaturesSource[j].Source, j);
}
}
break;
default:
value = (sbyte)ReadByte(ts.Register) << 1;
if (ts.HalfBit > 0)
{
value |= (ReadByte(ts.HalfRegister) >> ts.HalfBit) & 0x1;
}
source = (SourceNct67Xxd)ReadByte(ts.SourceRegister);
temperatureSourceMask |= 1L << (byte)source;
temperature = 0.5f * value;
if (temperature is > 125 or < -55)
temperature = null;
for (int j = 0; j < Temperatures.Length; j++)
{
if ((SourceNct67Xxd)_temperaturesSource[j].Source == source)
Temperatures[j] = temperature;
}
break;
}
}
for (int i = 0; i < _temperaturesSource.Length; i++)
{
TemperatureSourceData ts = _temperaturesSource[i];
if (!ts.AlternateRegister.HasValue)
{
System.Diagnostics.Debug.WriteLine("Alternate temperature register for temperature {0}, {1:G} ({1:D}), skipped, because address is null.", i, ts.Source);
continue;
}
if ((temperatureSourceMask & (1L << (byte)(SourceNct67Xxd)ts.Source)) > 0)
{
System.Diagnostics.Debug.WriteLine("Alternate temperature register for temperature {0}, {1:G} ({1:D}), at 0x{2:X3} skipped, because value already set.", i, ts.Source, ts.AlternateRegister.Value);
continue;
}
float? temperature = (sbyte)ReadByte(ts.AlternateRegister.Value);
System.Diagnostics.Debug.WriteLine("Alternate temperature register for temperature {0}, {1:G} ({1:D}), at 0x{2:X3} final temperature: {3}.", i, ts.Source, ts.AlternateRegister.Value, temperature);
if (temperature is > 125 or <= 0)
{
temperature = null;
System.Diagnostics.Debug.WriteLine("Alternate Temperature register for temperature {0}, {1:G} ({1:D}), discarded: Out of range.", i, ts.Source);
}
Temperatures[i] = temperature;
}
for (int i = 0; i < Fans.Length; i++)
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
if (_fanCountRegister != null)
{
byte high = ReadByte(_fanCountRegister[i]);
byte low = ReadByte((ushort)(_fanCountRegister[i] + 1));
int count = (high << 5) | (low & 0x1F);
if (count < _maxFanCount)
{
if (count >= _minFanCount)
{
Fans[i] = 1.35e6f / count;
}
else
{
Fans[i] = null;
}
}
else
{
Fans[i] = 0;
}
}
else
{
byte high = ReadByte(_fanRpmRegister[i]);
byte low = ReadByte((ushort)(_fanRpmRegister[i] + 1));
int value = (high << 8) | low;
Fans[i] = value > _minFanRpm ? value : 0;
}
}
else
{
Fans[i] = (ReadByte(_fanRpmRegister[i]) << 8) | ReadByte((ushort)(_fanRpmRegister[i] + 1));
}
}
for (int i = 0; i < Controls.Length; i++)
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
int value = ReadByte(FAN_PWM_OUT_REG[i]);
Controls[i] = value / 2.55f;
}
else
{
int value = ReadByte(FAN_PWM_OUT_REG[i]);
Controls[i] = (float)Math.Round(value / 2.55f);
}
}
Mutexes.ReleaseIsaBus();
}
/// <inheritdoc />
public void Close()
{
_lpcPort.Close();
}
public string GetReport()
{
StringBuilder r = new();
r.AppendLine("LPC " + GetType().Name);
r.AppendLine();
r.Append("Chip Id: 0x");
r.AppendLine(Chip.ToString("X"));
r.Append("Chip Revision: 0x");
r.AppendLine(_revision.ToString("X", CultureInfo.InvariantCulture));
r.Append("Base Address: 0x");
r.AppendLine(_port.ToString("X4", CultureInfo.InvariantCulture));
r.AppendLine();
if (!Mutexes.WaitIsaBus(100))
return r.ToString();
ushort[] addresses =
{
0x000,
0x010,
0x020,
0x030,
0x040,
0x050,
0x060,
0x070,
0x0F0,
0x100,
0x110,
0x120,
0x130,
0x140,
0x150,
0x200,
0x210,
0x220,
0x230,
0x240,
0x250,
0x260,
0x300,
0x320,
0x330,
0x340,
0x360,
0x400,
0x410,
0x420,
0x440,
0x450,
0x460,
0x480,
0x490,
0x4B0,
0x4C0,
0x4F0,
0x500,
0x550,
0x560,
0x600,
0x610,
0x620,
0x630,
0x640,
0x650,
0x660,
0x670,
0x700,
0x710,
0x720,
0x730,
0x800,
0x820,
0x830,
0x840,
0x900,
0x920,
0x930,
0x940,
0x960,
0xA00,
0xA10,
0xA20,
0xA30,
0xA40,
0xA50,
0xA60,
0xA70,
0xB00,
0xB10,
0xB20,
0xB30,
0xB50,
0xB60,
0xB70,
0xC00,
0xC10,
0xC20,
0xC30,
0xC50,
0xC60,
0xC70,
0xD00,
0xD10,
0xD20,
0xD30,
0xD50,
0xD60,
0xE00,
0xE10,
0xE20,
0xE30,
0xF00,
0xF10,
0xF20,
0xF30,
0x8040,
0x80F0
};
r.AppendLine("Hardware Monitor Registers");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
foreach (ushort address in addresses)
{
r.Append(" ");
r.Append(address.ToString("X4", CultureInfo.InvariantCulture));
r.Append(" ");
for (ushort j = 0; j <= 0xF; j++)
{
r.Append(" ");
r.Append(ReadByte((ushort)(address | j)).ToString("X2", CultureInfo.InvariantCulture));
}
r.AppendLine();
}
}
else
{
for (int i = 0; i <= 0xFF; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X4", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
ushort address = (ushort)(i << 4 | j);
r.Append(" ");
r.Append(ReadByte(address).ToString("X2", CultureInfo.InvariantCulture));
}
r.AppendLine();
}
}
r.AppendLine();
Mutexes.ReleaseIsaBus();
return r.ToString();
}
private byte ReadByte(ushort address)
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
byte bank = (byte)(address >> 8);
byte register = (byte)(address & 0xFF);
_lpcPort.WriteIoPort((ushort)(_port + ADDRESS_REGISTER_OFFSET), BANK_SELECT_REGISTER);
_lpcPort.WriteIoPort((ushort)(_port + DATA_REGISTER_OFFSET), bank);
_lpcPort.WriteIoPort((ushort)(_port + ADDRESS_REGISTER_OFFSET), register);
return _lpcPort.ReadIoPort((ushort)(_port + DATA_REGISTER_OFFSET));
}
byte page = (byte)(address >> 8);
byte index = (byte)(address & 0xFF);
//wait for access, access == EC_SPACE_PAGE_SELECT
//timeout: after 500ms, abort and force access
byte access;
DateTime timeout = DateTime.UtcNow.AddMilliseconds(500);
while (true)
{
access = _lpcPort.ReadIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET));
if (access == EC_SPACE_PAGE_SELECT || DateTime.UtcNow > timeout)
break;
Thread.Sleep(1);
}
if (access != EC_SPACE_PAGE_SELECT)
{
// Failed to gain access: force register access
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET), EC_SPACE_PAGE_SELECT);
}
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET), page);
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_INDEX_REGISTER_OFFSET), index);
byte result = _lpcPort.ReadIoPort((ushort)(_port + EC_SPACE_DATA_REGISTER_OFFSET));
//free access for other instances
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET), EC_SPACE_PAGE_SELECT);
return result;
}
private void WriteByte(ushort address, byte value)
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
byte bank = (byte)(address >> 8);
byte register = (byte)(address & 0xFF);
_lpcPort.WriteIoPort((ushort)(_port + ADDRESS_REGISTER_OFFSET), BANK_SELECT_REGISTER);
_lpcPort.WriteIoPort((ushort)(_port + DATA_REGISTER_OFFSET), bank);
_lpcPort.WriteIoPort((ushort)(_port + ADDRESS_REGISTER_OFFSET), register);
_lpcPort.WriteIoPort((ushort)(_port + DATA_REGISTER_OFFSET), value);
}
else
{
byte page = (byte)(address >> 8);
byte index = (byte)(address & 0xFF);
//wait for access, access == EC_SPACE_PAGE_SELECT
//timeout: after 500ms, abort and force access
byte access;
DateTime timeout = DateTime.UtcNow.AddMilliseconds(500);
while (true)
{
access = _lpcPort.ReadIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET));
if (access == EC_SPACE_PAGE_SELECT || DateTime.UtcNow > timeout)
break;
System.Threading.Thread.Sleep(1);
}
if (access != EC_SPACE_PAGE_SELECT)
{
// Failed to gain access: force register access
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET), EC_SPACE_PAGE_SELECT);
}
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET), page);
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_INDEX_REGISTER_OFFSET), index);
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_DATA_REGISTER_OFFSET), value);
//free access for other instances
_lpcPort.WriteIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET), EC_SPACE_PAGE_SELECT);
}
}
private bool IsNuvotonVendor()
{
return Chip is Chip.NCT6683D or Chip.NCT6686D or Chip.NCT6687D or Chip.NCT6687DR or Chip.NCT6701D || ((ReadByte(VENDOR_ID_HIGH_REGISTER) << 8) | ReadByte(VENDOR_ID_LOW_REGISTER)) == NUVOTON_VENDOR_ID;
}
private void Set6687DRControl(int index, byte? value)
{
if (index > 1) // System Fan Control
{
int initFanCurveReg = FAN_PWM_COMMAND_REG[index]; // Initial Register Address for the Fan Curve
int targetFanCurveAddr = initFanCurveReg; // Address of the Current Fan Curve Register we're writing to
ushort targetFanCurveReg; // Integer value of the current fan curve register address, not the value within
byte currentSpeed = ReadByte(FAN_PWM_OUT_REG[index]); // Current Speed of the target fan
// If current fan duty cycle matches requested duty cycle, skip re-writing the fan curve
if (currentSpeed == value.Value)
{
return;
}
else
{
// Write 7-point fan curve
for (int count = 0; count < 14; count += 2)
{
targetFanCurveAddr = initFanCurveReg+count;
targetFanCurveReg = Convert.ToUInt16(targetFanCurveAddr);
WriteByte(targetFanCurveReg, value.Value);
}
}
}
else // Control CPU and Pump Fan normally
{
WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);
}
}
private void SaveDefaultFanControl(int index)
{
if (!_restoreDefaultFanControlRequired[index])
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
_initialFanControlMode[index] = ReadByte(FAN_CONTROL_MODE_REG[index]);
}
else
{
byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
byte bitMask = (byte)(0x01 << index);
_initialFanControlMode[index] = (byte)(mode & bitMask);
}
_initialFanPwmCommand[index] = ReadByte(FAN_PWM_COMMAND_REG[index]);
_restoreDefaultFanControlRequired[index] = true;
}
}
private void RestoreDefaultFanControl(int index)
{
if (_restoreDefaultFanControlRequired[index])
{
if (Chip is not Chip.NCT6683D and not Chip.NCT6686D and not Chip.NCT6687D and not Chip.NCT6687DR)
{
WriteByte(FAN_CONTROL_MODE_REG[index], _initialFanControlMode[index]);
WriteByte(FAN_PWM_COMMAND_REG[index], _initialFanPwmCommand[index]);
}
else
{
byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
mode = (byte)(mode & ~_initialFanControlMode[index]);
WriteByte(FAN_CONTROL_MODE_REG[index], mode);
WriteByte(FAN_PWM_REQUEST_REG[index], 0x80);
Thread.Sleep(50);
if (Chip is Chip.NCT6687DR) { // for MSI AM5/LGA1851 boards using NCT6687D
Set6687DRControl(index, _initialFanPwmCommand[index]);
}
else { // All other motherboards that use NCT6683/6686/6687
WriteByte(FAN_PWM_COMMAND_REG[index], _initialFanPwmCommand[index]);
}
WriteByte(FAN_PWM_REQUEST_REG[index], 0x40);
Thread.Sleep(50);
}
_restoreDefaultFanControlRequired[index] = false;
}
}
private void DisableIOSpaceLock()
{
if (Chip is not Chip.NCT6791D and
not Chip.NCT6792D and
not Chip.NCT6792DA and
not Chip.NCT6793D and
not Chip.NCT6795D and
not Chip.NCT6796D and
not Chip.NCT6796DR and
not Chip.NCT6797D and
not Chip.NCT6798D and
not Chip.NCT6799D)
{
return;
}
// the lock is disabled already if the vendor ID can be read
if (IsNuvotonVendor())
return;
_lpcPort.WinbondNuvotonFintekEnter();
_lpcPort.NuvotonDisableIOSpaceLock();
_lpcPort.WinbondNuvotonFintekExit();
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
private enum SourceNct6771F : byte
{
SYSTIN = 1,
CPUTIN = 2,
AUXTIN = 3,
PECI_0 = 5
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
private enum SourceNct6776F : byte
{
SYSTIN = 1,
CPUTIN = 2,
AUXTIN = 3,
PECI_0 = 12
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
private enum SourceNct67Xxd : byte
{
SYSTIN = 1,
CPUTIN = 2,
AUXTIN0 = 3,
AUXTIN1 = 4,
AUXTIN2 = 5,
AUXTIN3 = 6,
AUXTIN4 = 7,
SMBUSMASTER0 = 8,
SMBUSMASTER1 = 9,
TSENSOR = 10,
PECI_0 = 16,
PECI_1 = 17,
PCH_CHIP_CPU_MAX_TEMP = 18,
PCH_CHIP_TEMP = 19,
PCH_CPU_TEMP = 20,
PCH_MCH_TEMP = 21,
AGENT0_DIMM0 = 22,
AGENT0_DIMM1 = 23,
AGENT1_DIMM0 = 24,
AGENT1_DIMM1 = 25,
BYTE_TEMP0 = 26,
BYTE_TEMP1 = 27,
PECI_0_CAL = 28,
PECI_1_CAL = 29,
VIRTUAL_TEMP = 31
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
private enum SourceNct610X : byte
{
CPUTIN = 1,
AUXTIN = 2,
SYSTIN0 = 3,
SYSTIN1 = 4,
SYSTIN2 = 5,
SYSTIN3 = 6,
PECI_0 = 12
}
// ReSharper disable InconsistentNaming
private const ushort ADDRESS_REGISTER_OFFSET = 0x05;
private const byte BANK_SELECT_REGISTER = 0x4E;
private const uint DATA_REGISTER_OFFSET = 0x06;
// NCT668X
private const uint EC_SPACE_PAGE_REGISTER_OFFSET = 0x04;
private const uint EC_SPACE_INDEX_REGISTER_OFFSET = 0x05;
private const uint EC_SPACE_DATA_REGISTER_OFFSET = 0x06;
private const byte EC_SPACE_PAGE_SELECT = 0xFF;
private const ushort NUVOTON_VENDOR_ID = 0x5CA3;
private readonly ushort[] FAN_CONTROL_MODE_REG;
private readonly ushort[] FAN_PWM_COMMAND_REG;
private readonly ushort[] FAN_PWM_OUT_REG;
private readonly ushort[] FAN_PWM_REQUEST_REG;
private readonly ushort VENDOR_ID_HIGH_REGISTER;
private readonly ushort VENDOR_ID_LOW_REGISTER;
// ReSharper restore InconsistentNaming
}