// 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
    }
}
