/*
 * spd.c - serial presence detect memory information
 implementation for reading memory spd

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 *
 * Originally restored from pcefi10.5 by netkas
 * Dynamic mem detection original impl. by Rekursor
 * System profiler fix and other fixes by Mozodojo.
 * Slice 2011  remade for UEFI
 * XMP detection - apianti
 */

//
#include "Platform.h"
#include "spd.h"
#include "memvendors.h"


#ifndef DEBUG_SPD
#ifndef DEBUG_ALL
#define DEBUG_SPD 0
#else
#define DEBUG_SPD DEBUG_ALL
#endif
#endif

#if DEBUG_SPD == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_SPD, __VA_ARGS__)
#endif

//extern EFI_DATA_HUB_PROTOCOL			*gDataHub;

extern MEM_STRUCTURE		gRAM;
//extern DMI*					gDMI;

//==>
extern UINT16 TotalCount;
//<==

BOOLEAN             smbIntel;
UINT8				smbPage;

CHAR8 *spd_memory_types[] =
{
  "RAM",          /* 00h  Undefined */
  "FPM",          /* 01h  FPM */
  "EDO",          /* 02h  EDO */
  "",				/* 03h  PIPELINE NIBBLE */
  "SDRAM",        /* 04h  SDRAM */
  "",				/* 05h  MULTIPLEXED ROM */
  "DDR SGRAM",	/* 06h  SGRAM DDR */
  "DDR SDRAM",	/* 07h  SDRAM DDR */
  "DDR2 SDRAM",   /* 08h  SDRAM DDR 2 */
  "",				/* 09h  Undefined */
  "",				/* 0Ah  Undefined */
  "DDR3 SDRAM",	/* 0Bh  SDRAM DDR 3 */
  "DDR4 SDRAM" /* 0Ch SDRAM DDR 4 */
};

#define UNKNOWN_MEM_TYPE 2
UINT8 spd_mem_to_smbios[] =
{
  UNKNOWN_MEM_TYPE,		/* 00h  Undefined */
  UNKNOWN_MEM_TYPE,		/* 01h  FPM */
  UNKNOWN_MEM_TYPE,		/* 02h  EDO */
  UNKNOWN_MEM_TYPE,		/* 03h  PIPELINE NIBBLE */
  SMB_MEM_TYPE_SDRAM,	/* 04h  SDRAM */
  SMB_MEM_TYPE_ROM,		/* 05h  MULTIPLEXED ROM */
  SMB_MEM_TYPE_SGRAM,	/* 06h  SGRAM DDR */
  SMB_MEM_TYPE_DDR,		/* 07h  SDRAM DDR */
  SMB_MEM_TYPE_DDR2,	/* 08h  SDRAM DDR 2 */
  UNKNOWN_MEM_TYPE,		/* 09h  Undefined */
  UNKNOWN_MEM_TYPE,		/* 0Ah  Undefined */
  SMB_MEM_TYPE_DDR3,	/* 0Bh  SDRAM DDR 3 */
  SMB_MEM_TYPE_DDR4   /* 0Ch  SDRAM DDR 4 */
};
#define SPD_TO_SMBIOS_SIZE (sizeof(spd_mem_to_smbios)/sizeof(UINT8))

//define rdtsc(low,high) UINT64=AsmReadTsc()
//define outb(port, val)   IoWrite8(port, val)
//define val=inb(port) val=IoRead(port)

// Intel SMB reg offsets
#define SMBHSTSTS 0
#define SMBHSTCNT 2
#define SMBHSTCMD 3
#define SMBHSTADD 4
#define SMBHSTDAT 5
#define SMBHSTDAT1 6
#define SBMBLKDAT 7
// MCP and nForce SMB reg offsets
#define SMBHPRTCL_NV 0 /* protocol, PEC */
#define SMBHSTSTS_NV 1 /* status */
#define SMBHSTADD_NV 2 /* address */
#define SMBHSTCMD_NV 3 /* command */
#define SMBHSTDAT_NV 4 /* 32 data registers */
//

// XMP memory profile
#define SPD_XMP_SIG1 176
#define SPD_XMP_SIG1_VALUE 0x0C
#define SPD_XMP_SIG2 177
#define SPD_XMP_SIG2_VALUE 0x4A
#define SPD_XMP_PROFILES 178
#define SPD_XMP_VERSION 179
#define SPD_XMP_PROF1_DIVISOR 180
#define SPD_XMP_PROF1_DIVIDEND 181
#define SPD_XMP_PROF2_DIVISOR 182
#define SPD_XMP_PROF2_DIVIDEND 183
#define SPD_XMP_PROF1_RATIO 186
#define SPD_XMP_PROF2_RATIO 221


#define SPD_XMP20_SIG1 0x180
#define SPD_XMP20_SIG2 0x181
#define SPD_XMP20_PROFILES 0x182
#define SPD_XMP20_VERSION 0x183
/* 0x189 */
#define SPD_XMP20_PROF1_MINCYCLE 0x18C
#define SPD_XMP20_PROF1_FINEADJUST 0x1AF
/* 0x1B8 */
#define SPD_XMP20_PROF2_MINCYCLE 0x1BB
#define SPD_XMP20_PROF2_FINEADJUST 0x1DE
/* 0x1E7 */

