Hardware/LPC/LPCIO.cs
author moel.mich
Sun, 08 May 2011 22:10:13 +0000
changeset 279 6bce967ba1b5
parent 265 961c07a3bd78
child 296 414783d1cda9
permissions -rw-r--r--
Fixed the bus and core clock reading on AMD family 10h model Ah CPUs. The new "Core Performance Boost" feature of these CPUs resulted in very low accuracy of the bus speed (and as a consequence also an inaccurate TSC multiplier). This fixed Issue 205.
moel@110
     1
/*
moel@110
     2
  
moel@110
     3
  Version: MPL 1.1/GPL 2.0/LGPL 2.1
moel@110
     4
moel@110
     5
  The contents of this file are subject to the Mozilla Public License Version
moel@110
     6
  1.1 (the "License"); you may not use this file except in compliance with
moel@110
     7
  the License. You may obtain a copy of the License at
moel@110
     8
 
moel@110
     9
  http://www.mozilla.org/MPL/
moel@110
    10
moel@110
    11
  Software distributed under the License is distributed on an "AS IS" basis,
moel@110
    12
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
moel@110
    13
  for the specific language governing rights and limitations under the License.
moel@110
    14
moel@110
    15
  The Original Code is the Open Hardware Monitor code.
moel@110
    16
moel@110
    17
  The Initial Developer of the Original Code is 
moel@110
    18
  Michael Möller <m.moeller@gmx.ch>.
moel@265
    19
  Portions created by the Initial Developer are Copyright (C) 2009-2011
moel@110
    20
  the Initial Developer. All Rights Reserved.
moel@110
    21
moel@110
    22
  Contributor(s):
moel@110
    23
moel@110
    24
  Alternatively, the contents of this file may be used under the terms of
moel@110
    25
  either the GNU General Public License Version 2 or later (the "GPL"), or
moel@110
    26
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
moel@110
    27
  in which case the provisions of the GPL or the LGPL are applicable instead
moel@110
    28
  of those above. If you wish to allow use of your version of this file only
moel@110
    29
  under the terms of either the GPL or the LGPL, and not to allow others to
moel@110
    30
  use your version of this file under the terms of the MPL, indicate your
moel@110
    31
  decision by deleting the provisions above and replace them with the notice
moel@110
    32
  and other provisions required by the GPL or the LGPL. If you do not delete
moel@110
    33
  the provisions above, a recipient may use your version of this file under
moel@110
    34
  the terms of any one of the MPL, the GPL or the LGPL.
moel@110
    35
 
moel@110
    36
*/
moel@110
    37
moel@110
    38
using System;
moel@110
    39
using System.Collections.Generic;
moel@166
    40
using System.Globalization;
moel@110
    41
using System.Text;
moel@110
    42
using System.Threading;
moel@110
    43
moel@110
    44
