* Add NCT6687DR fan control support with bit mapping and configuration phase handling * NCT6687DR: emulate CC_Engine SetManualFAN protocol - System fans (index > 8): skip manual mode bit (0xA00), use flat SmartFAN curve instead - Change commit value from 0x40 to 0xC0 (REQ|DONE) matching CC_Engine - Write 7-point temp curve (all 0x20/32C) alongside duty curve for system fans - Add Sleep(10ms) after request (0x80) and commit (0xC0) per CC_Engine timing - Restore: only clear manual mode bit for CPU/Pump/Chipset/EZ-Connect fans * NCT6687DR: add INVALID bit check with retry after EC commit - FinishFanCfgUpdate now returns bool: false if EC sets INVALID (0xCF8 & 0x10) - SetControl and RestoreDefaultFanControl retry up to 3 times on INVALID - Matches CC_Engine's post-commit validation behavior * fix(NCT6687DR): bypass SmartFAN engine for system fans — use direct PWM System fans (SYSFAN1-7) now use the same direct PWM mechanism as CPU/Pump/Chipset fans, completely bypassing the SmartFAN curve engine and its inherent ~2%/sec PWM smoothing. Key changes: - FAN_PWM_COMMAND_REG: system fans now point to direct PWM registers (SYSFAN7: 08:E9, SYSFAN1-6: 02:65..02:60) instead of SmartFAN curve base addresses (0xBE0..0xC70) - FAN_CONTROL_MODE_REG: system fans use 08:0F (not 0xA00) for their manual-mode enable bits - FAN_CONTROL_MODE_BIT: corrected per-channel bit positions derived from BIOS unk_104C0 table (sub_2F50 / sub_2BDC analysis) - Set6687DRControl: simplified to single WriteByte for all fans (no more 7-point flat curve + step/rate writes) - SetControl: unified path — all fans set manual-mode bit + direct PWM - RestoreDefaultFanControl: clear manual-mode bit to return to SmartFAN Register mapping discovered via IDA analysis of BIOS firmware: - Extended unk_101E0 table at 0x103AC contains channels 10-15 - sub_2BDC configures 5 register operations per channel before curve write - Entry[1] in unk_104C0 = manual-mode enable register + bit position * review: Stopwatch, engineSts rename, CompleteFanConfigUpdate * refactor: PreciseWait via SpinWait, DateTime to Stopwatch in ReadByte/WriteByte * review: remove regions, revert PreciseWait to Thread.Sleep
1552 lines
62 KiB
C#
1552 lines
62 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.
|
|
|
|
//#define NCT677X_DEBUG_LOG
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
|
|
|
|
internal class Nct677X : ISuperIO
|
|
{
|
|
// 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 EC space
|
|
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;
|
|
|
|
// NCT6687DR EC engine status register and flags (based on Linux nct6687d driver)
|
|
private const ushort NCT6687DR_REG_FAN_ENGINE_STS = 0xCF8;
|
|
private const byte NCT6687DR_FAN_CFG_LOCK = 1 << 6;
|
|
private const byte NCT6687DR_FAN_CFG_PHASE = 1 << 3;
|
|
private const byte NCT6687DR_FAN_CFG_INVALID = 1 << 4;
|
|
private const byte NCT6687DR_FAN_CFG_CHECK_DONE = 1 << 5;
|
|
private const byte NCT6687DR_FAN_CFG_REQ = 0x80;
|
|
private const byte NCT6687DR_FAN_CFG_DONE = 0x40;
|
|
// ReSharper restore InconsistentNaming
|
|
|
|
// Chip identity
|
|
private readonly LpcPort _lpcPort;
|
|
private readonly ushort _port;
|
|
private readonly byte _revision;
|
|
private readonly bool _isNuvotonVendor;
|
|
|
|
// Fan registers (chip-specific, initialized in constructor)
|
|
// ReSharper disable InconsistentNaming
|
|
private readonly ushort[] FAN_CONTROL_MODE_REG;
|
|
private readonly int[] FAN_CONTROL_MODE_BIT; // NCT6687DR only: maps array index → bit position in mode register
|
|
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
|
|
|
|
// Sensor registers
|
|
private readonly ushort[] _fanCountRegister;
|
|
private readonly ushort[] _fanRpmRegister;
|
|
private readonly int _maxFanCount;
|
|
private readonly int _minFanCount;
|
|
private readonly int _minFanRpm;
|
|
private readonly TemperatureSourceData[] _temperaturesSource;
|
|
private readonly ushort _vBatMonitorControlRegister;
|
|
private readonly ushort[] _voltageRegisters;
|
|
private readonly ushort _voltageVBatRegister;
|
|
|
|
// Fan control state (save/restore)
|
|
private readonly byte[] _initialFanControlMode = new byte[7];
|
|
private readonly byte[] _initialFanPwmCommand = new byte[7];
|
|
private readonly bool[] _restoreDefaultFanControlRequired = new bool[7];
|
|
|
|
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 = [0x04A, 0x04B, 0x04C];
|
|
FAN_PWM_COMMAND_REG = [0x119, 0x129, 0x139];
|
|
FAN_CONTROL_MODE_REG = [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 = [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 = [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 = [0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00]; // Not sure of next 8, MSI won't provide info
|
|
FAN_PWM_REQUEST_REG = [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
|
|
{
|
|
// Each index in the below arrays represents a fan header
|
|
// ARRAY_KEY = new ushort[] { CPU FAN, PUMP, CHIPSET, EZ-CONNECT FAN, null, null, null, null, null, SYSFAN7, SYSFAN1, SYSFAN2, SYSFAN3, SYSFAN4, SYSFAN5, SYSFAN6 };
|
|
FAN_PWM_OUT_REG = [0x160, 0x161, 0x162, 0x163, 0x164, 0x165, 0x166, 0x167, 0xFFF, 0xC93, 0xE05, 0xE04, 0xE03, 0xE02, 0xE01, 0xE00]; // Duty Cycle Sensors
|
|
// Direct PWM command registers ("pair 0" from extended unk_101E0 table in BIOS).
|
|
// CPU/Pump/Chipset/EZ-Connect: 0A:(28+ch). SYSFAN7(ch9): 08:E9. SYSFAN1-6(ch10-15): 02:(65..60).
|
|
// Setting the manual-mode bit in FAN_CONTROL_MODE_REG makes the EC use these for direct PWM,
|
|
// bypassing the SmartFAN curve engine and its inherent ~2%/sec smoothing.
|
|
FAN_PWM_COMMAND_REG = [0xA28, 0xA29, 0xA2A, 0xA2B, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x8E9, 0x265, 0x264, 0x263, 0x262, 0x261, 0x260];
|
|
// Manual-mode enable registers. Ch 0-7 use 0A:00 (one bit each); ch 8-15 use 08:0F.
|
|
FAN_CONTROL_MODE_REG = [0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0x80F, 0x80F, 0x80F, 0x80F, 0x80F, 0x80F, 0x80F];
|
|
FAN_PWM_REQUEST_REG = [0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01];
|
|
|
|
// Mapping from array index to bit position in the FAN_CONTROL_MODE_REG (0xA00) register.
|
|
// The EC firmware uses bits 0-7 for 8 fan channels; LHM's sparse array indices must map to the correct bit.
|
|
// Bit 0: CPU Fan, Bit 1: Pump, Bit 2: Chipset/SYSFAN1, Bit 3: EZ-Connect/SYSFAN2, etc.
|
|
// Based on the Linux nct6687d driver's msi_alt1 config: index 0=CPU, 1=Pump, 2-7=System fans.
|
|
// -1 means no valid mapping (unused index slots).
|
|
FAN_CONTROL_MODE_BIT = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; // Defaults
|
|
FAN_CONTROL_MODE_BIT[0] = 0; // CPU Fan → bit 0 of 0A:00
|
|
FAN_CONTROL_MODE_BIT[1] = 1; // Pump → bit 1 of 0A:00
|
|
FAN_CONTROL_MODE_BIT[2] = 2; // Chipset → bit 2 of 0A:00
|
|
FAN_CONTROL_MODE_BIT[3] = 3; // EZ-Connect → bit 3 of 0A:00
|
|
// System fans: manual-mode bits in 08:0F (derived from BIOS unk_104C0 entry[1]).
|
|
// EC channel mapping: LHM idx 9=ch9, 10=ch15, 11=ch14, 12=ch13, 13=ch12, 14=ch11, 15=ch10.
|
|
FAN_CONTROL_MODE_BIT[9] = 1; // SYSFAN7 (ch 9) → bit 1 of 08:0F
|
|
FAN_CONTROL_MODE_BIT[10] = 7; // SYSFAN1 (ch 15) → bit 7 of 08:0F
|
|
FAN_CONTROL_MODE_BIT[11] = 6; // SYSFAN2 (ch 14) → bit 6 of 08:0F
|
|
FAN_CONTROL_MODE_BIT[12] = 5; // SYSFAN3 (ch 13) → bit 5 of 08:0F
|
|
FAN_CONTROL_MODE_BIT[13] = 4; // SYSFAN4 (ch 12) → bit 4 of 08:0F
|
|
FAN_CONTROL_MODE_BIT[14] = 3; // SYSFAN5 (ch 11) → bit 3 of 08:0F
|
|
FAN_CONTROL_MODE_BIT[15] = 2; // SYSFAN6 (ch 10) → bit 2 of 08:0F
|
|
}
|
|
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 or Chip.NCT6796DS or Chip.NCT5585D
|
|
? new ushort[] { 0x001, 0x003, 0x011, 0x013, 0x015, 0xA09, 0xB09 }
|
|
: new ushort[] { 0x001, 0x003, 0x011, 0x013, 0x015, 0x017, 0x029 };
|
|
|
|
FAN_PWM_COMMAND_REG = [0x109, 0x209, 0x309, 0x809, 0x909, 0xA09, 0xB09];
|
|
FAN_CONTROL_MODE_REG = [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 = [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.NCT6796DS:
|
|
case Chip.NCT6797D:
|
|
case Chip.NCT6798D:
|
|
case Chip.NCT6799D:
|
|
case Chip.NCT6701D:
|
|
case Chip.NCT5585D:
|
|
switch (chip)
|
|
{
|
|
case Chip.NCT6779D:
|
|
Fans = new float?[5];
|
|
Controls = new float?[5];
|
|
break;
|
|
|
|
case Chip.NCT6796DR:
|
|
case Chip.NCT6796DS:
|
|
case Chip.NCT6797D:
|
|
case Chip.NCT6798D:
|
|
case Chip.NCT6799D:
|
|
case Chip.NCT6701D:
|
|
case Chip.NCT5585D:
|
|
Fans = new float?[7];
|
|
Controls = new float?[7];
|
|
break;
|
|
|
|
default:
|
|
Fans = new float?[6];
|
|
Controls = new float?[6];
|
|
break;
|
|
}
|
|
|
|
_fanCountRegister = [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 = [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)
|
|
{
|
|
// --- GROUP A: NCT6793D/6795D (Common features, separated from 6796/98 by AUXTIN4/TSENSOR) ---
|
|
case Chip.NCT6793D:
|
|
case Chip.NCT6795D:
|
|
case Chip.NCT6791D: // Assuming 6791/92 use a similar core map but less features than 6795
|
|
case Chip.NCT6792D:
|
|
case Chip.NCT6701D: // Defaulting to this group if map is less feature-rich than 6796/98
|
|
temperaturesSources.AddRange(new TemperatureSourceData[]
|
|
{
|
|
// Note: Linux labels start at index 1 (0 is empty).
|
|
// Indices 1-6 are consistent (SYSTIN, CPUTIN, AUXTIN0-3)
|
|
new(SourceNct67Xxd.PECI_0, 0x073, 0x074, 7, 0x100), // Index 0 in your list / PECI_0 is often 0x73 or 0x027
|
|
new(SourceNct67Xxd.CPUTIN, 0x075, 0x076, 7, 0x200, 0x491), // Index 2 (CPUTIN)
|
|
new(SourceNct67Xxd.SYSTIN, 0x077, 0x078, 7, 0x300, 0x490), // Index 1 (SYSTIN)
|
|
new(SourceNct67Xxd.AUXTIN0, 0x079, 0x07A, 7, 0x800, 0x492), // Index 3
|
|
new(SourceNct67Xxd.AUXTIN1, 0x07B, 0x07C, 7, 0x900, 0x493), // Index 4
|
|
new(SourceNct67Xxd.AUXTIN2, 0x07D, 0x07E, 7, 0xA00, 0x494), // Index 5
|
|
new(SourceNct67Xxd.AUXTIN3, 0x4A0, 0x49E, 6, 0xB00, 0x495), // Index 6
|
|
|
|
// AUXTIN4 (AUXTIN4 is missing or unnamed in 6793/6795 label array)
|
|
// Keeping AUXTIN4 here with original register mapping for compatibility, but moving down.
|
|
new(SourceNct67Xxd.AUXTIN4, 0x027, 0, -1, 0x621),
|
|
|
|
// PECI / PCH Group (Indices 16-25 in Linux labels)
|
|
new(SourceNct67Xxd.PECI_1, 0x672, 0, -1, 0xC27),
|
|
new(SourceNct67Xxd.PCH_CHIP_CPU_MAX_TEMP, 0x674, 0, -1, 0xC28, 0x400), // Index 18 (0x400 alt reg)
|
|
new(SourceNct67Xxd.PCH_CHIP_TEMP, 0x676, 0, -1, 0xC29, 0x401), // Index 19 (0x401 alt reg)
|
|
new(SourceNct67Xxd.PCH_CPU_TEMP, 0x678, 0, -1, 0xC2A, 0x402), // Index 20 (0x402 alt reg)
|
|
new(SourceNct67Xxd.PCH_MCH_TEMP, 0x67A, 0, -1, 0xC2B, 0x404), // Index 21 (0x404 alt reg)
|
|
new(SourceNct67Xxd.AGENT0_DIMM0, 0x405, 0, -1), // Index 22 (0x405 alt reg)
|
|
new(SourceNct67Xxd.AGENT0_DIMM1, 0x406, 0, -1), // Index 23 (0x406 alt reg)
|
|
new(SourceNct67Xxd.AGENT1_DIMM0, 0x407, 0, -1), // Index 24 (0x407 alt reg)
|
|
new(SourceNct67Xxd.AGENT1_DIMM1, 0x408, 0, -1), // Index 25 (0x408 alt reg)
|
|
|
|
// SMBUS Group (Indices 8-9 are consistent)
|
|
new(SourceNct67Xxd.SMBUSMASTER0, 0x150, 0x151, 7, 0x622), // Index 8
|
|
new(SourceNct67Xxd.SMBUSMASTER1, 0x670, 0, -1, 0xC26), // Index 9
|
|
|
|
// BYTE TEMP, CALIBRATION, and VIRTUAL (Indices 26-31)
|
|
new(SourceNct67Xxd.BYTE_TEMP0, 0x419, 0, -1), // Index 26
|
|
new(SourceNct67Xxd.BYTE_TEMP1, 0x41A, 0, -1), // Index 27
|
|
new(SourceNct67Xxd.PECI_0_CAL, 0x4F4, 0, -1), // Index 28
|
|
new(SourceNct67Xxd.PECI_1_CAL, 0x4F5, 0, -1), // Index 29
|
|
new(SourceNct67Xxd.VIRTUAL_TEMP, 0), // Index 31
|
|
new(SourceNct67Xxd.SPARE_TEMP, 0),
|
|
new(SourceNct67Xxd.SPARE_TEMP2, 0)
|
|
});
|
|
break;
|
|
|
|
// --- GROUP B: NCT6796D/6797D (Introduces AUXTIN4) ---
|
|
case Chip.NCT6796D:
|
|
case Chip.NCT6796DR:
|
|
case Chip.NCT6797D:
|
|
temperaturesSources.AddRange(new TemperatureSourceData[]
|
|
{
|
|
// Indices 1-7 are consistent with 6796 labels (AUXTIN4 is Index 7)
|
|
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), // Index 7
|
|
|
|
// SMBUS Group (Indices 8-9 are consistent)
|
|
new(SourceNct67Xxd.SMBUSMASTER0, 0x150, 0x151, 7, 0x622),
|
|
new(SourceNct67Xxd.SMBUSMASTER1, 0x670, 0, -1, 0xC26),
|
|
|
|
// PECI / PCH / DIMM Group (Indices 16-25)
|
|
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, 0x405, 0, -1),
|
|
new(SourceNct67Xxd.AGENT0_DIMM1, 0x406, 0, -1),
|
|
new(SourceNct67Xxd.AGENT1_DIMM0, 0x407, 0, -1),
|
|
new(SourceNct67Xxd.AGENT1_DIMM1, 0x408, 0, -1),
|
|
|
|
// BYTE TEMP, CALIBRATION, and VIRTUAL (Indices 26-31)
|
|
new(SourceNct67Xxd.BYTE_TEMP0, 0x419, 0, -1),
|
|
new(SourceNct67Xxd.BYTE_TEMP1, 0x41A, 0, -1),
|
|
new(SourceNct67Xxd.PECI_0_CAL, 0x4F4, 0, -1),
|
|
new(SourceNct67Xxd.PECI_1_CAL, 0x4F5, 0, -1),
|
|
new(SourceNct67Xxd.VIRTUAL_TEMP, 0),
|
|
new(SourceNct67Xxd.SPARE_TEMP, 0),
|
|
new(SourceNct67Xxd.SPARE_TEMP2, 0)
|
|
});
|
|
break;
|
|
|
|
// --- GROUP D: NCT6798D/NCT6799D (Introduces AUXTIN5) ---
|
|
case Chip.NCT6798D:
|
|
case Chip.NCT6799D:
|
|
temperaturesSources.AddRange(new TemperatureSourceData[]
|
|
{
|
|
// Indices 1-7 are consistent (SYSTIN through AUXTIN4)
|
|
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), // Index 7
|
|
|
|
new(SourceNct67Xxd.TSENSOR, 0x4A2, 0x4A1, 7, 0xC00, 0x496),
|
|
|
|
// SMBUS Group (Indices 8-9 are consistent)
|
|
new(SourceNct67Xxd.SMBUSMASTER0, 0x150, 0x151, 7, 0x622),
|
|
new(SourceNct67Xxd.SMBUSMASTER1, 0x670, 0, -1, 0xC26),
|
|
|
|
// PECI / PCH / DIMM Group (Indices 16-25)
|
|
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, 0x405, 0, -1),
|
|
new(SourceNct67Xxd.AGENT0_DIMM1, 0x406, 0, -1),
|
|
new(SourceNct67Xxd.AGENT1_DIMM0, 0x407, 0, -1),
|
|
new(SourceNct67Xxd.AGENT1_DIMM1, 0x408, 0, -1),
|
|
|
|
// BYTE TEMP, CALIBRATION, and VIRTUAL (Indices 26-31)
|
|
new(SourceNct67Xxd.BYTE_TEMP0, 0x419, 0, -1),
|
|
new(SourceNct67Xxd.BYTE_TEMP1, 0x41A, 0, -1),
|
|
new(SourceNct67Xxd.PECI_0_CAL, 0x4F4, 0, -1),
|
|
new(SourceNct67Xxd.PECI_1_CAL, 0x4F5, 0, -1),
|
|
new(SourceNct67Xxd.VIRTUAL_TEMP, 0),
|
|
new(SourceNct67Xxd.SPARE_TEMP, 0),
|
|
new(SourceNct67Xxd.SPARE_TEMP2, 0)
|
|
});
|
|
break;
|
|
|
|
case Chip.NCT6796DS:
|
|
temperaturesSources.AddRange(new TemperatureSourceData[]
|
|
{
|
|
//https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/pull/2012
|
|
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, 0x07D, 0x07E, 7, 0xA00, 0x495),
|
|
new(SourceNct67Xxd.AUXTIN4, 0x027, 0, 4, 0xC16, 0x496),
|
|
new(SourceNct67Xxd.AUXTIN5, 0x449, 0, 4, 0x100, 0x4A2),
|
|
new(SourceNct67Xxd.SMBUSMASTER0, 0x150, 0x151, 7, 0x622),
|
|
new(SourceNct67Xxd.PECI_0, 0x0720),
|
|
new(SourceNct67Xxd.VIRTUAL_TEMP, 0)
|
|
});
|
|
|
|
break;
|
|
|
|
case Chip.NCT5585D:
|
|
temperaturesSources.AddRange(new TemperatureSourceData[]
|
|
{
|
|
new(SourceNct67Xxd.PECI_0, 0x0720, 0, -1, 0x100),
|
|
new(SourceNct67Xxd.CPUTIN, 0x075, 0x076, 7, 0x000, 0x073),
|
|
new(SourceNct67Xxd.AUXTIN1, 0x07B, 0x07C, 7, 0x900, 0x493),
|
|
new(SourceNct67Xxd.AUXTIN3, 0x4A0, 0x49E, 6, 0xB00, 0x495),
|
|
});
|
|
|
|
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 = [0x300, 0x301, 0x302, 0x303, 0x304, 0x305, 0x307, 0x308, 0x309];
|
|
_voltageVBatRegister = 0x308;
|
|
Temperatures = new float?[7];
|
|
_temperaturesSource =
|
|
[
|
|
new TemperatureSourceData(SourceNct610X.PECI_0, 0x06b, 0, -1, 0x621),
|
|
new TemperatureSourceData(SourceNct610X.AUXTIN, 0x010, 0x016, 0),
|
|
new TemperatureSourceData(SourceNct610X.CPUTIN, 0x011, 0x01B, 1),
|
|
new TemperatureSourceData(SourceNct610X.SYSTIN0, 0x012, 0x01B, 2),
|
|
new TemperatureSourceData(SourceNct610X.SYSTIN1, 0x013, 0x016, 3),
|
|
new TemperatureSourceData(SourceNct610X.SYSTIN2, 0x014, 0x01B, 4),
|
|
new TemperatureSourceData(SourceNct610X.SYSTIN3, 0x015, 0x01B, 5)
|
|
];
|
|
|
|
break;
|
|
|
|
case Chip.NCT6683D:
|
|
case Chip.NCT6686D:
|
|
case Chip.NCT6687D:
|
|
Fans = new float?[17];
|
|
Controls = new float?[8];
|
|
Voltages = new float?[14];
|
|
Temperatures = new float?[11];
|
|
|
|
// CPU
|
|
// System
|
|
// MOS
|
|
// PCH
|
|
// CPU Socket
|
|
// PCIE_1
|
|
// M2_1
|
|
// PCIE_1
|
|
// PCIE_2
|
|
// M2_1
|
|
// M2_4
|
|
_temperaturesSource =
|
|
[
|
|
new TemperatureSourceData(null, 0x100),
|
|
new TemperatureSourceData(null, 0x102),
|
|
new TemperatureSourceData(null, 0x104),
|
|
new TemperatureSourceData(null, 0x106),
|
|
new TemperatureSourceData(null, 0x108),
|
|
new TemperatureSourceData(null, 0x10A),
|
|
new TemperatureSourceData(null, 0x10C),
|
|
new TemperatureSourceData(null, 0x10E),
|
|
new TemperatureSourceData(null, 0x110),
|
|
new TemperatureSourceData(null, 0x112),
|
|
new TemperatureSourceData(null, 0x114)
|
|
];
|
|
|
|
// 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 = [0x120, 0x122, 0x124, 0x126, 0x128, 0x12A, 0x12C, 0x12E, 0x130, 0x13A, 0x13E, 0x136, 0x138, 0x13C];
|
|
|
|
// CPU Fan
|
|
// PUMP Fan 1
|
|
// 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 - PUMP Fan 2 on some NCT6687Ds (e.g. Z790 GODLIKE MAX)
|
|
// SYS Fan 5 on newer NCT6687Ds - EZ-Connect Fan 1 (JAF) on some NCT6687Ds (e.g. Z790 GODLIKE MAX)
|
|
// SYS Fan 4 on newer NCT6687Ds
|
|
// SYS Fan 3 on newer NCT6687Ds
|
|
// SYS Fan 2 on newer NCT6687Ds
|
|
// SYS Fan 1 on newer NCT6687Ds
|
|
// SYS Fan 7 on some NCT6687Ds - 0x852 (e.g. Z790 GODLIKE MAX)
|
|
_fanRpmRegister = [0x140, 0x142, 0x144, 0x146, 0x148, 0x14A, 0x14C, 0x14E, 0x150, 0x152, 0x154, 0x156, 0x158, 0x15A, 0x15C, 0x15E, 0x852];
|
|
|
|
// On some boards, there will be SYS Fan 7 (e.g. MSI MEG Z790 GODLIKE MAX)
|
|
_fanCountRegister = [0x852];
|
|
|
|
// max value for 13-bit fan counter
|
|
_maxFanCount = 0x1FFF;
|
|
|
|
// min value that could be transferred to 16-bit RPM registers
|
|
_minFanCount = 0x15;
|
|
|
|
_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?[16];
|
|
Controls = new float?[16];
|
|
Voltages = new float?[14];
|
|
Temperatures = new float?[7];
|
|
|
|
_temperaturesSource =
|
|
[
|
|
new TemperatureSourceData(null, 0x100), // CPU
|
|
new TemperatureSourceData(null, 0x102), // System
|
|
new TemperatureSourceData(null, 0x104), // MOS
|
|
new TemperatureSourceData(null, 0x106), // PCH
|
|
new TemperatureSourceData(null, 0x108), // CPU Socket
|
|
new TemperatureSourceData(null, 0x10A), // PCIE_1
|
|
new TemperatureSourceData(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 = [0x120, 0x122, 0x124, 0x126, 0x128, 0x12A, 0x12C, 0x12E, 0x130, 0x13A, 0x13E, 0x136, 0x138, 0x13C];
|
|
|
|
// CPU Fan 0x140
|
|
// PUMP Fan 0x142
|
|
// Chipset Fan 0x144
|
|
// EZ-Conn 0x146
|
|
// NOTHING
|
|
// NOTHING
|
|
// NOTHING
|
|
// NOTHING
|
|
// NOTHING
|
|
// SYS Fan 7 0x152
|
|
// 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 = [0x140, 0x142, 0x144, 0x146, 0x148, 0x14A, 0x14C, 0x14E, 0x150, 0x152, 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 if (Chip is Chip.NCT6687DR)
|
|
{
|
|
// NCT6687DR (MSI AM5/LGA1851): Direct PWM mode for ALL fans.
|
|
// Set the manual-mode bit to bypass the SmartFAN curve engine entirely.
|
|
// CPU/Pump/Chipset/EZ-Connect: bit in 0xA00. System fans: bit in 0x80F.
|
|
// Derived from BIOS unk_104C0 table — entry[1] = set manual-mode bit.
|
|
int bitPos = FAN_CONTROL_MODE_BIT[index];
|
|
if (bitPos >= 0)
|
|
{
|
|
byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
|
|
byte bitMask = (byte)(0x01 << bitPos);
|
|
mode = (byte)(mode | bitMask);
|
|
WriteByte(FAN_CONTROL_MODE_REG[index], mode);
|
|
}
|
|
|
|
// Retry up to 3 times if EC rejects the configuration (INVALID bit)
|
|
for (int attempt = 0; attempt < 3; attempt++)
|
|
{
|
|
if (!StartFanCfgUpdate(index))
|
|
break;
|
|
|
|
Set6687DRControl(index, value.Value);
|
|
if (CompleteFanConfigUpdate(index))
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// NCT6683D / NCT6686D / NCT6687D (non-DR)
|
|
// 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
|
|
|
|
WriteByte(FAN_PWM_REQUEST_REG[index], 0x80);
|
|
Thread.Sleep(50);
|
|
|
|
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_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
|
|
};
|
|
}
|
|
}
|
|
|
|
Log("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(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.NCT6796DS:
|
|
case Chip.NCT6797D:
|
|
case Chip.NCT6798D:
|
|
case Chip.NCT6799D:
|
|
case Chip.NCT5585D:
|
|
if (_temperaturesSource[i].Register == 0)
|
|
{
|
|
Log("Temperature register {0} skipped, address 0.", i);
|
|
continue;
|
|
}
|
|
|
|
value = (sbyte)ReadByte(_temperaturesSource[i].Register) << 1;
|
|
Log("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;
|
|
Log("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);
|
|
Log("Temperature register {0} source at 0x{1:X3}: {2:G} ({2:D})", i, ts.SourceRegister, source);
|
|
}
|
|
else
|
|
{
|
|
source = (SourceNct67Xxd)ts.Source;
|
|
Log("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)
|
|
{
|
|
Log("Temperature register {0} discarded, because source seen before.", i);
|
|
continue;
|
|
}
|
|
|
|
temperature = 0.5f * value;
|
|
Log("Temperature register {0} final temperature: {1}.", i, temperature);
|
|
if (temperature is > 125 or < -55)
|
|
{
|
|
temperature = null;
|
|
Log("Temperature register {0} discarded: Out of range.", i);
|
|
}
|
|
else
|
|
{
|
|
temperatureSourceMask |= 1L << (byte)source;
|
|
Log("Temperature register {0} accepted.", i);
|
|
}
|
|
|
|
for (int j = 0; j < Temperatures.Length; j++)
|
|
{
|
|
if ((SourceNct67Xxd)_temperaturesSource[j].Source == source)
|
|
{
|
|
Temperatures[j] = temperature;
|
|
Log("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)
|
|
{
|
|
Log("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)
|
|
{
|
|
Log("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);
|
|
Log("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;
|
|
Log("Alternate Temperature register for temperature {0}, {1:G} ({1:D}), discarded: Out of range.", i, ts.Source);
|
|
}
|
|
|
|
Temperatures[i] = temperature;
|
|
}
|
|
|
|
void Update13BitFan(int i, byte low, byte high)
|
|
{
|
|
// No fan plugged in OR fan is at 0 RPM
|
|
if (Chip is Chip.NCT6687D && high == 0xFF && low == 0xF8)
|
|
{
|
|
Fans[i] = 0;
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
var fcrList = _fanCountRegister?.ToList() ?? new List<ushort>();
|
|
|
|
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));
|
|
|
|
Update13BitFan(i, low, high);
|
|
}
|
|
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
|
|
{
|
|
byte high = ReadByte(_fanRpmRegister[i]);
|
|
byte low = ReadByte((ushort)(_fanRpmRegister[i] + 1));
|
|
|
|
//13-bit fan ?
|
|
if (fcrList.Contains(_fanRpmRegister[i]))
|
|
{
|
|
Update13BitFan(i, low, high);
|
|
}
|
|
else
|
|
{
|
|
Fans[i] = (high << 8) | low;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
Stopwatch timeout = Stopwatch.StartNew();
|
|
while (true)
|
|
{
|
|
access = _lpcPort.ReadIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET));
|
|
if (access == EC_SPACE_PAGE_SELECT || timeout.Elapsed >= TimeSpan.FromMilliseconds(500))
|
|
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;
|
|
|
|
Stopwatch timeout = Stopwatch.StartNew();
|
|
while (true)
|
|
{
|
|
access = _lpcPort.ReadIoPort((ushort)(_port + EC_SPACE_PAGE_REGISTER_OFFSET));
|
|
if (access == EC_SPACE_PAGE_SELECT || timeout.Elapsed >= TimeSpan.FromMilliseconds(500))
|
|
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);
|
|
_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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request the EC to enter fan configuration phase and wait until registers are unlocked.
|
|
/// Based on the Linux nct6687d driver's start_fan_cfg_update() function.
|
|
/// </summary>
|
|
/// <returns>true if the EC entered config phase successfully; false on timeout.</returns>
|
|
private bool StartFanCfgUpdate(int index)
|
|
{
|
|
byte engineSts = ReadByte(NCT6687DR_REG_FAN_ENGINE_STS);
|
|
|
|
// Already accessible
|
|
if ((engineSts & NCT6687DR_FAN_CFG_LOCK) == 0 && (engineSts & NCT6687DR_FAN_CFG_PHASE) != 0)
|
|
return true;
|
|
|
|
// Wait until any existing config phase is done and request flag is clear
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
while (sw.Elapsed < TimeSpan.FromSeconds(1))
|
|
{
|
|
engineSts = ReadByte(NCT6687DR_REG_FAN_ENGINE_STS);
|
|
if ((engineSts & NCT6687DR_FAN_CFG_PHASE) == 0)
|
|
{
|
|
byte req = ReadByte(FAN_PWM_REQUEST_REG[index]);
|
|
if ((req & NCT6687DR_FAN_CFG_REQ) == 0)
|
|
break;
|
|
}
|
|
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
// Send config request
|
|
WriteByte(FAN_PWM_REQUEST_REG[index], NCT6687DR_FAN_CFG_REQ);
|
|
Thread.Sleep(10); // CC_Engine: fixed 10ms delay after request
|
|
|
|
// Wait until EC enters config phase and unlocks registers
|
|
sw.Restart();
|
|
while (sw.Elapsed < TimeSpan.FromSeconds(1))
|
|
{
|
|
engineSts = ReadByte(NCT6687DR_REG_FAN_ENGINE_STS);
|
|
if ((engineSts & NCT6687DR_FAN_CFG_LOCK) == 0 && (engineSts & NCT6687DR_FAN_CFG_PHASE) != 0)
|
|
return true;
|
|
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
return false; // Timeout: EC did not enter config phase
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signal the EC that fan configuration is complete and wait for acknowledgment.
|
|
/// Returns false if the EC set the INVALID bit (configuration rejected).
|
|
/// </summary>
|
|
private bool CompleteFanConfigUpdate(int index)
|
|
{
|
|
byte engineSts = ReadByte(NCT6687DR_REG_FAN_ENGINE_STS);
|
|
|
|
// Already not accessible
|
|
if ((engineSts & NCT6687DR_FAN_CFG_LOCK) != 0 || (engineSts & NCT6687DR_FAN_CFG_PHASE) == 0)
|
|
return false;
|
|
|
|
// Signal done — CC_Engine uses 0xC0 (REQ|DONE) to commit atomically
|
|
WriteByte(FAN_PWM_REQUEST_REG[index], NCT6687DR_FAN_CFG_REQ | NCT6687DR_FAN_CFG_DONE);
|
|
Thread.Sleep(10); // CC_Engine: fixed 10ms delay after commit
|
|
|
|
// Wait until EC checks the new configuration
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
while (sw.Elapsed < TimeSpan.FromSeconds(1))
|
|
{
|
|
engineSts = ReadByte(NCT6687DR_REG_FAN_ENGINE_STS);
|
|
if ((engineSts & NCT6687DR_FAN_CFG_CHECK_DONE) != 0)
|
|
break;
|
|
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
// Check if EC rejected the configuration (INVALID bit)
|
|
engineSts = ReadByte(NCT6687DR_REG_FAN_ENGINE_STS);
|
|
return (engineSts & NCT6687DR_FAN_CFG_INVALID) == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write fan control value for NCT6687DR. All fans use direct PWM command registers.
|
|
/// </summary>
|
|
private void Set6687DRControl(int index, byte value)
|
|
{
|
|
WriteByte(FAN_PWM_COMMAND_REG[index], 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 if (Chip is Chip.NCT6687DR)
|
|
{
|
|
// Use the correct bit position mapping for NCT6687DR
|
|
int bitPos = FAN_CONTROL_MODE_BIT[index];
|
|
if (bitPos >= 0)
|
|
{
|
|
byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
|
|
byte bitMask = (byte)(0x01 << bitPos);
|
|
_initialFanControlMode[index] = (byte)(mode & bitMask);
|
|
}
|
|
|
|
}
|
|
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 if (Chip is Chip.NCT6687DR)
|
|
{
|
|
// NCT6687DR: Restore original manual-mode bit for all fans.
|
|
// Clear the bit we set in SetControl to return to SmartFAN curve mode.
|
|
int bitPos = FAN_CONTROL_MODE_BIT[index];
|
|
if (bitPos >= 0)
|
|
{
|
|
byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
|
|
byte bitMask = (byte)(0x01 << bitPos);
|
|
mode = (byte)(mode & ~bitMask);
|
|
WriteByte(FAN_CONTROL_MODE_REG[index], mode);
|
|
}
|
|
|
|
for (int attempt = 0; attempt < 3; attempt++)
|
|
{
|
|
if (!StartFanCfgUpdate(index))
|
|
break;
|
|
|
|
Set6687DRControl(index, _initialFanPwmCommand[index]);
|
|
if (CompleteFanConfigUpdate(index))
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// NCT6683D / NCT6686D / NCT6687D (non-DR)
|
|
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);
|
|
|
|
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.NCT6796DS and
|
|
not Chip.NCT6797D and
|
|
not Chip.NCT6798D and
|
|
not Chip.NCT6799D and
|
|
not Chip.NCT5585D)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// the lock is disabled already if the vendor ID can be read
|
|
if (IsNuvotonVendor())
|
|
return;
|
|
|
|
_lpcPort.WinbondNuvotonFintekEnter();
|
|
_lpcPort.NuvotonDisableIOSpaceLock();
|
|
_lpcPort.WinbondNuvotonFintekExit();
|
|
}
|
|
|
|
[Conditional("DEBUG_LOG"), Conditional("NCT677X_DEBUG_LOG")]
|
|
private static void Log(string format, params object[] args)
|
|
{
|
|
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, format, args));
|
|
}
|
|
|
|
private readonly struct TemperatureSourceData(Enum source, ushort register, ushort halfRegister = 0, int halfBit = -1, ushort sourceRegister = 0, ushort? alternateRegister = null)
|
|
{
|
|
public readonly Enum Source = source;
|
|
public readonly ushort Register = register;
|
|
public readonly ushort HalfRegister = halfRegister;
|
|
public readonly int HalfBit = halfBit;
|
|
public readonly ushort SourceRegister = sourceRegister;
|
|
public readonly ushort? AlternateRegister = alternateRegister;
|
|
}
|
|
|
|
[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,
|
|
TSENSOR = 10,
|
|
SMBUSMASTER0 = 8,
|
|
SMBUSMASTER1 = 9,
|
|
RESERVED_1 = 10,
|
|
RESERVED_2 = 11,
|
|
RESERVED_3 = 12,
|
|
RESERVED_4 = 13,
|
|
RESERVED_5 = 14,
|
|
RESERVED_6 = 15,
|
|
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,
|
|
RESERVED_7 = 30,
|
|
VIRTUAL_TEMP = 31,
|
|
SPARE_TEMP = 32,
|
|
SPARE_TEMP2 = 33,
|
|
AUXTIN5 = 34,
|
|
}
|
|
|
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
private enum SourceNct610X : byte
|
|
{
|
|
CPUTIN = 1,
|
|
AUXTIN = 2,
|
|
SYSTIN0 = 3,
|
|
SYSTIN1 = 4,
|
|
SYSTIN2 = 5,
|
|
SYSTIN3 = 6,
|
|
PECI_0 = 12
|
|
}
|
|
}
|