UINT16 spd_indexes_ddr[] = {
  /* 3 */ SPD_NUM_ROWS,  /* ModuleSize */
  /* 4 */ SPD_NUM_COLUMNS,
  /* 5 */ SPD_NUM_DIMM_BANKS,
  /* 17 */ SPD_NUM_BANKS_PER_SDRAM,
  9, /* Frequency */
  64, /* Manufacturer */
  95,96,97,98, /* UIS */
  0
};

UINT16 spd_indexes_ddr3[] = {
  4,7,8, /* ModuleSize */
  10,11,12, /* Frequency */
  /* 0x75, 0x76 */ SPD_DDR3_MEMORY_BANK, SPD_DDR3_MEMORY_CODE, /* Manufacturer */
  122,123,124,125, /* UIS */
  /* XMP */
  SPD_XMP_SIG1,
  SPD_XMP_SIG2,
  SPD_XMP_PROFILES,
  SPD_XMP_VERSION,
  SPD_XMP_PROF1_DIVISOR,
  SPD_XMP_PROF1_DIVIDEND,
  SPD_XMP_PROF2_DIVISOR,
  SPD_XMP_PROF2_DIVIDEND,
  SPD_XMP_PROF1_RATIO,
  SPD_XMP_PROF2_RATIO,
  0
};

UINT16 spd_indexes_ddr4[] = {
  4,6,12,13, /* ModuleSize */
  18,125, /* Frequency */
  SPD_DDR4_MANUFACTURER_ID_BANK, SPD_DDR4_MANUFACTURER_ID_CODE, /* Manufacturer */
  325,326,327,328, /* UIS */
  /* XMP 2.0 */
  SPD_XMP20_SIG1,
  SPD_XMP20_SIG2,
  SPD_XMP20_PROFILES,
  SPD_XMP20_VERSION,
  SPD_XMP20_PROF1_MINCYCLE,
  SPD_XMP20_PROF1_FINEADJUST,
  SPD_XMP20_PROF2_MINCYCLE,
  SPD_XMP20_PROF2_FINEADJUST,
  0
};

/** Read one byte from i2c, used for reading SPD */