namespace OpenHardwareMonitor.Hardware.LPC {
moel@165
    45
  internal class LPCIO {
moel@110
    46
moel@195
    47
    private readonly List<ISuperIO> superIOs = new List<ISuperIO>();
moel@195
    48
    private readonly StringBuilder report = new StringBuilder();
moel@110
    49
moel@110
    50
    // I/O Ports
moel@195
    51
    private readonly ushort[] REGISTER_PORTS = new ushort[] { 0x2E, 0x4E };
moel@195
    52
    private readonly ushort[] VALUE_PORTS = new ushort[] { 0x2F, 0x4F };
moel@110
    53
moel@110
    54
    private ushort registerPort;
moel@110
    55
    private ushort valuePort;
moel@110
    56
moel@110
    57
    // Registers
moel@110
    58
    private const byte CONFIGURATION_CONTROL_REGISTER = 0x02;
moel@110
    59
    private const byte DEVCIE_SELECT_REGISTER = 0x07;
moel@110
    60
    private const byte CHIP_ID_REGISTER = 0x20;
moel@110
    61
    private const byte CHIP_REVISION_REGISTER = 0x21;
moel@110
    62
    private const byte BASE_ADDRESS_REGISTER = 0x60;
moel@110
    63
moel@110
    64
    private byte ReadByte(byte register) {
moel@236
    65
      Ring0.WriteIoPort(registerPort, register);
moel@236
    66
      return Ring0.ReadIoPort(valuePort);
moel@228
    67
    }
moel@110
    68
moel@110
    69
    private ushort ReadWord(byte register) {
moel@228
    70
      return (ushort)((ReadByte(register) << 8) |
moel@110
    71
        ReadByte((byte)(register + 1)));
moel@110
    72
    }
moel@110
    73
moel@110
    74
    private void Select(byte logicalDeviceNumber) {
moel@236
    75
      Ring0.WriteIoPort(registerPort, DEVCIE_SELECT_REGISTER);
moel@236
    76
      Ring0.WriteIoPort(valuePort, logicalDeviceNumber);
moel@110
    77
    }
moel@110
    78
moel@169
    79
    private void ReportUnknownChip(string type, int chip) {
moel@169
    80
      report.Append("Chip ID: Unknown ");
moel@169
    81
      report.Append(type);
moel@169
    82
      report.Append(" with ID 0x");
moel@169
    83
      report.Append(chip.ToString("X", CultureInfo.InvariantCulture));
moel@169
    84
      report.Append(" at 0x");
moel@169
    85
      report.Append(registerPort.ToString("X", CultureInfo.InvariantCulture));
moel@169
    86
      report.Append("/0x");
moel@169
    87
      report.AppendLine(valuePort.ToString("X", CultureInfo.InvariantCulture));
moel@169
    88
      report.AppendLine();
moel@110
    89
    }
moel@110
    90
moel@245
    91
    #region Winbond, Nuvoton, Fintek
moel@110
    92
moel@110
    93
    private const byte FINTEK_VENDOR_ID_REGISTER = 0x23;
moel@110
    94
    private const ushort FINTEK_VENDOR_ID = 0x1934;
moel@110
    95
moel@245
    96
    private const byte WINBOND_NUVOTON_HARDWARE_MONITOR_LDN = 0x0B;
moel@110
    97
moel@110
    98
    private const byte F71858_HARDWARE_MONITOR_LDN = 0x02;
moel@110
    99
    private const byte FINTEK_HARDWARE_MONITOR_LDN = 0x04;
moel@110
   100
moel@245
   101
    private void WinbondNuvotonFintekEnter() {
moel@236
   102
      Ring0.WriteIoPort(registerPort, 0x87);
moel@236
   103
      Ring0.WriteIoPort(registerPort, 0x87);
moel@110
   104
    }
moel@110
   105
moel@245
   106
    private void WinbondNuvotonFintekExit() {
moel@236
   107
      Ring0.WriteIoPort(registerPort, 0xAA);
moel@110
   108
    }
moel@110
   109
moel@167
   110
    private bool DetectWinbondFintek() {
moel@245
   111
      WinbondNuvotonFintekEnter();
moel@167
   112
moel@195
   113
      byte logicalDeviceNumber = 0;
moel@167
   114
      byte id = ReadByte(CHIP_ID_REGISTER);
moel@167
   115
      byte revision = ReadByte(CHIP_REVISION_REGISTER);
moel@167
   116
      Chip chip = Chip.Unknown;
moel@167
   117
      switch (id) {
moel@167
   118
        case 0x05:
moel@167
   119
          switch (revision) {
moel@167
   120
            case 0x07:
moel@167
   121
              chip = Chip.F71858;
moel@167
   122
              logicalDeviceNumber = F71858_HARDWARE_MONITOR_LDN;
moel@167
   123
              break;
moel@167
   124
            case 0x41:
moel@167
   125
              chip = Chip.F71882;
moel@167
   126
              logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
moel@167
   127
              break;
moel@167
   128
          } break;
moel@167
   129
        case 0x06:
moel@167
   130
          switch (revision) {
moel@167
   131
            case 0x01:
moel@167
   132
              chip = Chip.F71862;
moel@167
   133
              logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
moel@167
   134
              break;
moel@167
   135
          } break;
moel@167
   136
        case 0x07:
moel@167
   137
          switch (revision) {
moel@167
   138
            case 0x23:
moel@167
   139
              chip = Chip.F71889F;
moel@167
   140
              logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
moel@167
   141
              break;
moel@167
   142
          } break;
moel@167
   143
        case 0x08:
moel@167
   144
          switch (revision) {
moel@167
   145
            case 0x14:
moel@167
   146
              chip = Chip.F71869;
moel@167
   147
              logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
moel@167
   148
              break;
moel@167
   149
          } break;
moel@167
   150
        case 0x09:
moel@167
   151
          switch (revision) {
moel@167
   152
            case 0x09:
moel@167
   153
              chip = Chip.F71889ED;
moel@167
   154
              logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
moel@167
   155
              break;
moel@167
   156
          } break;
moel@167
   157
        case 0x52:
moel@167
   158
          switch (revision) {
moel@167
   159
            case 0x17:
moel@167
   160
            case 0x3A:
moel@167
   161
            case 0x41:
moel@167
   162
              chip = Chip.W83627HF;
moel@245
   163
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   164
              break;
moel@167
   165
          } break;
moel@167
   166
        case 0x82:
moel@167
   167
          switch (revision & 0xF0) {
moel@167
   168
            case 0x80:
moel@167
   169
              chip = Chip.W83627THF;
moel@245
   170
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   171
              break;
moel@167
   172
          } break;
moel@167
   173
        case 0x85:
moel@167
   174
          switch (revision) {
moel@167
   175
            case 0x41:
moel@167
   176
              chip = Chip.W83687THF;
moel@245
   177
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   178
              break;
moel@167
   179
          } break;
moel@167
   180
        case 0x88:
moel@167
   181
          switch (revision & 0xF0) {
moel@167
   182
            case 0x50:
moel@167
   183
            case 0x60:
moel@167
   184
              chip = Chip.W83627EHF;
moel@245
   185
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   186
              break;
moel@167
   187
          } break;
moel@167
   188
        case 0xA0:
moel@167
   189
          switch (revision & 0xF0) {
moel@167
   190
            case 0x20:
moel@167
   191
              chip = Chip.W83627DHG;
moel@245
   192
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   193
              break;
moel@167
   194
          } break;
moel@167
   195
        case 0xA5:
moel@167
   196
          switch (revision & 0xF0) {
moel@167
   197
            case 0x10:
moel@167
   198
              chip = Chip.W83667HG;
moel@245
   199
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   200
              break;
moel@167
   201
          } break;
moel@167
   202
        case 0xB0:
moel@167
   203
          switch (revision & 0xF0) {
moel@167
   204
            case 0x70:
moel@167
   205
              chip = Chip.W83627DHGP;
moel@245
   206
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   207
              break;
moel@167
   208
          } break;
moel@167
   209
        case 0xB3:
moel@167
   210
          switch (revision & 0xF0) {
moel@167
   211
            case 0x50:
moel@167
   212
              chip = Chip.W83667HGB;
moel@245
   213
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@245
   214
              break;
moel@245
   215
          } break;
moel@245
   216
        case 0xB4:
moel@245
   217
          switch (revision & 0xF0) {
moel@245
   218
            case 0x70:
moel@245
   219
              chip = Chip.NCT6771F;
moel@245
   220
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@167
   221
              break;
moel@167
   222
          } break;
moel@265
   223
        case 0xC3:
moel@265
   224
          switch (revision & 0xF0) {
moel@265
   225
            case 0x30:
moel@265
   226
              chip = Chip.NCT6776F;
moel@265
   227
              logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
moel@265
   228
              break;
moel@265
   229
          } break;
moel@167
   230
      }
moel@167
   231
      if (chip == Chip.Unknown) {
moel@167
   232
        if (id != 0 && id != 0xff) {
moel@245
   233
          WinbondNuvotonFintekExit();
moel@167
   234
moel@245
   235
          ReportUnknownChip("Winbond / Nuvoton / Fintek", 
moel@245
   236
            ((id << 8) | revision));
moel@167
   237
        }
moel@167
   238
      } else {
moel@167
   239
moel@167
   240
        Select(logicalDeviceNumber);
moel@167
   241
        ushort address = ReadWord(BASE_ADDRESS_REGISTER);
moel@167
   242
        Thread.Sleep(1);
moel@167
   243
        ushort verify = ReadWord(BASE_ADDRESS_REGISTER);
moel@167
   244
moel@167
   245
        ushort vendorID = ReadWord(FINTEK_VENDOR_ID_REGISTER);
moel@167
   246
moel@245
   247
        WinbondNuvotonFintekExit();
moel@167
   248
moel@167
   249
        if (address != verify) {
moel@167
   250
          report.Append("Chip ID: 0x");
moel@167
   251
          report.AppendLine(chip.ToString("X"));
moel@167
   252
          report.Append("Chip revision: 0x");
moel@228
   253
          report.AppendLine(revision.ToString("X",
moel@167
   254
            CultureInfo.InvariantCulture));
moel@167
   255
          report.AppendLine("Error: Address verification failed");
moel@167
   256
          report.AppendLine();
moel@167
   257
          return false;
moel@167
   258
        }
moel@167
   259
moel@167
   260
        // some Fintek chips have address register offset 0x05 added already
moel@167
   261
        if ((address & 0x07) == 0x05)
moel@167
   262
          address &= 0xFFF8;
moel@167
   263
moel@167
   264
        if (address < 0x100 || (address & 0xF007) != 0) {
moel@167
   265
          report.Append("Chip ID: 0x");
moel@167
   266
          report.AppendLine(chip.ToString("X"));
moel@167
   267
          report.Append("Chip revision: 0x");
moel@228
   268
          report.AppendLine(revision.ToString("X",
moel@167
   269
            CultureInfo.InvariantCulture));
moel@167
   270
          report.Append("Error: Invalid address 0x");
moel@228
   271
          report.AppendLine(address.ToString("X",
moel@167
   272
            CultureInfo.InvariantCulture));
moel@167
   273
          report.AppendLine();
moel@167
   274
          return false;
moel@167
   275
        }
moel@167
   276
moel@167
   277
        switch (chip) {
moel@167
   278
          case Chip.W83627DHG:
moel@167
   279
          case Chip.W83627DHGP:
moel@167
   280
          case Chip.W83627EHF:
moel@167
   281
          case Chip.W83627HF:
moel@167
   282
          case Chip.W83627THF:
moel@167
   283
          case Chip.W83667HG:
moel@167
   284
          case Chip.W83667HGB:
moel@167
   285
          case Chip.W83687THF:
moel@167
   286
            superIOs.Add(new W836XX(chip, revision, address));
moel@167
   287
            break;
moel@245
   288
          case Chip.NCT6771F:
moel@265
   289
          case Chip.NCT6776F:
moel@245
   290
            superIOs.Add(new NCT677X(chip, revision, address));
moel@245
   291
            break;
moel@167
   292
          case Chip.F71858:
moel@167
   293
          case Chip.F71862:
moel@167
   294
          case Chip.F71869:
moel@167
   295
          case Chip.F71882:
moel@167
   296
          case Chip.F71889ED:
moel@167
   297
          case Chip.F71889F:
moel@167
   298
            if (vendorID != FINTEK_VENDOR_ID) {
moel@167
   299
              report.Append("Chip ID: 0x");
moel@167
   300
              report.AppendLine(chip.ToString("X"));
moel@167
   301
              report.Append("Chip revision: 0x");
moel@228
   302
              report.AppendLine(revision.ToString("X",
moel@167
   303
                CultureInfo.InvariantCulture));
moel@167
   304
              report.Append("Error: Invalid vendor ID 0x");
moel@228
   305
              report.AppendLine(vendorID.ToString("X",
moel@167
   306
                CultureInfo.InvariantCulture));
moel@167
   307
              report.AppendLine();
moel@167
   308
              return false;
moel@167
   309
            }
moel@167
   310
            superIOs.Add(new F718XX(chip, address));
moel@167
   311
            break;
moel@167
   312
          default: break;
moel@167
   313
        }
moel@167
   314
moel@167
   315
        return true;
moel@167
   316
      }
moel@167
   317
moel@167
   318
      return false;
moel@167
   319
    }
moel@167
   320
moel@169
   321
    #endregion
moel@169
   322
moel@169
   323
    #region ITE
moel@169
   324
moel@169
   325
    private const byte IT87_ENVIRONMENT_CONTROLLER_LDN = 0x04;
moel@228
   326
    private const byte IT87_GPIO_LDN = 0x07;
moel@169
   327
    private const byte IT87_CHIP_VERSION_REGISTER = 0x22;
moel@169
   328
moel@169
   329
    private void IT87Enter() {
moel@236
   330
      Ring0.WriteIoPort(registerPort, 0x87);
moel@236
   331
      Ring0.WriteIoPort(registerPort, 0x01);
moel@236
   332
      Ring0.WriteIoPort(registerPort, 0x55);
moel@236
   333
      Ring0.WriteIoPort(registerPort, 0x55);
moel@169
   334
    }
moel@169
   335
moel@169
   336
    private void IT87Exit() {
moel@236
   337
      Ring0.WriteIoPort(registerPort, CONFIGURATION_CONTROL_REGISTER);
moel@236
   338
      Ring0.WriteIoPort(valuePort, 0x02);
moel@169
   339
    }
moel@169
   340
moel@167
   341
    private bool DetectIT87() {
moel@169
   342
moel@169
   343
      // IT87XX can enter only on port 0x2E
moel@169
   344
      if (registerPort != 0x2E)
moel@169
   345
        return false;
moel@169
   346
moel@167
   347
      IT87Enter();
moel@167
   348
moel@167
   349
      ushort chipID = ReadWord(CHIP_ID_REGISTER);
moel@167
   350
      Chip chip;
moel@167
   351
      switch (chipID) {
moel@167
   352
        case 0x8712: chip = Chip.IT8712F; break;
moel@167
   353
        case 0x8716: chip = Chip.IT8716F; break;
moel@167
   354
        case 0x8718: chip = Chip.IT8718F; break;
moel@167
   355
        case 0x8720: chip = Chip.IT8720F; break;
moel@170
   356
        case 0x8721: chip = Chip.IT8721F; break;
moel@167
   357
        case 0x8726: chip = Chip.IT8726F; break;
moel@277
   358
        case 0x8728: chip = Chip.IT8728F; break;
moel@167
   359
        default: chip = Chip.Unknown; break;
moel@167
   360
      }
moel@167
   361
      if (chip == Chip.Unknown) {
moel@167
   362
        if (chipID != 0 && chipID != 0xffff) {
moel@167
   363
          IT87Exit();
moel@167
   364
moel@167
   365
          ReportUnknownChip("ITE", chipID);
moel@167
   366
        }
moel@167
   367
      } else {
moel@167
   368
        Select(IT87_ENVIRONMENT_CONTROLLER_LDN);
moel@167
   369
        ushort address = ReadWord(BASE_ADDRESS_REGISTER);
moel@167
   370
        Thread.Sleep(1);
moel@167
   371
        ushort verify = ReadWord(BASE_ADDRESS_REGISTER);
moel@167
   372
moel@167
   373
        byte version = (byte)(ReadByte(IT87_CHIP_VERSION_REGISTER) & 0x0F);
moel@167
   374
moel@228
   375
        Select(IT87_GPIO_LDN);
moel@228
   376
        ushort gpioAddress = ReadWord(BASE_ADDRESS_REGISTER + 2);
moel@228
   377
        Thread.Sleep(1);
moel@228
   378
        ushort gpioVerify = ReadWord(BASE_ADDRESS_REGISTER + 2);
moel@228
   379
moel@167
   380
        IT87Exit();
moel@167
   381
moel@167
   382
        if (address != verify || address < 0x100 || (address & 0xF007) != 0) {
moel@167
   383
          report.Append("Chip ID: 0x");
moel@167
   384
          report.AppendLine(chip.ToString("X"));
moel@167
   385
          report.Append("Error: Invalid address 0x");
moel@167
   386
          report.AppendLine(address.ToString("X",
moel@167
   387
            CultureInfo.InvariantCulture));
moel@167
   388
          report.AppendLine();
moel@167
   389
          return false;
moel@167
   390
        }
moel@167
   391
moel@228
   392
        if (gpioAddress != gpioVerify || gpioAddress < 0x100 ||
moel@228
   393
          (gpioAddress & 0xF007) != 0) {
moel@228
   394
          report.Append("Chip ID: 0x");
moel@228
   395
          report.AppendLine(chip.ToString("X"));
moel@228
   396
          report.Append("Error: Invalid GPIO address 0x");
moel@228
   397
          report.AppendLine(gpioAddress.ToString("X",
moel@228
   398
            CultureInfo.InvariantCulture));
moel@228
   399
          report.AppendLine();
moel@228
   400
          return false;
moel@228
   401
        }
moel@228
   402
moel@228
   403
        superIOs.Add(new IT87XX(chip, address, gpioAddress, version));
moel@167
   404
        return true;
moel@167
   405
      }
moel@167
   406
moel@167
   407
      return false;
moel@167
   408
    }
moel@167
   409
moel@169
   410
    #endregion
moel@169
   411
moel@169
   412
    #region SMSC
moel@169
   413
moel@169
   414
    private void SMSCEnter() {
moel@236
   415
      Ring0.WriteIoPort(registerPort, 0x55);
moel@169
   416
    }
moel@169
   417
moel@169
   418
    private void SMSCExit() {
moel@236
   419
      Ring0.WriteIoPort(registerPort, 0xAA);
moel@169
   420
    }
moel@169
   421
moel@167
   422
    private bool DetectSMSC() {
moel@167
   423
      SMSCEnter();
moel@167
   424
moel@167
   425
      ushort chipID = ReadWord(CHIP_ID_REGISTER);
moel@167
   426
      Chip chip;
moel@167
   427
      switch (chipID) {
moel@167
   428
        default: chip = Chip.Unknown; break;
moel@167
   429
      }
moel@167
   430
      if (chip == Chip.Unknown) {
moel@167
   431
        if (chipID != 0 && chipID != 0xffff) {
moel@167
   432
          SMSCExit();
moel@167
   433
moel@167
   434
          ReportUnknownChip("SMSC", chipID);
moel@167
   435
        }
moel@167
   436
      } else {
moel@167
   437
        SMSCExit();
moel@167
   438
        return true;
moel@167
   439
      }
moel@167
   440
moel@167
   441
      return false;
moel@167
   442
    }
moel@167
   443
moel@169
   444
    #endregion
moel@169
   445
moel@163
   446
    private void Detect() {
moel@110
   447
moel@110
   448
      for (int i = 0; i < REGISTER_PORTS.Length; i++) {
moel@110
   449
        registerPort = REGISTER_PORTS[i];
moel@110
   450
        valuePort = VALUE_PORTS[i];
moel@110
   451
moel@167
   452
        if (DetectWinbondFintek()) continue;
moel@110
   453
moel@167
   454
        if (DetectIT87()) continue;
moel@110
   455
moel@167
   456
        if (DetectSMSC()) continue;
moel@228
   457
      }
moel@163
   458
    }
moel@163
   459
moel@163
   460
    public LPCIO() {
moel@236
   461
      if (!Ring0.IsOpen)
moel@163
   462
        return;
moel@163
   463
moel@236
   464
      if (!Ring0.WaitIsaBusMutex(100))
moel@163
   465
        return;
moel@163
   466
moel@163
   467
      Detect();
moel@163
   468
moel@236
   469
      Ring0.ReleaseIsaBusMutex();
moel@110
   470
    }
moel@110
   471
moel@130
   472
    public ISuperIO[] SuperIO {
moel@110
   473
      get {
moel@130
   474
        return superIOs.ToArray();
moel@110
   475
      }
moel@110
   476
    }
moel@110
   477
moel@110
   478
    public string GetReport() {
moel@110
   479
      if (report.Length > 0) {
moel@195
   480
        return "LPCIO" + Environment.NewLine + Environment.NewLine + report;
moel@110
   481
      } else
moel@110
   482
        return null;
moel@110
   483
    }
moel@110
   484
  }
moel@110
   485
}