dorisoy_HardwareMonitor/LibreHardwareMonitorLib/Hardware/Motherboard/Lpc/Nct677X.cs
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Threading;

namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc
{
    internal class Nct677X : ISuperIO
    {
        private readonly struct TemperatureSourceData
        {
            public TemperatureSourceData(Enum? source, ushort register, ushort halfRegister = 0, int halfBit = -1, ushort sourceRegister = 0, ushort? alternateRegister = null)
            {
                Source = source;
                Register = register;
                HalfRegister = halfRegister;
                HalfBit = halfBit;
                SourceRegister = sourceRegister;
                AlternateRegister = alternateRegister;
            }
            public readonly Enum? Source;
            public readonly ushort Register;
            public readonly ushort HalfRegister;
            public readonly int HalfBit;
            public readonly ushort SourceRegister;
            public readonly ushort? AlternateRegister;
        };

        private readonly ushort[] _fanCountRegister;
        private readonly ushort[] _fanRpmRegister;
        private readonly byte[] _initialFanControlMode = new byte[7];
        private readonly byte[] _initialFanPwmCommand = new byte[7];
        private readonly bool _isNuvotonVendor;
        private readonly LpcPort _lpcPort;
        private readonly int _maxFanCount;
        private readonly int _minFanCount;
        private readonly int _minFanRpm;
        private readonly ushort _port;
        private readonly bool[] _restoreDefaultFanControlRequired = new bool[7];
        private readonly byte _revision;
        private readonly TemperatureSourceData[] _temperaturesSource;
        private readonly ushort _vBatMonitorControlRegister;
        private readonly ushort[] _voltageRegisters;
        private readonly ushort _voltageVBatRegister;

        public Nct677X(Chip chip, byte revision, ushort port, LpcPort lpcPort)
        {
            Chip = chip;
            _revision = revision;
            _port = port;
            _lpcPort = lpcPort;

            if (chip == Chip.NCT610XD)
            {
                VENDOR_ID_HIGH_REGISTER = 0x80FE;
                VENDOR_ID_LOW_REGISTER = 0x00FE;

                FAN_PWM_OUT_REG = new ushort[] { 0x04A, 0x04B, 0x04C };
                FAN_PWM_COMMAND_REG = new ushort[] { 0x119, 0x129, 0x139 };
                FAN_CONTROL_MODE_REG = new ushort[] { 0x113, 0x123, 0x133 };

                _vBatMonitorControlRegister = 0x0318;
            }
            else if (chip == Chip.NCT6687D || chip == Chip.NCT6683D)
            {
                FAN_PWM_OUT_REG = new ushort[] { 0x160, 0x161, 0x162, 0x163, 0x164, 0x165, 0x166, 0x167 };
                FAN_PWM_COMMAND_REG = new ushort[] { 0xA28, 0xA29, 0xA2A, 0xA2B, 0xA2C, 0xA2D, 0xA2E, 0xA2F };
                FAN_CONTROL_MODE_REG = new ushort[] { 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00, 0xA00 };
                FAN_PWM_REQUEST_REG = new ushort[] { 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01, 0xA01 };
            }
            else
            {
                VENDOR_ID_HIGH_REGISTER = 0x804F;
                VENDOR_ID_LOW_REGISTER = 0x004F;

                if (chip == Chip.NCT6797D || chip == Chip.NCT6798D)
                    FAN_PWM_OUT_REG = new ushort[] { 0x001, 0x003, 0x011, 0x013, 0x015, 0xA09, 0xB09 };
                else
                    FAN_PWM_OUT_REG = new ushort[] { 0x001, 0x003, 0x011, 0x013, 0x015, 0x017, 0x029 };

                FAN_PWM_COMMAND_REG = new ushort[] { 0x109, 0x209, 0x309, 0x809, 0x909, 0xA09, 0xB09 };
                FAN_CONTROL_MODE_REG = new ushort[] { 0x102, 0x202, 0x302, 0x802, 0x902, 0xA02, 0xB02 };

                _vBatMonitorControlRegister = 0x005D;
            }

            _isNuvotonVendor = IsNuvotonVendor();

            if (!_isNuvotonVendor)
                return;


            switch (chip)
            {
                case Chip.NCT6771F:
                case Chip.NCT6776F:
                {
                    if (chip == Chip.NCT6771F)
                    {
                        Fans = new float?[4];

                        // min value RPM value with 16-bit fan counter
                        _minFanRpm = (int)(1.35e6 / 0xFFFF);
                    }
                    else
                    {
                        Fans = new float?[5];

                        // min value RPM value with 13-bit fan counter
                        _minFanRpm = (int)(1.35e6 / 0x1FFF);
                    }

                    _fanRpmRegister = new ushort[5];
                    for (int i = 0; i < _fanRpmRegister.Length; i++)
                        _fanRpmRegister[i] = (ushort)(0x656 + (i << 1));

                    Controls = new float?[3];

                    Voltages = new float?[9];
                    _voltageRegisters = new ushort[] { 0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x550, 0x551 };
                    _voltageVBatRegister = 0x551;
                    _temperaturesSource = new TemperatureSourceData[]
                    {
                        new TemperatureSourceData(chip == Chip.NCT6771F ?  SourceNct6771F.PECI_0 : SourceNct6776F.PECI_0, 0x027, 0, -1, 0x621),
                        new TemperatureSourceData(chip == Chip.NCT6771F ?  SourceNct6771F.CPUTIN : SourceNct6776F.CPUTIN, 0x073, 0x074, 7, 0x100),
                        new TemperatureSourceData(chip == Chip.NCT6771F ?  SourceNct6771F.AUXTIN : SourceNct6776F.AUXTIN, 0x075, 0x076, 7, 0x200),
                        new TemperatureSourceData(chip == Chip.NCT6771F ?  SourceNct6771F.SYSTIN : SourceNct6776F.SYSTIN, 0x077, 0x078, 7, 0x300),
                        new TemperatureSourceData(null, 0x150, 0x151, 7, 0x622),
                        new TemperatureSourceData(null, 0x250, 0x251, 7, 0x623),
                        new TemperatureSourceData(null, 0x62B, 0x62E, 0, 0x624),
                        new TemperatureSourceData(null, 0x62C, 0x62E, 1, 0x625),
                        new TemperatureSourceData(null, 0x62D, 0x62E, 2, 0x626)
                    };

                    Temperatures = new float?[4];
                    break;
                }
                case Chip.NCT6779D:
                case Chip.NCT6791D:
                case Chip.NCT6792D:
                case Chip.NCT6792DA:
                case Chip.NCT6793D:
                case Chip.NCT6795D:
                case Chip.NCT6796D:
                case Chip.NCT6796DR:
                case Chip.NCT6797D:
                case Chip.NCT6798D:
                {
                    switch (chip)
                    {
                        case Chip.NCT6779D:
                        {
                            Fans = new float?[5];
                            Controls = new float?[5];
                            break;
                        }
                        case Chip.NCT6796DR:
                        case Chip.NCT6797D:
                        case Chip.NCT6798D:
                        {
                            Fans = new float?[7];
                            Controls = new float?[7];
                            break;
                        }
                        default:
                        {
                            Fans = new float?[6];
                            Controls = new float?[6];
                            break;
                        }
                    }

                    _fanCountRegister = new ushort[] { 0x4B0, 0x4B2, 0x4B4, 0x4B6, 0x4B8, 0x4BA, 0x4CC };

                    // max value for 13-bit fan counter
                    _maxFanCount = 0x1FFF;

                    // min value that could be transferred to 16-bit RPM registers
                    _minFanCount = 0x15;

                    Voltages = new float?[15];
                    _voltageRegisters = new ushort[] { 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, 0x488, 0x489, 0x48A, 0x48B, 0x48C, 0x48D, 0x48E };
                    _voltageVBatRegister = 0x488;
                    var temperaturesSources = new List<TemperatureSourceData>();
                    switch (chip)
                    {
                        case Chip.NCT6796D:
                        case Chip.NCT6796DR:
                        case Chip.NCT6797D:
                        case Chip.NCT6798D:
                        {
                            temperaturesSources.AddRange(new TemperatureSourceData[]
                            {
                                new TemperatureSourceData(SourceNct67Xxd.PECI_0, 0x073, 0x074, 7, 0x100),
                                new TemperatureSourceData(SourceNct67Xxd.CPUTIN, 0x075, 0x076, 7, 0x200, 0x491),
                                new TemperatureSourceData(SourceNct67Xxd.SYSTIN, 0x077, 0x078, 7, 0x300, 0x490),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN0, 0x079, 0x07A, 7, 0x800, 0x492),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN1, 0x07B, 0x07C, 7, 0x900, 0x493),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN2, 0x07D, 0x07E, 7, 0xA00, 0x494),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN3, 0x4A0, 0x49E, 6, 0xB00, 0x495),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN4, 0x027, 0, -1, 0x621),
                                new TemperatureSourceData(SourceNct67Xxd.SMBUSMASTER0, 0x150, 0x151, 7, 0x622),
                                new TemperatureSourceData(SourceNct67Xxd.SMBUSMASTER1, 0x670, 0, -1, 0xC26),
                                new TemperatureSourceData(SourceNct67Xxd.PECI_1, 0x672, 0, -1, 0xC27),
                                new TemperatureSourceData(SourceNct67Xxd.PCH_CHIP_CPU_MAX_TEMP, 0x674, 0, -1, 0xC28, 0x400),
                                new TemperatureSourceData(SourceNct67Xxd.PCH_CHIP_TEMP, 0x676, 0, -1, 0xC29, 0x401),
                                new TemperatureSourceData(SourceNct67Xxd.PCH_CPU_TEMP,  0x678, 0, -1, 0xC2A, 0x402),
                                new TemperatureSourceData(SourceNct67Xxd.PCH_MCH_TEMP, 0x67A, 0, -1, 0xC2B, 0x404),
                                new TemperatureSourceData(SourceNct67Xxd.AGENT0_DIMM0, 0),
                                new TemperatureSourceData(SourceNct67Xxd.AGENT0_DIMM1, 0),
                                new TemperatureSourceData(SourceNct67Xxd.AGENT1_DIMM0, 0),
                                new TemperatureSourceData(SourceNct67Xxd.AGENT1_DIMM1, 0),
                                new TemperatureSourceData(SourceNct67Xxd.BYTE_TEMP0, 0),
                                new TemperatureSourceData(SourceNct67Xxd.BYTE_TEMP1, 0),
                                new TemperatureSourceData(SourceNct67Xxd.PECI_0_CAL, 0),
                                new TemperatureSourceData(SourceNct67Xxd.PECI_1_CAL, 0),
                                new TemperatureSourceData(SourceNct67Xxd.VIRTUAL_TEMP, 0)
                            });
                            break;
                        }
                        default:
                        {
                            temperaturesSources.AddRange(new TemperatureSourceData[]
                            {
                                new TemperatureSourceData(SourceNct67Xxd.PECI_0, 0x027, 0, -1, 0x621),
                                new TemperatureSourceData(SourceNct67Xxd.CPUTIN, 0x073, 0x074, 7, 0x100, 0x491),
                                new TemperatureSourceData(SourceNct67Xxd.SYSTIN, 0x075, 0x076, 7, 0x200, 0x490),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN0, 0x077, 0x078, 7, 0x300, 0x492),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN1, 0x079, 0x07A, 7, 0x800, 0x493),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN2, 0x07B, 0x07C, 7, 0x900, 0x494),
                                new TemperatureSourceData(SourceNct67Xxd.AUXTIN3, 0x150, 0x151, 7, 0x622, 0x495)
                            });
                            break;
                        }
                    }
                    _temperaturesSource = temperaturesSources.ToArray();
                    Temperatures = new float?[_temperaturesSource.Length];
                    break;
                }
                case Chip.NCT610XD:
                {
                    Fans = new float?[3];
                    Controls = new float?[3];

                    _fanRpmRegister = new ushort[3];
                    for (int i = 0; i < _fanRpmRegister.Length; i++)
                        _fanRpmRegister[i] = (ushort)(0x030 + (i << 1));

                    // min value RPM value with 13-bit fan counter
                    _minFanRpm = (int)(1.35e6 / 0x1FFF);

                    Voltages = new float?[9];
                    _voltageRegisters = new ushort[] { 0x300, 0x301, 0x302, 0x303, 0x304, 0x305, 0x307, 0x308, 0x309 };
                    _voltageVBatRegister = 0x308;
                    Temperatures = new float?[4];
                    _temperaturesSource = new TemperatureSourceData[] {
                        new TemperatureSourceData(SourceNct610X.PECI_0, 0x027, 0, -1, 0x621),
                        new TemperatureSourceData(SourceNct610X.SYSTIN, 0x018, 0x01B, 7, 0x100, 0x018),
                        new TemperatureSourceData(SourceNct610X.CPUTIN, 0x019, 0x11B, 7, 0x200, 0x019),
                        new TemperatureSourceData(SourceNct610X.AUXTIN, 0x01A, 0x21B, 7, 0x300, 0x01A)
                    };
                    break;
                }
                case Chip.NCT6683D:
                case Chip.NCT6687D:
                {
                    Fans = new float?[8];
                    Controls = new float?[8];
                    Voltages = new float?[14];
                    Temperatures = new float?[7];

                    // CPU
                    // System
                    // MOS
                    // PCH
                    // CPU Socket
                    // PCIE_1
                    // M2_1
                    _temperaturesSource = new TemperatureSourceData[] {
                        new 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),
                    };

                    // VIN0 +12V
                    // VIN1 +5V
                    // VIN2 VCore
                    // VIN3 SIO
                    // VIN4 DRAM
                    // VIN5 CPU IO
                    // VIN6 CPU SA
                    // VIN7 SIO
                    // 3VCC I/O +3.3
                    // SIO VTT
                    // SIO VREF
                    // SIO VSB
                    // SIO AVSB
                    // SIO VBAT
                    _voltageRegisters = new ushort[] { 0x120, 0x122, 0x124, 0x126, 0x128, 0x12A, 0x12C, 0x12E, 0x130, 0x13A, 0x13E, 0x136, 0x138, 0x13C };

                    // CPU Fan
                    // PUMP Fan
                    // SYS Fan 1
                    // SYS Fan 2
                    // SYS Fan 3
                    // SYS Fan 4
                    // SYS Fan 5
                    // SYS Fan 6
                    _fanRpmRegister = new ushort[] { 0x140, 0x142, 0x144, 0x146, 0x148, 0x14A, 0x14C, 0x14E };

                    _restoreDefaultFanControlRequired = new bool[_fanRpmRegister.Length];
                    _initialFanControlMode = new byte[_fanRpmRegister.Length];
                    _initialFanPwmCommand = new byte[_fanRpmRegister.Length];

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

        public Chip Chip { get; }

        public float?[] Controls { get; } = new float?[0];

        public float?[] Fans { get; } = new float?[0];

        public float?[] Temperatures { get; } = new float?[0];

        public float?[] Voltages { get; } = new float?[0];

        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 (!Ring0.WaitIsaBusMutex(10))
                return;


            if (value.HasValue)
            {
                SaveDefaultFanControl(index);

                if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
                {
                    // set manual mode
                    WriteByte(FAN_CONTROL_MODE_REG[index], 0);

                    // set output value
                    WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);
                }
                else
                {
                    // Manual mode, bit(1 : set, 0 : unset)
                    // bit 0 : CPU Fan
                    // bit 1 : PUMP Fan
                    // bit 2 : SYS Fan 1
                    // bit 3 : SYS Fan 2
                    // bit 4 : SYS Fan 3
                    // bit 5 : SYS Fan 4
                    // bit 6 : SYS Fan 5
                    // bit 7 : SYS Fan 6

                    byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
                    byte bitMask = (byte)(0x01 << index);
                    mode = (byte)(mode | bitMask);
                    WriteByte(FAN_CONTROL_MODE_REG[index], mode);

                    WriteByte(FAN_PWM_REQUEST_REG[index], 0x80);
                    Thread.Sleep(50);

                    WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);
                    WriteByte(FAN_PWM_REQUEST_REG[index], 0x40);
                    Thread.Sleep(50);
                }
            }
            else
            {
                RestoreDefaultFanControl(index);
            }

            Ring0.ReleaseIsaBusMutex();
        }

        public void Update()
        {
            if (!_isNuvotonVendor)
                return;

            if (!Ring0.WaitIsaBusMutex(10))
                return;


            DisableIOSpaceLock();

            for (int i = 0; i < Voltages.Length; i++)
            {
                if (Chip != Chip.NCT6687D)
                {
                    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 : (float?)null;
                }
                else
                {
                    float value = 0.001f * (16 * ReadByte(_voltageRegisters[i]) + (ReadByte((ushort)(_voltageRegisters[i] + 1)) >> 4));

                    Voltages[i] = i switch
                    {
                        // 12V
                        0 => value * 12.0f,
                        // 5V
                        1 => value * 5.0f,
                        // DRAM
                        4 => value * 2.0f,
                        _ => value
                    };
                }
            }

            System.Diagnostics.Debug.WriteLine("Updating temperatures.");
            long temperatureSourceMask = 0;
            for (int i = 0; i < _temperaturesSource.Length ; i++)
            {
                TemperatureSourceData ts = _temperaturesSource[i];
                switch (Chip)
                {
                    case Chip.NCT6687D:
                    case Chip.NCT6683D:
                    {
                        int value = (sbyte)ReadByte(ts.Register);
                        int half = (ReadByte((ushort)(ts.Register + 1)) >> 7) & 0x1;
                        float temperature = value + (0.5f * half);
                        Temperatures[i] = temperature;
                        break;
                    }
                    case Chip.NCT6796D:
                    case Chip.NCT6796DR:
                    case Chip.NCT6797D:
                    case Chip.NCT6798D: 
                    {
                        if (_temperaturesSource[i].Register == 0)
                        {
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} skipped, address 0.", i);
                            continue;
                        }

                        int value = (sbyte)ReadByte(_temperaturesSource[i].Register) << 1;
                        System.Diagnostics.Debug.WriteLine("Temperature register {0} at 0x{1:X3} value (integer): {2}/2", i, ts.Register, value);
                        if (_temperaturesSource[i].HalfBit > 0)
                        {
                            value |= (ReadByte(_temperaturesSource[i].HalfRegister) >> ts.HalfBit) & 0x1;
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} value updated from 0x{1:X3} (fractional): {2}/2", i, ts.HalfRegister, value);
                        }

                        SourceNct67Xxd source;
                        if (ts.SourceRegister > 0)
                        {
                            source = (SourceNct67Xxd)(ReadByte(ts.SourceRegister) & 0x1F);
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} source at 0x{1:X3}: {2:G} ({2:D})", i, ts.SourceRegister, source);
                        }
                        else
                        {
                            source = (SourceNct67Xxd)ts.Source;
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} source register is 0, source set to: {1:G} ({1:D})", i, source);
                        }
                        
                        // Skip reading when already filled, because later values are without fractional
                        if ((temperatureSourceMask & (1L << (byte)source)) > 0)
                        {
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} discarded, because source seen before.", i);
                            continue;
                        }
                        
                        float? temperature = 0.5f * value;
                        System.Diagnostics.Debug.WriteLine("Temperature register {0} final temperature: {1}.", i, temperature);
                        if (temperature > 125 || temperature < -55)
                        {
                            temperature = null;
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} discarded: Out of range.", i);
                        }
                        else{
                            temperatureSourceMask |= 1L << (byte)source;
                            System.Diagnostics.Debug.WriteLine("Temperature register {0} accepted.", i);
                        }

                        for (int j = 0; j < Temperatures.Length; j++)
                        {
                            if ((SourceNct67Xxd)_temperaturesSource[j].Source == source)
                            {
                                Temperatures[j] = temperature;
                                System.Diagnostics.Debug.WriteLine("Temperature register {0}, value from source {1:G} ({1:D}), written at position {2}.", i, _temperaturesSource[j].Source, j);
                            }
                        }
                        break;
                    }
                    default:
                    {
                        int value = (sbyte)ReadByte(ts.Register) << 1;
                        if (ts.HalfBit > 0)
                        {
                            value |= (ReadByte(ts.HalfRegister) >> ts.HalfBit) & 0x1;
                        }

                        SourceNct67Xxd source = (SourceNct67Xxd)ReadByte(ts.SourceRegister);
                        temperatureSourceMask |= 1L << (byte)source;

                        float? temperature = 0.5f * value;
                        if (temperature > 125 || temperature < -55)
                            temperature = null;

                        for (int j = 0; j < Temperatures.Length; j++)
                        {
                            if ((SourceNct67Xxd)_temperaturesSource[j].Source == source)
                                Temperatures[j] = temperature;
                        }
                        break;
                    }
                }
            }

            for (int i = 0; i < _temperaturesSource.Length; i++)
            {
                TemperatureSourceData ts = _temperaturesSource[i];
                if (!ts.AlternateRegister.HasValue)
                {
                    System.Diagnostics.Debug.WriteLine("Alternate temperature register for temperature {0}, {1:G} ({1:D}), skipped, because address is null.", i, ts.Source);
                    continue;
                }

                if ((temperatureSourceMask & (1L << (byte)(SourceNct67Xxd)ts.Source)) > 0)
                {
                    System.Diagnostics.Debug.WriteLine("Alternate temperature register for temperature {0}, {1:G} ({1:D}), at 0x{2:X3} skipped, because value already set.", i, ts.Source, ts.AlternateRegister.Value);
                    continue;
                }


                float? temperature = (sbyte)ReadByte(ts.AlternateRegister.Value);
                System.Diagnostics.Debug.WriteLine("Alternate temperature register for temperature {0}, {1:G} ({1:D}), at 0x{2:X3} final temperature: {3}.", i, ts.Source, ts.AlternateRegister.Value, temperature);

                if (temperature > 125 || temperature <= 0)
                {
                    temperature = null;
                    System.Diagnostics.Debug.WriteLine("Alternate Temperature register for temperature {0}, {1:G} ({1:D}), discarded: Out of range.", i, ts.Source);
                }

                Temperatures[i] = temperature;
            }

            for (int i = 0; i < Fans.Length; i++)
            {
                if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
                {
                    if (_fanCountRegister != null)
                    {
                        byte high = ReadByte(_fanCountRegister[i]);
                        byte low = ReadByte((ushort)(_fanCountRegister[i] + 1));

                        int count = (high << 5) | (low & 0x1F);
                        if (count < _maxFanCount)
                        {
                            if (count >= _minFanCount)
                            {
                                Fans[i] = 1.35e6f / count;
                            }
                            else
                            {
                                Fans[i] = null;
                            }
                        }
                        else
                        {
                            Fans[i] = 0;
                        }
                    }
                    else
                    {
                        byte high = ReadByte(_fanRpmRegister[i]);
                        byte low = ReadByte((ushort)(_fanRpmRegister[i] + 1));
                        int value = (high << 8) | low;

                        Fans[i] = value > _minFanRpm ? value : 0;
                    }
                }
                else
                {
                    int value = (ReadByte(_fanRpmRegister[i]) << 8) | ReadByte((ushort)(_fanRpmRegister[i] + 1));
                    Fans[i] = value;
                }
            }

            for (int i = 0; i < Controls.Length; i++)
            {
                if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
                {
                    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);
                }
            }

            Ring0.ReleaseIsaBusMutex();
        }

        public string GetReport()
        {
            StringBuilder r = new StringBuilder();

            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 (!Ring0.WaitIsaBusMutex(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 != Chip.NCT6687D && Chip != Chip.NCT6683D)
            {
                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();

            Ring0.ReleaseIsaBusMutex();

            return r.ToString();
        }

        private byte ReadByte(ushort address)
        {
            if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
            {
                byte bank = (byte)(address >> 8);
                byte register = (byte)(address & 0xFF);
                Ring0.WriteIoPort(_port + ADDRESS_REGISTER_OFFSET, BANK_SELECT_REGISTER);
                Ring0.WriteIoPort(_port + DATA_REGISTER_OFFSET, bank);
                Ring0.WriteIoPort(_port + ADDRESS_REGISTER_OFFSET, register);
                return Ring0.ReadIoPort(_port + DATA_REGISTER_OFFSET);
            }

            byte page = (byte)(address >> 8);
            byte index = (byte)(address & 0xFF);
            Ring0.WriteIoPort(_port + EC_SPACE_PAGE_REGISTER_OFFSET, EC_SPACE_PAGE_SELECT);
            Ring0.WriteIoPort(_port + EC_SPACE_PAGE_REGISTER_OFFSET, page);
            Ring0.WriteIoPort(_port + EC_SPACE_INDEX_REGISTER_OFFSET, index);
            return Ring0.ReadIoPort(_port + EC_SPACE_DATA_REGISTER_OFFSET);
        }

        private void WriteByte(ushort address, byte value)
        {
            if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
            {
                byte bank = (byte)(address >> 8);
                byte register = (byte)(address & 0xFF);
                Ring0.WriteIoPort(_port + ADDRESS_REGISTER_OFFSET, BANK_SELECT_REGISTER);
                Ring0.WriteIoPort(_port + DATA_REGISTER_OFFSET, bank);
                Ring0.WriteIoPort(_port + ADDRESS_REGISTER_OFFSET, register);
                Ring0.WriteIoPort(_port + DATA_REGISTER_OFFSET, value);
            }
            else
            {
                byte page = (byte)(address >> 8);
                byte index = (byte)(address & 0xFF);
                Ring0.WriteIoPort(_port + EC_SPACE_PAGE_REGISTER_OFFSET, EC_SPACE_PAGE_SELECT);
                Ring0.WriteIoPort(_port + EC_SPACE_PAGE_REGISTER_OFFSET, page);
                Ring0.WriteIoPort(_port + EC_SPACE_INDEX_REGISTER_OFFSET, index);
                Ring0.WriteIoPort(_port + EC_SPACE_DATA_REGISTER_OFFSET, value);
            }
        }

        private bool IsNuvotonVendor()
        {
            return Chip == Chip.NCT6687D || Chip == Chip.NCT6683D || ((ReadByte(VENDOR_ID_HIGH_REGISTER) << 8) | ReadByte(VENDOR_ID_LOW_REGISTER)) == NUVOTON_VENDOR_ID;
        }

        private void SaveDefaultFanControl(int index)
        {
            if (!_restoreDefaultFanControlRequired[index])
            {
                if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
                {
                    _initialFanControlMode[index] = ReadByte(FAN_CONTROL_MODE_REG[index]);
                }
                else
                {
                    byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
                    byte bitMask = (byte)(0x01 << index);
                    _initialFanControlMode[index] = (byte)(mode & bitMask);
                }

                _initialFanPwmCommand[index] = ReadByte(FAN_PWM_COMMAND_REG[index]);
                _restoreDefaultFanControlRequired[index] = true;
            }
        }

        private void RestoreDefaultFanControl(int index)
        {
            if (_restoreDefaultFanControlRequired[index])
            {
                if (Chip != Chip.NCT6687D && Chip != Chip.NCT6683D)
                {
                    WriteByte(FAN_CONTROL_MODE_REG[index], _initialFanControlMode[index]);
                    WriteByte(FAN_PWM_COMMAND_REG[index], _initialFanPwmCommand[index]);
                }
                else
                {
                    byte mode = ReadByte(FAN_CONTROL_MODE_REG[index]);
                    mode = (byte)(mode & ~_initialFanControlMode[index]);
                    WriteByte(FAN_CONTROL_MODE_REG[index], mode);

                    WriteByte(FAN_PWM_REQUEST_REG[index], 0x80);
                    Thread.Sleep(50);

                    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 != Chip.NCT6791D &&
                Chip != Chip.NCT6792D &&
                Chip != Chip.NCT6792DA &&
                Chip != Chip.NCT6793D &&
                Chip != Chip.NCT6795D &&
                Chip != Chip.NCT6796D &&
                Chip != Chip.NCT6796DR &&
                Chip != Chip.NCT6797D &&
                Chip != Chip.NCT6798D)
            {
                return;
            }

            // the lock is disabled already if the vendor ID can be read
            if (IsNuvotonVendor())
                return;


            _lpcPort.WinbondNuvotonFintekEnter();
            _lpcPort.NuvotonDisableIOSpaceLock();
            _lpcPort.WinbondNuvotonFintekExit();
        }

        [SuppressMessage("ReSharper", "InconsistentNaming")]
        private enum SourceNct6771F : byte
        {
            SYSTIN = 1,
            CPUTIN = 2,
            AUXTIN = 3,
            PECI_0 = 5
        }

        [SuppressMessage("ReSharper", "InconsistentNaming")]
        private enum SourceNct6776F : byte
        {
            SYSTIN = 1,
            CPUTIN = 2,
            AUXTIN = 3,
            PECI_0 = 12
        }

        [SuppressMessage("ReSharper", "InconsistentNaming")]
        private enum SourceNct67Xxd : byte
        {
            SYSTIN = 1,
            CPUTIN = 2,
            AUXTIN0 = 3,
            AUXTIN1 = 4,
            AUXTIN2 = 5,
            AUXTIN3 = 6,
            AUXTIN4 = 7,
            SMBUSMASTER0 = 8,
            SMBUSMASTER1 = 9,
            PECI_0 = 16,
            PECI_1 = 17,
            PCH_CHIP_CPU_MAX_TEMP = 18,
            PCH_CHIP_TEMP = 19,
            PCH_CPU_TEMP = 20,
            PCH_MCH_TEMP = 21,
            AGENT0_DIMM0 = 22,
            AGENT0_DIMM1 = 23,
            AGENT1_DIMM0 = 24,
            AGENT1_DIMM1 = 25,
            BYTE_TEMP0 = 26,
            BYTE_TEMP1 = 27,
            PECI_0_CAL = 28,
            PECI_1_CAL = 29,
            VIRTUAL_TEMP = 31
        }

        [SuppressMessage("ReSharper", "InconsistentNaming")]
        private enum SourceNct610X : byte
        {
            SYSTIN = 1,
            CPUTIN = 2,
            AUXTIN = 3,
            PECI_0 = 12
        }

        // ReSharper disable InconsistentNaming
        private const uint ADDRESS_REGISTER_OFFSET = 0x05;
        private const byte BANK_SELECT_REGISTER = 0x4E;
        private const uint DATA_REGISTER_OFFSET = 0x06;

        // NCT668X
        private const uint EC_SPACE_PAGE_REGISTER_OFFSET = 0x04;
        private const uint EC_SPACE_INDEX_REGISTER_OFFSET = 0x05;
        private const uint EC_SPACE_DATA_REGISTER_OFFSET = 0x06;
        private const byte EC_SPACE_PAGE_SELECT = 0xFF;

        private const ushort NUVOTON_VENDOR_ID = 0x5CA3;

        private readonly ushort[] FAN_CONTROL_MODE_REG;
        private readonly ushort[] FAN_PWM_COMMAND_REG;
        private readonly ushort[] FAN_PWM_OUT_REG;
        private readonly ushort[] FAN_PWM_REQUEST_REG;

        private readonly ushort VENDOR_ID_HIGH_REGISTER;
        private readonly ushort VENDOR_ID_LOW_REGISTER;

        // ReSharper restore InconsistentNaming
    }
}