UINT8 smb_read_byte(UINT32 base, UINT8 adr, UINT16 cmd)
{
  //   INTN l1, h1, l2, h2;
  UINT64 t, t1, t2;
  UINT8 page;
  UINT8 c;
  //	UINT8 p;

  if (smbIntel) {
    IoWrite8(base + SMBHSTSTS, 0x1f);				// reset SMBus Controller (set busy)
    IoWrite8(base + SMBHSTDAT, 0xff);

    t1 = AsmReadTsc(); //rdtsc(l1, h1);
    while ( IoRead8(base + SMBHSTSTS) & 0x01) {   // wait until host is not busy
      t2 = AsmReadTsc(); //rdtsc(l2, h2);
      t = DivU64x64Remainder((t2 - t1),
                             DivU64x32(gCPUStructure.TSCFrequency, 1000),
                             0);
      if (t > 5) {
        DBG("host is busy for too long for byte %2X:%d!\n", adr, cmd);
        return 0xFF;                  // break
      }
    }

    page = (cmd >> 8) & 1;
    if (page != smbPage) {
      // p = 0xFF;
      IoWrite8(base + SMBHSTCMD, 0x00);
      IoWrite8(base + SMBHSTADD, 0x6C + (page << 1)); // Set SPD Page Address
#if 0
      IoWrite8(base + SMBHSTCNT, 0x48); // Start + Byte Data Write
      // Don't use "Byte Data Write" because status goes from 0x41 (Busy) -> 0x44 (Error)
#else
      IoWrite8(base + SMBHSTCNT, 0x40); // Start + Quick Write
      // status goes from 0x41 (Busy) -> 0x42 (Completed)
#endif
      smbPage = page;

      t1 = AsmReadTsc();
      while (!( (c=IoRead8(base + SMBHSTSTS)) & 0x02)) {	// wait until command finished
        t2 = AsmReadTsc();
        t = DivU64x64Remainder((t2 - t1), DivU64x32(gCPUStructure.TSCFrequency, 1000), 0);
        /*
         if (c != p) {
         DBG("%02d %02X spd page change status %2X\n", t, cmd, c);
         p = c;
         }
         */
        if (c & 4) {
          DBG("spd page change error for byte %2X:%d!\n", adr, cmd);
          break;
        }
        if (t > 5) {
          DBG("spd page change taking too long for byte %2X:%d!\n", adr, cmd);
          break;									// break after 5ms
        }
      }
      return smb_read_byte(base, adr, cmd);
    }

   	// p = 0xFF;
    IoWrite8(base + SMBHSTCMD, (UINT8)(cmd & 0xFF)); // SMBus uses 8 bit commands
    IoWrite8(base + SMBHSTADD, (adr << 1) | 0x01 ); // read from spd
    IoWrite8(base + SMBHSTCNT, 0x48 ); // Start + Byte Data Read
    // status goes from 0x41 (Busy) -> 0x42 (Completed) or 0x44 (Error)

    t1 = AsmReadTsc();
    while (!( (c=IoRead8(base + SMBHSTSTS)) & 0x02)) {	// wait until command finished
      t2 = AsmReadTsc();
      t = DivU64x64Remainder((t2 - t1), DivU64x32(gCPUStructure.TSCFrequency, 1000), 0);
      /*
       if (c != p) {
       DBG("%02d %02X spd read status %2X\n", t, cmd, c);
       p = c;
       }
       */
      if (c & 4) {
        // This alway happens when trying to read the memory type (cmd 2) of an empty slot
        // DBG("spd byte read error for byte %2X:%d!\n", adr, cmd);
        break;
      }
      if (t > 5) {
        // if (cmd != 2)
        DBG("spd byte read taking too long for byte %2X:%d!\n", adr, cmd);
        break;									// break after 5ms
      }
    }
    return IoRead8(base + SMBHSTDAT);
  }
  else {
    IoWrite8(base + SMBHSTSTS_NV, 0x1f);			// reset SMBus Controller
    IoWrite8(base + SMBHSTDAT_NV, 0xff);

    t1 = AsmReadTsc(); //rdtsc(l1, h1);
    while ( IoRead8(base + SMBHSTSTS_NV) & 0x01) {    // wait until read
      t2 = AsmReadTsc(); //rdtsc(l2, h2);
      t = DivU64x64Remainder((t2 - t1),
                             DivU64x32(gCPUStructure.TSCFrequency, 1000),
                             0);
      if (t > 5)
        return 0xFF;                  // break
    }

    IoWrite8(base + SMBHSTSTS_NV, 0x00); // clear status register
    IoWrite16(base + SMBHSTCMD_NV, cmd);
    IoWrite8(base + SMBHSTADD_NV, (adr << 1) | 0x01 );
    IoWrite8(base + SMBHPRTCL_NV, 0x07 );
    t1 = AsmReadTsc();

    while (!( IoRead8(base + SMBHSTSTS_NV) & 0x9F)) {		// wait till command finished
      t2 = AsmReadTsc();
      t = DivU64x64Remainder((t2 - t1),
                             DivU64x32(gCPUStructure.TSCFrequency, 1000),
                             0);
      if (t > 5)
        break; // break after 5ms
    }
    return IoRead8(base + SMBHSTDAT_NV);
  }
}

/* SPD i2c read optimization: prefetch only what we need, read non prefetcheable bytes on the fly */
#define READ_SPD(spd, base, slot, x) spd[x] = smb_read_byte(base, 0x50 + slot, x)


/** Read from spd *used* values only*/
VOID init_spd(UINT16* spd_indexes, UINT8* spd, UINT32 base, UINT8 slot)
{
  UINT16 i;
  for (i=0; spd_indexes[i]; i++) {
    READ_SPD(spd, base, slot, spd_indexes[i]);
  }

#if 0
  DBG("Reading entire spd data\n");
  for (i = 0; i < 512; i++) {
    UINT8 b = smb_read_byte(base, 0x50 + slot, i);
    DBG("%02X", b);
  }
  DBG(".\n");
#endif
}

// Get Vendor Name from spd, 3 cases handled DDR3, DDR4 and DDR2,
// have different formats, always return a valid ptr.
CHAR8* getVendorName(RAM_SLOT_INFO* slot, UINT8 *spd, UINT32 base, UINT8 slot_num)
{
  UINT8 bank = 0;
  UINT8 code = 0;
  UINT8 parity;
  UINT8 testbit;
  INTN  i = 0;
  //UINT8 * spd = (UINT8 *) slot->spd;
  if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR4) { // DDR4
    bank = spd[SPD_DDR4_MANUFACTURER_ID_BANK];
    code = spd[SPD_DDR4_MANUFACTURER_ID_CODE];
    parity = bank;
    testbit = bank;
    for (i=6; i >= 0; i--) { parity ^= (testbit <<= 1); }
    if ( (parity & 0x80) == 0 ) {
      DBG("Bad parity bank=0x%2X code=0x%2X\n", bank, code);
    }
    bank &= 0x7f;
    for (i=0; i < VEN_MAP_SIZE; i++) {
      if (bank==vendorMap[i].bank && code==vendorMap[i].code) {
        return vendorMap[i].name;
      }
    }
    DBG("Unknown vendor bank=0x%2X code=0x%2X\n", bank, code);
  } else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR3) { // DDR3
    bank = spd[SPD_DDR3_MEMORY_BANK]; // constructors like Patriot use b7=1
    code = spd[SPD_DDR3_MEMORY_CODE];
    parity = bank;
    testbit = bank;
    for (i=6; i >= 0; i--) { parity ^= (testbit <<= 1); }
    if ( (parity & 0x80) == 0 ) {
      DBG("Bad parity bank=0x%2X code=0x%2X\n", bank, code);
    }
    bank &= 0x7f;

    for (i=0; i < VEN_MAP_SIZE; i++) {
      if (bank==vendorMap[i].bank && code==vendorMap[i].code) {
        return vendorMap[i].name;
      }
    }
  } else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR2 ||
             spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR) {
    if(spd[64]==0x7f) {
      for (i=64; i<72 && spd[i]==0x7f;i++) {
        bank++;
        READ_SPD(spd, base, slot_num, (UINT8)(i+1)); // prefetch next spd byte to read for next loop
      }
      READ_SPD(spd, base, slot_num, (UINT8)i);
      code = spd[i];
    } else {
      code = spd[64];
      bank = 0;
    }
    for (i=0; i < VEN_MAP_SIZE; i++) {
      if (bank==vendorMap[i].bank && code==vendorMap[i].code) {
        return vendorMap[i].name;
      }
    }
  }
  /* OK there is no vendor id here lets try to match the partnum if it exists */
  if (AsciiStrStr(slot->PartNo,"GU332") == slot->PartNo) { // Unifosa fingerprint
    return "Unifosa";
  }
  return "NoName";
}

