moel@245: /*
moel@245:  
moel@344:   This Source Code Form is subject to the terms of the Mozilla Public
moel@344:   License, v. 2.0. If a copy of the MPL was not distributed with this
moel@344:   file, You can obtain one at http://mozilla.org/MPL/2.0/.
moel@245:  
moel@355:   Copyright (C) 2010-2012 Michael Möller <mmoeller@openhardwaremonitor.org>
moel@344: 	
moel@245: */
moel@245: 
moel@355: using System;
moel@245: using System.Globalization;
moel@245: using System.Text;
moel@245: 
moel@245: namespace OpenHardwareMonitor.Hardware.LPC {
moel@245:   internal class NCT677X : ISuperIO {
moel@245: 
moel@245:     private readonly ushort port;
moel@245:     private readonly byte revision;
moel@245: 
moel@245:     private readonly Chip chip;
moel@245: 
moel@355:     private readonly float?[] voltages;
moel@355:     private readonly float?[] temperatures;
moel@355:     private readonly float?[] fans ;
moel@355:     private readonly float?[] controls;
moel@245: 
moel@245:     // Hardware Monitor
moel@245:     private const uint ADDRESS_REGISTER_OFFSET = 0x05;
moel@245:     private const uint DATA_REGISTER_OFFSET = 0x06;
moel@245:     private const byte BANK_SELECT_REGISTER = 0x4E;
moel@245: 
moel@245:     private byte ReadByte(ushort address) {
moel@245:       byte bank = (byte)(address >> 8);
moel@245:       byte register = (byte)(address & 0xFF);
moel@245:       Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, BANK_SELECT_REGISTER);
moel@245:       Ring0.WriteIoPort(port + DATA_REGISTER_OFFSET, bank);
moel@245:       Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, register);
moel@245:       return Ring0.ReadIoPort(port + DATA_REGISTER_OFFSET);
moel@323:     }
moel@323: 
moel@323:     private void WriteByte(ushort address, byte value) {
moel@323:       byte bank = (byte)(address >> 8);
moel@323:       byte register = (byte)(address & 0xFF);
moel@323:       Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, BANK_SELECT_REGISTER);
moel@323:       Ring0.WriteIoPort(port + DATA_REGISTER_OFFSET, bank);
moel@323:       Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, register);
moel@323:       Ring0.WriteIoPort(port + DATA_REGISTER_OFFSET, value);
moel@356:     }
moel@245: 
moel@245:     // Consts 
moel@245:     private const ushort NUVOTON_VENDOR_ID = 0x5CA3;
moel@245: 
moel@245:     // Hardware Monitor Registers    
moel@245:     private const ushort VENDOR_ID_HIGH_REGISTER = 0x804F;
moel@355:     private const ushort VENDOR_ID_LOW_REGISTER = 0x004F;  
moel@355:     
moel@355:     private readonly ushort[] FAN_PWM_OUT_REG = 
moel@355:       { 0x001, 0x003, 0x011, 0x013, 0x015 };
moel@355:     private readonly ushort[] FAN_PWM_COMMAND_REG = 
moel@355:       { 0x109, 0x209, 0x309, 0x809, 0x909 };
moel@355:     private readonly ushort[] FAN_CONTROL_MODE_REG = 
moel@355:       { 0x102, 0x202, 0x302, 0x802, 0x902 };
moel@245: 
moel@355:     private readonly ushort fanRpmBaseRegister;
moel@265:     private readonly int minFanRPM;
moel@245: 
moel@355:     private bool[] restoreDefaultFanControlRequired = new bool[5];       
moel@355:     private byte[] initialFanControlMode = new byte[5];
moel@355:     private byte[] initialFanPwmCommand = new byte[5];
moel@355: 
moel@355:     private readonly ushort[] voltageRegisters;
moel@355:     private readonly ushort voltageVBatRegister;
moel@355: 
moel@355:     private readonly byte[] temperaturesSource;
moel@355: 
moel@355:     private readonly ushort[] temperatureRegister;
moel@355:     private readonly ushort[] temperatureHalfRegister;
moel@355:     private readonly int[] temperatureHalfBit;  
moel@355:     private readonly ushort[] temperatureSourceRegister;        
moel@355: 
moel@355:     private readonly ushort?[] alternateTemperatureRegister;
moel@323: 
moel@276:     private enum SourceNCT6771F : byte {
moel@245:       SYSTIN = 1,
moel@245:       CPUTIN = 2,
moel@245:       AUXTIN = 3,
moel@245:       SMBUSMASTER = 4,
moel@276:       PECI_0 = 5, 
moel@276:       PECI_1 = 6, 
moel@276:       PECI_2 = 7,
moel@276:       PECI_3 = 8,
moel@276:       PECI_4 = 9,
moel@276:       PECI_5 = 10,
moel@276:       PECI_6 = 11,
moel@276:       PECI_7 = 12,
moel@245:       PCH_CHIP_CPU_MAX_TEMP = 13,
moel@245:       PCH_CHIP_TEMP = 14,
moel@245:       PCH_CPU_TEMP = 15,
moel@245:       PCH_MCH_TEMP = 16, 
moel@245:       PCH_DIM0_TEMP = 17,
moel@245:       PCH_DIM1_TEMP = 18,
moel@245:       PCH_DIM2_TEMP = 19,
moel@245:       PCH_DIM3_TEMP = 20
moel@245:     }
moel@245: 
moel@276:     private enum SourceNCT6776F : byte {
moel@276:       SYSTIN = 1,
moel@276:       CPUTIN = 2,
moel@276:       AUXTIN = 3,
moel@276:       SMBUSMASTER_0 = 4,
moel@276:       SMBUSMASTER_1 = 5,
moel@276:       SMBUSMASTER_2 = 6,
moel@276:       SMBUSMASTER_3 = 7,
moel@276:       SMBUSMASTER_4 = 8,
moel@276:       SMBUSMASTER_5 = 9,
moel@276:       SMBUSMASTER_6 = 10,
moel@276:       SMBUSMASTER_7 = 11,
moel@276:       PECI_0 = 12,
moel@276:       PECI_1 = 13,
moel@276:       PCH_CHIP_CPU_MAX_TEMP = 14,
moel@276:       PCH_CHIP_TEMP = 15,
moel@276:       PCH_CPU_TEMP = 16,
moel@276:       PCH_MCH_TEMP = 17,
moel@276:       PCH_DIM0_TEMP = 18,
moel@276:       PCH_DIM1_TEMP = 19,
moel@276:       PCH_DIM2_TEMP = 20,
moel@276:       PCH_DIM3_TEMP = 21,
moel@276:       BYTE_TEMP = 22
moel@276:     }
moel@276: 
moel@355:     private enum SourceNCT6779D : byte {
moel@355:       SYSTIN = 1,
moel@355:       CPUTIN = 2,
moel@355:       AUXTIN0 = 3,
moel@355:       AUXTIN1 = 4,
moel@355:       AUXTIN2 = 5,
moel@355:       AUXTIN3 = 6,      
moel@355:       SMBUSMASTER_0 = 8,
moel@355:       SMBUSMASTER_1 = 9,
moel@355:       SMBUSMASTER_2 = 10,
moel@355:       SMBUSMASTER_3 = 11,
moel@355:       SMBUSMASTER_4 = 12,
moel@355:       SMBUSMASTER_5 = 13,
moel@355:       SMBUSMASTER_6 = 14,
moel@355:       SMBUSMASTER_7 = 15,
moel@355:       PECI_0 = 16,
moel@355:       PECI_1 = 17,
moel@355:       PCH_CHIP_CPU_MAX_TEMP = 18,
moel@355:       PCH_CHIP_TEMP = 19,
moel@355:       PCH_CPU_TEMP = 20,
moel@355:       PCH_MCH_TEMP = 21,
moel@355:       PCH_DIM0_TEMP = 22,
moel@355:       PCH_DIM1_TEMP = 23,
moel@355:       PCH_DIM2_TEMP = 24,
moel@355:       PCH_DIM3_TEMP = 25,
moel@355:       BYTE_TEMP = 26
moel@355:     }
moel@355: 
moel@245:     public NCT677X(Chip chip, byte revision, ushort port) {
moel@245:       this.chip = chip;
moel@245:       this.revision = revision;
moel@245:       this.port = port;
moel@245: 
moel@245:       if (!IsNuvotonVendor())
moel@265:         return;
moel@265: 
moel@265:       switch (chip) {
moel@355:         case Chip.NCT6771F:
moel@355:         case Chip.NCT6776F:          
moel@355:           if (chip == Chip.NCT6771F) {
moel@355:             fans = new float?[4];
moel@355: 
moel@355:             // min value RPM value with 16-bit fan counter
moel@355:             minFanRPM = (int)(1.35e6 / 0xFFFF);
moel@355: 
moel@355:             temperaturesSource = new byte[] {
moel@355:               (byte)SourceNCT6771F.PECI_0,
moel@355:               (byte)SourceNCT6771F.CPUTIN,
moel@355:               (byte)SourceNCT6771F.AUXTIN,
moel@355:               (byte)SourceNCT6771F.SYSTIN
moel@355:             };            
moel@355:           } else {
moel@355:             fans = new float?[5];
moel@355: 
moel@355:             // min value RPM value with 13-bit fan counter
moel@355:             minFanRPM = (int)(1.35e6 / 0x1FFF);
moel@355: 
moel@355:             temperaturesSource = new byte[] {
moel@355:               (byte)SourceNCT6776F.PECI_0,
moel@355:               (byte)SourceNCT6776F.CPUTIN,
moel@355:               (byte)SourceNCT6776F.AUXTIN,
moel@355:               (byte)SourceNCT6776F.SYSTIN 
moel@355:             };
moel@355:           }
moel@355:           fanRpmBaseRegister = 0x656;
moel@355: 
moel@355:           controls = new float?[3];
moel@355: 
moel@355:           voltages = new float?[9];
moel@355:           voltageRegisters = new ushort[] 
moel@355:             { 0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x550, 0x551 };
moel@355:           voltageVBatRegister = 0x551;
moel@355: 
moel@355:           temperatures = new float?[4];
moel@355:           temperatureRegister = new ushort[]
moel@355:             { 0x027, 0x073, 0x075, 0x077, 0x150, 0x250, 0x62B, 0x62C, 0x62D };
moel@355:           temperatureHalfRegister = new ushort[]
moel@355:             { 0, 0x074, 0x076, 0x078, 0x151, 0x251, 0x62E, 0x62E, 0x62E };
moel@355:           temperatureHalfBit = new int[] 
moel@355:             { -1, 7, 7, 7, 7, 7, 0, 1, 2 };
moel@355:           temperatureSourceRegister = new ushort[]
moel@355:             { 0x621, 0x100, 0x200, 0x300, 0x622, 0x623, 0x624, 0x625, 0x626 };
moel@355: 
moel@355:           alternateTemperatureRegister = new ushort?[] 
moel@355:             { null, null, null, null };
moel@265:           break;
moel@356: 
moel@355:         case Chip.NCT6779D:
moel@265:           fans = new float?[5];
moel@355:           fanRpmBaseRegister = 0x4C0;
moel@355: 
moel@265:           // min value RPM value with 13-bit fan counter
moel@265:           minFanRPM = (int)(1.35e6 / 0x1FFF);
moel@355: 
moel@355:           controls = new float?[5];
moel@355: 
moel@355:           voltages = new float?[15];
moel@355:           voltageRegisters = new ushort[] 
moel@355:             { 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, 0x488, 
moel@355:               0x489, 0x48A, 0x48B, 0x48C, 0x48D, 0x48E };
moel@355:           voltageVBatRegister = 0x488;
moel@355: 
moel@355:           temperatures = new float?[7];
moel@355:           temperaturesSource = new byte[] {
moel@355:             (byte)SourceNCT6779D.PECI_0,
moel@355:             (byte)SourceNCT6779D.CPUTIN,
moel@355:             (byte)SourceNCT6779D.SYSTIN,
moel@355:             (byte)SourceNCT6779D.AUXTIN0,
moel@355:             (byte)SourceNCT6779D.AUXTIN1,
moel@355:             (byte)SourceNCT6779D.AUXTIN2,
moel@355:             (byte)SourceNCT6779D.AUXTIN3
moel@355:           };
moel@355: 
moel@355:           temperatureRegister = new ushort[]
moel@355:             { 0x027, 0x073, 0x075, 0x077, 0x079, 0x07B, 0x150 };
moel@355:           temperatureHalfRegister = new ushort[]
moel@355:             { 0, 0x074, 0x076, 0x078, 0x07A, 0x07C, 0x151 };              
moel@355:           temperatureHalfBit = new int[]
moel@355:             { -1, 7, 7, 7, 7, 7, 7 };
moel@355:           temperatureSourceRegister = new ushort[] 
moel@355:             { 0x621, 0x100, 0x200, 0x300, 0x800, 0x900, 0x622 };
moel@355: 
moel@355:           alternateTemperatureRegister = new ushort?[] 
moel@355:             {null, 0x491, 0x490, 0x492, 0x493, 0x494, 0x495 };
moel@355: 
moel@265:           break;        
moel@265:       }
moel@245:     }
moel@245: 
moel@245:     private bool IsNuvotonVendor() {
moel@245:       return ((ReadByte(VENDOR_ID_HIGH_REGISTER) << 8) |
moel@245:         ReadByte(VENDOR_ID_LOW_REGISTER)) == NUVOTON_VENDOR_ID;
moel@245:     }
moel@245: 
moel@245:     public byte? ReadGPIO(int index) {
moel@245:       return null;
moel@245:     }
moel@245: 
moel@245:     public void WriteGPIO(int index, byte value) { }
moel@245: 
moel@323: 
moel@323:     private void SaveDefaultFanControl(int index) {
moel@323:       if (!restoreDefaultFanControlRequired[index]) {
moel@323:         initialFanControlMode[index] = ReadByte(FAN_CONTROL_MODE_REG[index]);
moel@323:         initialFanPwmCommand[index] = ReadByte(FAN_PWM_COMMAND_REG[index]);
moel@323:         restoreDefaultFanControlRequired[index] = true;
moel@323:       }
moel@323:     }
moel@323: 
moel@323:     private void RestoreDefaultFanControl(int index) {
moel@323:       if (restoreDefaultFanControlRequired[index]) {
moel@323:         WriteByte(FAN_CONTROL_MODE_REG[index], initialFanControlMode[index]);
moel@323:         WriteByte(FAN_PWM_COMMAND_REG[index], initialFanPwmCommand[index]);
moel@323:         restoreDefaultFanControlRequired[index] = false;
moel@323:       }
moel@323:     }
moel@323: 
moel@323:     public void SetControl(int index, byte? value) {
moel@355:       if (index < 0 || index >= controls.Length)
moel@355:         throw new ArgumentOutOfRangeException("index");
moel@355: 
moel@323:       if (!Ring0.WaitIsaBusMutex(10))
moel@323:         return;
moel@323: 
moel@323:       if (value.HasValue) {
moel@323:         SaveDefaultFanControl(index);
moel@323: 
moel@323:         // set manual mode
moel@323:         WriteByte(FAN_CONTROL_MODE_REG[index], 0);
moel@323: 
moel@323:         // set output value
moel@323:         WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);  
moel@323:       } else {
moel@323:         RestoreDefaultFanControl(index);
moel@323:       }
moel@323: 
moel@323:       Ring0.ReleaseIsaBusMutex();
moel@323:     }   
moel@323: 
moel@245:     public Chip Chip { get { return chip; } }
moel@245:     public float?[] Voltages { get { return voltages; } }
moel@245:     public float?[] Temperatures { get { return temperatures; } }
moel@245:     public float?[] Fans { get { return fans; } }
moel@323:     public float?[] Controls { get { return controls; } }
moel@245: 
moel@245:     public void Update() {
moel@245:       if (!Ring0.WaitIsaBusMutex(10))
moel@245:         return;
moel@245: 
moel@245:       for (int i = 0; i < voltages.Length; i++) {
moel@355:         float value = 0.008f * ReadByte(voltageRegisters[i]);
moel@245:         bool valid = value > 0;
moel@245: 
moel@245:         // check if battery voltage monitor is enabled
moel@355:         if (valid && voltageRegisters[i] == voltageVBatRegister) 
moel@245:           valid = (ReadByte(0x005D) & 0x01) > 0;
moel@245: 
moel@245:         voltages[i] = valid ? value : (float?)null;
moel@245:       }
moel@245: 
moel@355:       int temperatureSourceMask = 0;
moel@355:       for (int i = temperatureRegister.Length - 1; i >= 0 ; i--) {
moel@355:         int value = ((sbyte)ReadByte(temperatureRegister[i])) << 1;
moel@355:         if (temperatureHalfBit[i] > 0) {
moel@355:           value |= ((ReadByte(temperatureHalfRegister[i]) >>
moel@355:             temperatureHalfBit[i]) & 0x1);
moel@245:         }
moel@245: 
moel@355:         byte source = ReadByte(temperatureSourceRegister[i]);
moel@355:         temperatureSourceMask |= 1 << source;
moel@245: 
moel@246:         float? temperature = 0.5f * value;
moel@246:         if (temperature > 125 || temperature < -55)
moel@246:           temperature = null;
moel@246: 
moel@355:         for (int j = 0; j < temperatures.Length; j++) 
moel@355:           if (temperaturesSource[j] == source)
moel@355:             temperatures[j] = temperature; 
moel@355:       }
moel@355:       for (int i = 0; i < alternateTemperatureRegister.Length; i++) {
moel@355:         if (!alternateTemperatureRegister[i].HasValue)
moel@355:           continue;
moel@355: 
moel@355:         if ((temperatureSourceMask & (1 << temperaturesSource[i])) > 0)
moel@355:           continue;
moel@355: 
moel@356:         float? temperature = (sbyte)
moel@356:           ReadByte(alternateTemperatureRegister[i].Value);
moel@356: 
moel@356:         if (temperature > 125 || temperature < -55)
moel@356:           temperature = null;
moel@356: 
moel@356:         temperatures[i] = temperature;
moel@245:       }
moel@245: 
moel@245:       for (int i = 0; i < fans.Length; i++) {
moel@355:         byte high = ReadByte((ushort)(fanRpmBaseRegister + (i << 1)));
moel@355:         byte low = ReadByte((ushort)(fanRpmBaseRegister + (i << 1) + 1));
moel@265:         int value = (high << 8) | low;
moel@265: 
moel@265:         fans[i] = value > minFanRPM ? value : 0;
moel@245:       }
moel@245: 
moel@323:       for (int i = 0; i < controls.Length; i++) {
moel@323:         int value = ReadByte(FAN_PWM_OUT_REG[i]);
moel@323:         controls[i] = value / 2.55f;
moel@323:       }
moel@323: 
moel@245:       Ring0.ReleaseIsaBusMutex();
moel@245:     }
moel@245: 
moel@245:     public string GetReport() {
moel@245:       StringBuilder r = new StringBuilder();
moel@245: 
moel@245:       r.AppendLine("LPC " + this.GetType().Name);
moel@245:       r.AppendLine();
moel@245:       r.Append("Chip ID: 0x"); r.AppendLine(chip.ToString("X"));
moel@245:       r.Append("Chip revision: 0x");
moel@245:       r.AppendLine(revision.ToString("X", CultureInfo.InvariantCulture));
moel@245:       r.Append("Base Adress: 0x");
moel@245:       r.AppendLine(port.ToString("X4", CultureInfo.InvariantCulture));
moel@245:       r.AppendLine();
moel@245: 
moel@245:       if (!Ring0.WaitIsaBusMutex(100))
moel@245:         return r.ToString();
moel@245: 
moel@246:       ushort[] addresses = new ushort[] { 
moel@246:         0x000, 0x010, 0x020, 0x030, 0x040, 0x050, 0x060, 0x070,
moel@246:         0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 
moel@246:         0x200,        0x220, 0x230, 0x240, 0x250,
moel@246:         0x300,        0x320, 0x330, 0x340, 
moel@355:         0x400, 0x410, 0x420,        0x440, 0x450, 0x460, 0x480, 0x490, 0x4C0,
moel@246:         0x500,                             0x550, 
moel@246:         0x600, 0x610 ,0x620, 0x630, 0x640, 0x650, 0x660, 0x670, 
moel@355:         0x800,
moel@355:         0x900,
moel@246:         0xA00, 0xA10, 0xA20, 0xA30,        0xA50, 0xA60, 0xA70, 
moel@246:         0xB00, 0xB10, 0xB20, 0xB30,        0xB50, 0xB60, 0xB70, 
moel@246:         0xC00, 0xC10, 0xC20, 0xC30,        0xC50, 0xC60, 0xC70,
moel@246:         0xD00, 0xD10, 0xD20, 0xD30,        0xD50, 0xD60, 
moel@246:         0xE00, 0xE10, 0xE20, 0xE30, 
moel@246:         0xF00, 0xF10, 0xF20, 0xF30};
moel@246: 
moel@245:       r.AppendLine("Hardware Monitor Registers");
moel@245:       r.AppendLine();
moel@245:       r.AppendLine("       00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
moel@245:       r.AppendLine();
moel@246:       foreach (ushort address in addresses) {
moel@245:           r.Append(" ");
moel@246:           r.Append(address.ToString("X3", CultureInfo.InvariantCulture));
moel@245:           r.Append("  ");
moel@246:           for (ushort j = 0; j <= 0xF; j++) {
moel@245:             r.Append(" ");
moel@246:             r.Append(ReadByte((ushort)(address | j)).ToString(
moel@245:               "X2", CultureInfo.InvariantCulture));
moel@245:           }
moel@245:           r.AppendLine();
moel@245:       }
moel@245:       r.AppendLine();
moel@245: 
moel@245:       Ring0.ReleaseIsaBusMutex();
moel@245: 
moel@245:       return r.ToString();
moel@245:     }
moel@245:   }
moel@245: }