/** Get Default Memory Module Speed (no overclocking handled) */
UINT16 getDDRspeedMhz(UINT8 * spd)
{
  UINT16 frequency = 0; // default freq for unknown types //shit! DDR1 = 533
  UINT16 xmpFrequency1 = 0, xmpFrequency2 = 0;
  UINT8 xmpVersion = 0;
  UINT8 xmpProfiles = 0;

  if (spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR4) {
    UINT16 mincycle = spd[18];
    INT8 fineadjust = spd[125];
    frequency = (UINT16)(2000000 / (mincycle * 125 + fineadjust));

    // Check if module supports XMP
    if ((spd[SPD_XMP20_SIG1] == SPD_XMP_SIG1_VALUE) &&
        (spd[SPD_XMP20_SIG2] == SPD_XMP_SIG2_VALUE)) {
      xmpVersion = spd[SPD_XMP20_VERSION];
      xmpProfiles = spd[SPD_XMP20_PROFILES] & 3;

      if ((xmpProfiles & 1) == 1) {
        // Check the first profile
        mincycle = spd[SPD_XMP20_PROF1_MINCYCLE];
        fineadjust = spd[SPD_XMP20_PROF1_FINEADJUST];
        xmpFrequency1 = (UINT16)(2000000 / (mincycle * 125 + fineadjust));
        DBG("XMP Profile1: %d*125 %d ns\n", mincycle, fineadjust);
      }
      if ((xmpProfiles & 2) == 2) {
        // Check the second profile
        mincycle = spd[SPD_XMP20_PROF2_MINCYCLE];
        fineadjust = spd[SPD_XMP20_PROF2_FINEADJUST];
        xmpFrequency2 = (UINT16)(2000000 / (mincycle * 125 + fineadjust));
        DBG("XMP Profile2: %d*125 %d ns\n", mincycle, fineadjust);
      }
    }
  } else if (spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR3) {
    // This should be multiples of MTB converted to MHz- apianti
    UINT16 divisor = spd[10];
    UINT16 dividend = spd[11];
    UINT16 ratio = spd[12];
    frequency = (((dividend != 0) && (divisor != 0) && (ratio != 0)) ?
                 ((2000 * dividend) / (divisor * ratio)) : 0);

    // Check if module supports XMP
    if ((spd[SPD_XMP_SIG1] == SPD_XMP_SIG1_VALUE) &&
        (spd[SPD_XMP_SIG2] == SPD_XMP_SIG2_VALUE)) {
      xmpVersion = spd[SPD_XMP_VERSION];
      xmpProfiles = spd[SPD_XMP_PROFILES] & 3;

      if ((xmpProfiles & 1) == 1) {
        // Check the first profile
        divisor = spd[SPD_XMP_PROF1_DIVISOR];
        dividend = spd[SPD_XMP_PROF1_DIVIDEND];
        ratio = spd[SPD_XMP_PROF1_RATIO];
        xmpFrequency1 = (((dividend != 0) && (divisor != 0) && (ratio != 0)) ?
                         ((2000 * dividend) / (divisor * ratio)) : 0);
        DBG("XMP Profile1: %d*%d/%dns\n", ratio, divisor, dividend);
      }
      if ((xmpProfiles & 2) == 2) {
        // Check the second profile
        divisor = spd[SPD_XMP_PROF2_DIVISOR];
        dividend = spd[SPD_XMP_PROF2_DIVIDEND];
        ratio = spd[SPD_XMP_PROF2_RATIO];
        xmpFrequency2 = (((dividend != 0) && (divisor != 0) && (ratio != 0)) ?
                         ((2000 * dividend) / (divisor * ratio)) : 0);
        DBG("XMP Profile2: %d*%d/%dns\n", ratio, divisor, dividend);
      }
    }
  } else if ((spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR2) ||
             (spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR)) {
    switch(spd[9]) {
      case 0x50:
        return 400;
      case 0x3d:
        return 533;
      case 0x30:
        return 667;
      case 0x25:
      default:
        return 800;
      case 0x1E:
        return 1066;
    }
  }

  if (xmpProfiles) {
    DBG("Found module with XMP version %d.%d\n", (xmpVersion >> 4) & 0xF, xmpVersion & 0xF);

    switch (gSettings.XMPDetection) {
      case 0:
        // Detect the better XMP profile
        if (xmpFrequency1 >= xmpFrequency2) {
          if (xmpFrequency1 >= frequency) {
            DBG("Using XMP Profile1 instead of standard frequency %dMHz\n", frequency);
            frequency = xmpFrequency1;
          }
        } else if (xmpFrequency2 >= frequency) {
          DBG("Using XMP Profile2 instead of standard frequency %dMHz\n", frequency);
          frequency = xmpFrequency2;
        }
        break;

      case 1:
        // Use first profile if present
        if ((xmpProfiles & 1) == 1) {
          frequency = xmpFrequency1;
          DBG("Using XMP Profile1 instead of standard frequency %dMHz\n", frequency);
        } else {
          DBG("Not using XMP Profile1 because it is not present\n");
        }
        break;

      case 2:
        // Use second profile
        if ((xmpProfiles & 2) == 2) {
          frequency = xmpFrequency2;
          DBG("Using XMP Profile2 instead of standard frequency %dMHz\n", frequency);
        } else {
          DBG("Not using XMP Profile2 because it is not present\n");
        }
        break;

      default:
        break;
    }
  } else {
    // Print out XMP not detected
    switch (gSettings.XMPDetection) {
      case 0:
        DBG("Not using XMP because it is not present\n");
        break;

      case 1:
      case 2:
        DBG("Not using XMP Profile%d because it is not present\n", gSettings.XMPDetection);
        break;

      default:
        break;
    }
  }
  return frequency;
}

#define SMST(a) ((UINT8)((spd[a] & 0xf0) >> 4))
#define SLST(a) ((UINT8)(spd[a] & 0x0f))

/** Get DDR3 or DDR2 serial number, 0 most of the times, always return a valid ptr */
CHAR8* getDDRSerial(UINT8* spd)
{
  CHAR8* asciiSerial; //[16];
  asciiSerial = AllocatePool(17);
  if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR4) { // DDR4
    AsciiSPrint(asciiSerial, 17, "%2X%2X%2X%2X%2X%2X%2X%2X", SMST(325) /*& 0x7*/, SLST(325), SMST(326), SLST(326), SMST(327), SLST(327), SMST(328), SLST(328));
  } else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR3) { // DDR3
    AsciiSPrint(asciiSerial, 17, "%2X%2X%2X%2X%2X%2X%2X%2X", SMST(122) /*& 0x7*/, SLST(122), SMST(123), SLST(123), SMST(124), SLST(124), SMST(125), SLST(125));
  } else if (spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR2 ||
             spd[SPD_MEMORY_TYPE]==SPD_MEMORY_TYPE_SDRAM_DDR) {  // DDR2 or DDR
    AsciiSPrint(asciiSerial, 17, "%2X%2X%2X%2X%2X%2X%2X%2X", SMST(95) /*& 0x7*/, SLST(95), SMST(96), SLST(96), SMST(97), SLST(97), SMST(98), SLST(98));
  } else {
    AsciiStrCpyS(asciiSerial, 17, "0000000000000000");
  }

  return asciiSerial;
}

/** Get DDR2 or DDR3 or DDR4 Part Number, always return a valid ptr */
CHAR8* getDDRPartNum(UINT8* spd, UINT32 base, UINT8 slot)
{
  UINT16 i, start=0, index = 0;
  CHAR8 c;
  CHAR8* asciiPartNo = AllocatePool(32); //[32];

  if (spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR4) {
    start = 329;
  } else if (spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR3) {
    start = 128;
  } else if (spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR2 ||
             spd[SPD_MEMORY_TYPE] == SPD_MEMORY_TYPE_SDRAM_DDR) {
    start = 73;
  }

  // Check that the spd part name is zero terminated and that it is ascii:
  ZeroMem(asciiPartNo, 32);  //sizeof(asciiPartNo));
  for (i = start; i < start + 20; i++) {
    READ_SPD(spd, base, slot, (UINT16)i); // only read once the corresponding model part (ddr3 or ddr2)
    c = spd[i];
    if (IS_ALFA(c) || IS_DIGIT(c) || IS_PUNCT(c)) // It seems that System Profiler likes only letters and digits...
      asciiPartNo[index++] = c;
    else if (c < 0x20)
      break;
  }

  return asciiPartNo;
}

//INTN mapping []= {0,2,1,3,4,6,5,7,8,10,9,11};
#define PCI_COMMAND_OFFSET                          0x04

/** Read from smbus the SPD content and interpret it for detecting memory attributes */
STATIC VOID read_smb(EFI_PCI_IO_PROTOCOL *PciIo, UINT16	vid, UINT16	did)
{
  //	EFI_STATUS	Status;
  UINT16      speed;
  UINT8       i;// spd_size, spd_type;
  UINT32			base, mmio, hostc;
  UINT16			Command;
  //RAM_SLOT_INFO*  slot;
  //BOOLEAN			fullBanks;
  UINT8*			spdbuf;
//  UINT16			vid, did;

  UINT8                  TotalSlotsCount;

  smbPage = 0; // valid pages are 0 and 1; assume the first page (page 0) is already selected
//  vid = gPci->Hdr.VendorId;
//  did = gPci->Hdr.DeviceId;

  /*Status = */PciIo->Pci.Read (
                                PciIo,
                                EfiPciIoWidthUint16,
                                PCI_COMMAND_OFFSET,
                                1,
                                &Command
                                );

  Command |= 1;
  /*Status = */PciIo->Pci.Write (
                                 PciIo,
                                 EfiPciIoWidthUint16,
                                 PCI_COMMAND_OFFSET,
                                 1,
                                 &Command
                                 );

  DBG("SMBus CmdReg: 0x%x\n", Command);

  /*Status = */PciIo->Pci.Read (
                                PciIo,
                                EfiPciIoWidthUint32,
                                0x10,
                                1,
                                &mmio
                                );
  if (vid == 0x8086) {
    /*Status = */PciIo->Pci.Read (
                                  PciIo,
                                  EfiPciIoWidthUint32,
                                  0x20,
                                  1,
                                  &base
                                  );

    base &= 0xFFFE;
    smbIntel = TRUE;
  }
  else {
    /*Status = */PciIo->Pci.Read (
                                  PciIo,
                                  EfiPciIoWidthUint32,
                                  0x24, // iobase offset 0x24 on MCP
                                  1,
                                  &base
                                  );
    base &= 0xFFFC;
    smbIntel = FALSE;
  }
  /*Status = */PciIo->Pci.Read (
                                PciIo,
                                EfiPciIoWidthUint32,
                                0x40,
                                1,
                                &hostc
                                );


  MsgLog("Scanning SMBus [%04x:%04x], mmio: 0x%x, ioport: 0x%x, hostc: 0x%x\n",
         vid, did, mmio, base, hostc);

  // needed at least for laptops
  //fullBanks = (gDMI->MemoryModules == gDMI->CntMemorySlots);

  spdbuf = AllocateZeroPool(MAX_SPD_SIZE);

  // Search MAX_RAM_SLOTS slots
  //==>
  /*  TotalSlotsCount = (UINT8) TotalCount;
   if (!TotalSlotsCount) {
   TotalSlotsCount = MAX_RAM_SLOTS;
   } */
  TotalSlotsCount = 8; //MAX_RAM_SLOTS;  -- spd can read only 8 slots
  DBG("Slots to scan [%d]...\n", TotalSlotsCount);
  for (i = 0; i <  TotalSlotsCount; i++){
    //<==
    ZeroMem(spdbuf, MAX_SPD_SIZE);
    READ_SPD(spdbuf, base, i, SPD_MEMORY_TYPE);
    if (spdbuf[SPD_MEMORY_TYPE] == 0xFF) {
      //DBG("SPD[%d]: Empty\n", i);
      continue;
    }
    else if (spdbuf[SPD_MEMORY_TYPE] == 0) {
      // First 0x40 bytes of DDR4 spd second page is 0. Maybe we need to change page, so do that and retry.
      DBG("SPD[%d]: Got invalid type %d @0x%x. Will set page and retry.\n", i, spdbuf[SPD_MEMORY_TYPE], 0x50 + i);
      smbPage = 0xFF; // force page to be set
      READ_SPD(spdbuf, base, i, SPD_MEMORY_TYPE);
    }
    
    // Copy spd data into buffer
    DBG("SPD[%d]: Type %d @0x%x\n", i, spdbuf[SPD_MEMORY_TYPE], 0x50 + i);
    switch (spdbuf[SPD_MEMORY_TYPE])  {
      case SPD_MEMORY_TYPE_SDRAM_DDR:
        init_spd(spd_indexes_ddr, spdbuf, base, i);

        gRAM.SPD[i].Type = MemoryTypeDdr;
        gRAM.SPD[i].ModuleSize = (((1 << ((spdbuf[SPD_NUM_ROWS] & 0x0f)
                                          + (spdbuf[SPD_NUM_COLUMNS] & 0x0f) - 17)) *
                                   ((spdbuf[SPD_NUM_DIMM_BANKS] & 0x7) + 1) *
                                   spdbuf[SPD_NUM_BANKS_PER_SDRAM])/3)*2;
        break;

      case SPD_MEMORY_TYPE_SDRAM_DDR2:
        init_spd(spd_indexes_ddr, spdbuf, base, i);

        gRAM.SPD[i].Type = MemoryTypeDdr2;
        gRAM.SPD[i].ModuleSize = ((1 << ((spdbuf[SPD_NUM_ROWS] & 0x0f)
                                         + (spdbuf[SPD_NUM_COLUMNS] & 0x0f) - 17)) *
                                  ((spdbuf[SPD_NUM_DIMM_BANKS] & 0x7) + 1) *
                                  spdbuf[SPD_NUM_BANKS_PER_SDRAM]);
        break;

      case SPD_MEMORY_TYPE_SDRAM_DDR3:
        init_spd(spd_indexes_ddr3, spdbuf, base, i);

        gRAM.SPD[i].Type = MemoryTypeDdr3;
        gRAM.SPD[i].ModuleSize = ((spdbuf[4] & 0x0f) + 28 ) + ((spdbuf[8] & 0x7)  + 3 );
        gRAM.SPD[i].ModuleSize -= (spdbuf[7] & 0x7) + 25;
        gRAM.SPD[i].ModuleSize = ((1 << gRAM.SPD[i].ModuleSize) * (((spdbuf[7] >> 3) & 0x1f) + 1));

        break;

      case SPD_MEMORY_TYPE_SDRAM_DDR4:
        init_spd(spd_indexes_ddr4, spdbuf, base, i);

        gRAM.SPD[i].Type = MemoryTypeDdr4;

        gRAM.SPD[i].ModuleSize
        = (1 << ((spdbuf[4] & 0x0f) + 8 /* Mb */ - 3 /* MB */)) // SDRAM Capacity
        * (1 << ((spdbuf[13] & 0x07) + 3)) // Primary Bus Width
        / (1 << ((spdbuf[12] & 0x07) + 2)) // SDRAM Width
        * (((spdbuf[12] >> 3) & 0x07) + 1) // Logical Ranks per DIMM
        * (((spdbuf[6] & 0x03) == 2) ? (((spdbuf[6] >> 4) & 0x07) + 1) : 1);

        /*
         Total = SDRAM Capacity / 8 * Primary Bus Width / SDRAM Width * Logical Ranks per DIMM
         where:
         : SDRAM Capacity = SPD byte 4 bits 3~0
         : Primary Bus Width = SPD byte 13 bits 2~0
         : SDRAM Width = SPD byte 12 bits 2~0
         : Logical Ranks per DIMM =
         for SDP, DDP, QDP: = SPD byte 12 bits 5~3
         for 3DS: = SPD byte 12 bits 5~3
         times SPD byte 6 bits 6~4 (Die Count)

         SDRAM Capacity

         0	0000 = 256 Mb
         1	0001 = 512 Mb
         2	0010 = 1 Gb
         3	0011 = 2 Gb
         4	0100 = 4 Gb
         5	0101 = 8 Gb
         6	0110 = 16 Gb
         7	0111 = 32 Gb


         Primary Bus Width

         000 = 8 bits
         001 = 16 bits
         010 = 32 bits
         011 = 64 bits


         SDRAM Device Width

         000 = 4 bits
         001 = 8 bits
         010 = 16 bits
         011 = 32 bits


         Logical Ranks per DIMM for SDP, DDP, QDP

         000 = 1 Package Rank
         001 = 2 Package Ranks
         010 = 3 Package Ranks
         011 = 4 Package Ranks


         Die Count for 3DS

         000 = Single die 001 = 2 die
         010 = 3 die
         011 = 4 die
         100 = 5 die
         101 = 6 die
         110 = 7 die
         111 = 8 die
         */
        break;

      default:
        gRAM.SPD[i].ModuleSize = 0;
        break;
    }

    if (gRAM.SPD[i].ModuleSize == 0) continue;
    //spd_type = (slot->spd[SPD_MEMORY_TYPE] < ((UINT8) 12) ? slot->spd[SPD_MEMORY_TYPE] : 0);
    //gRAM Type = spd_mem_to_smbios[spd_type];
    gRAM.SPD[i].PartNo = getDDRPartNum(spdbuf, base, i);
    gRAM.SPD[i].Vendor = getVendorName(&(gRAM.SPD[i]), spdbuf, base, i);
    gRAM.SPD[i].SerialNo = getDDRSerial(spdbuf);
    //XXX - when we can FreePool allocated for these buffers?
    // determine spd speed
    speed = getDDRspeedMhz(spdbuf);
    DBG("DDR speed %dMHz\n", speed);
    if (gRAM.SPD[i].Frequency<speed) gRAM.SPD[i].Frequency = speed;

#if 0
    // pci memory controller if available, is more reliable
    if (gRAM.Frequency > 0) {
      UINT32 freq = (UINT32)DivU64x32(gRAM.Frequency, 500000);
      // now round off special cases
      UINT32 fmod100 = freq %100;
      switch(fmod100) {
        case  1:	freq--;	break;
        case 32:	freq++;	break;
        case 65:	freq++; break;
        case 98:	freq+=2;break;
        case 99:	freq++; break;
      }
      gRAM.SPD[i].Frequency = freq;
      DBG("RAM speed %dMHz\n", freq);
    }
#endif

    MsgLog("Slot: %d Type %d %dMB %dMHz Vendor=%a PartNo=%a SerialNo=%a\n",
           i,
           (int)gRAM.SPD[i].Type,
           gRAM.SPD[i].ModuleSize,
           gRAM.SPD[i].Frequency,
           gRAM.SPD[i].Vendor,
           gRAM.SPD[i].PartNo,
           gRAM.SPD[i].SerialNo);

    gRAM.SPD[i].InUse = TRUE;
    ++(gRAM.SPDInUse);
    //}

    // laptops sometimes show slot 0 and 2 with slot 1 empty when only 2 slots are presents so:
    //gDMI->DIMM[i]= (UINT32)((i>0 && gRAM->DIMM[1].InUse==FALSE && !fullBanks && TotalCount == 2)?mapping[i] : i); // for laptops case, mapping setup would need to be more generic than this

    //slot->spd = NULL;

  } // for
  if (smbPage != 0) {
    READ_SPD(spdbuf, base, 0, 0); // force first page when we're done
  }
}

VOID ScanSPD()
{
  EFI_STATUS            Status;
  EFI_HANDLE            *HandleBuffer = NULL;
//  EFI_GUID              **ProtocolGuidArray;
  EFI_PCI_IO_PROTOCOL   *PciIo = NULL;
  UINTN                 HandleCount;
//  UINTN                 ArrayCount;
  UINTN                 Index;
//  UINTN                 ProtocolIndex;
  PCI_TYPE00            gPci;

  DbgHeader("ScanSPD");
  
  // Scan PCI handles
  Status = gBS->LocateHandleBuffer (
                                    ByProtocol,
                                    &gEfiPciIoProtocolGuid,
                                    NULL,
                                    &HandleCount,
                                    &HandleBuffer
                                    );
  
  if (!EFI_ERROR (Status)) {
    for (Index = 0; Index < HandleCount; ++Index) {
      Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiPciIoProtocolGuid, (VOID **)&PciIo);
      if (!EFI_ERROR (Status)) {
        // Read PCI BUS
        //PciIo->GetLocation (PciIo, &Segment, &Bus, &Device, &Function);
        Status = PciIo->Pci.Read (
                                  PciIo,
                                  EfiPciIoWidthUint32,
                                  0,
                                  sizeof (gPci) / sizeof (UINT32),
                                  &gPci
                                  );
        //SmBus controller has class = 0x0c0500
        if ((gPci.Hdr.ClassCode[2] == 0x0c) && (gPci.Hdr.ClassCode[1] == 5)
            && (gPci.Hdr.ClassCode[0] == 0) && (gPci.Hdr.VendorId == 0x8086 || gPci.Hdr.VendorId == 0x10DE)) {
          DBG ("SMBus device : %04x %04x class=%02x%02x%02x status=%r\n",
               gPci.Hdr.VendorId,
               gPci.Hdr.DeviceId,
               gPci.Hdr.ClassCode[2],
               gPci.Hdr.ClassCode[1],
               gPci.Hdr.ClassCode[0],
               Status
               );
          read_smb(PciIo, gPci.Hdr.VendorId, gPci.Hdr.DeviceId);
        }
      }
    }
  }


  // Scan PCI BUS For SmBus controller 
/*  Status = gBS->LocateHandleBuffer(AllHandles,NULL,NULL,&HandleCount,&HandleBuffer);
  if (!EFI_ERROR(Status)) {
    for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
      Status = gBS->ProtocolsPerHandle(HandleBuffer[HandleIndex],&ProtocolGuidArray,&ArrayCount);
      if (!EFI_ERROR(Status)) {
        for (ProtocolIndex = 0; ProtocolIndex < ArrayCount; ProtocolIndex++) {
          if (CompareGuid(&gEfiPciIoProtocolGuid, ProtocolGuidArray[ProtocolIndex])) {
            Status = gBS->OpenProtocol(HandleBuffer[HandleIndex],&gEfiPciIoProtocolGuid,(VOID **)&PciIo,gImageHandle,NULL,EFI_OPEN_PROTOCOL_GET_PROTOCOL);
            
            if (!EFI_ERROR(Status)) {
              // Read PCI BUS 
              Status = PciIo->Pci.Read (
                                        PciIo,
                                        EfiPciIoWidthUint32,
                                        0,
                                        sizeof (gPci) / sizeof (UINT32),
                                        &gPci
                                        );
              
              //SmBus controller has class = 0x0c0500
              if ((gPci.Hdr.ClassCode[2] == 0x0c) && (gPci.Hdr.ClassCode[1] == 5) 
                  && (gPci.Hdr.ClassCode[0] == 0) && (gPci.Hdr.VendorId == 0x8086 || gPci.Hdr.VendorId == 0x10DE)) {
                read_smb(PciIo);
              }
            }
          }
        }
      }
    }
  }
 */
}
