// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2004-2022 Samuel Demeulemeester

#include "display.h"

#include "io.h"
#include "tsc.h"
#include "pci.h"
#include "unistd.h"
#include "string.h"

#include "cpuinfo.h"
#include "smbus.h"
#include "smbios.h"
#include "jedec_id.h"
#include "hwquirks.h"

#define LINE_SPD        13
#define MAX_SPD_SLOT    8

ram_info ram = { 0, 0, 0, 0, 0, 0, "N/A"};

int smbdev, smbfun;
unsigned short smbusbase = 0;
uint32_t smbus_id = 0;
static uint16_t extra_initial_sleep_for_smb_transaction = 0;

static int8_t spd_page = -1;
static int8_t last_adr = -1;

// Functions Prototypes
static void read_sku(char *sku, uint8_t slot_idx, uint16_t offset, uint8_t max_len);

static void parse_spd_rdram (spd_info *spdi, uint8_t slot_idx);
static void parse_spd_sdram (spd_info *spdi, uint8_t slot_idx);
static void parse_spd_ddr   (spd_info *spdi, uint8_t slot_idx);
static void parse_spd_ddr2  (spd_info *spdi, uint8_t slot_idx);
static void parse_spd_ddr3  (spd_info *spdi, uint8_t slot_idx);
static void parse_spd_ddr4  (spd_info *spdi, uint8_t slot_idx);
static void parse_spd_ddr5  (spd_info *spdi, uint8_t slot_idx);
static void print_spdi(spd_info spdi, uint8_t lidx);

static bool setup_smb_controller(void);
static bool find_smb_controller(uint16_t vid, uint16_t did);

static uint8_t get_spd(uint8_t slot_idx, uint16_t spd_adr);

static bool nv_mcp_get_smb(void);
static bool amd_sb_get_smb(void);
static bool fch_zen_get_smb(void);
static bool piix4_get_smb(uint8_t address);
static bool ich5_get_smb(void);
static uint8_t ich5_process(void);
static uint8_t ich5_read_spd_byte(uint8_t adr, uint16_t cmd);
static uint8_t nf_read_spd_byte(uint8_t smbus_adr, uint8_t spd_adr);

static inline uint8_t bcd_to_ui8(uint8_t bcd)
{
    return bcd - 6 * (bcd >> 4);
}

void print_smbus_startup_info(void)
{
    uint8_t spdidx = 0, spd_line_idx = 0;

    spd_info curspd;
    ram.freq = 0;
    curspd.isValid = false;

    if (quirk.type & QUIRK_TYPE_SMBUS) {
        quirk.process();
    }

    if (!setup_smb_controller() || smbusbase == 0) {
        return;
    }

    for (spdidx = 0; spdidx < MAX_SPD_SLOT; spdidx++) {

        memset(&curspd, 0, sizeof(curspd));
        curspd.slot_num = spdidx;

        if (get_spd(spdidx, 0) != 0xFF) {
            switch(get_spd(spdidx, 2))
            {
                default:
                    continue;
                case 0x12: // DDR5
                    parse_spd_ddr5(&curspd, spdidx);
                    break;
                case 0x0C: // DDR4
                    parse_spd_ddr4(&curspd, spdidx);
                    break;
                case 0x0B: // DDR3
                    parse_spd_ddr3(&curspd, spdidx);
                    break;
                case 0x08: // DDR2
                    parse_spd_ddr2(&curspd, spdidx);
                    break;
                case 0x07: // DDR
                    parse_spd_ddr(&curspd, spdidx);
                    break;
                case 0x04: // SDRAM
                    parse_spd_sdram(&curspd, spdidx);
                    break;
                case 0x01: // RAMBUS - RDRAM
                    if (get_spd(spdidx, 1) == 8) {
                        parse_spd_rdram(&curspd, spdidx);
                    }
                    break;
            }

            if (curspd.isValid) {
                if (spd_line_idx == 0) {
                    prints(LINE_SPD-2, 0, "Memory SPD Information");
                    prints(LINE_SPD-1, 0, "----------------------");
                }

                print_spdi(curspd, spd_line_idx);
                spd_line_idx++;
            }
        }
    }
}

static void print_spdi(spd_info spdi, uint8_t lidx)
{
    uint8_t curcol;
    uint16_t i;

    // Print Slot Index, Module Size, type & Max frequency (Jedec or XMP)
    curcol = printf(LINE_SPD+lidx, 0, " - Slot %i: %kB %s-%i",
                    spdi.slot_num,
                    spdi.module_size * 1024,
                    spdi.type,
                    spdi.freq);

    // Print ECC status
    if (spdi.hasECC) {
        curcol = prints(LINE_SPD+lidx, ++curcol, "ECC");
    }

    // Print XMP/EPP Status
    if (spdi.XMP > 0 && spdi.XMP < 20) {
        curcol = prints(LINE_SPD+lidx, ++curcol, "XMP");
    } else if (spdi.XMP == 20) {
        curcol = prints(LINE_SPD+lidx, ++curcol, "EPP");
    }

    // Print Manufacturer from JEDEC106
    for (i = 0; i < JEP106_CNT; i++) {
        if (spdi.jedec_code == jep106[i].jedec_code) {
            curcol = printf(LINE_SPD+lidx, ++curcol, "- %s", jep106[i].name);
            break;
        }
    }

    // If not present in JEDEC106, display raw JEDEC ID
    if (spdi.jedec_code == 0) {
        curcol = prints(LINE_SPD+lidx, ++curcol, "- Noname");
    } else if (i == JEP106_CNT) {
        curcol = printf(LINE_SPD+lidx, ++curcol, "- Unknown (0x%x)", spdi.jedec_code);
    }

    // Print SKU
    if (*spdi.sku)
        curcol = prints(LINE_SPD+lidx, curcol + 1, spdi.sku);

    // Check manufacturing date and print if valid.
    // fab_year is uint8_t and carries only the last two digits.
    //  - for 0..31 we assume 20xx, and 0 means 2000.
    //  - for 96..99 we assume 19xx.
    //  - values 32..95 and > 99 are considered invalid.
    if (curcol <= 69 && spdi.fab_week <= 53 && spdi.fab_week != 0 &&
        (spdi.fab_year < 32 || (spdi.fab_year >= 96 && spdi.fab_year <= 99))) {
        curcol = printf(LINE_SPD+lidx, ++curcol, "(%02i%02i-W%02i)",
                        spdi.fab_year >= 96 ? 19 : 20,
                        spdi.fab_year, spdi.fab_week);
    }

    // Populate global ram var
    ram.type = spdi.type;
    if (ram.freq == 0 || ram.freq > spdi.freq) {
        ram.freq = spdi.freq;
    }
    if (ram.tCL < spdi.tCL) {
        ram.tCL     = spdi.tCL;
        ram.tCL_dec = spdi.tCL_dec;
        ram.tRCD    = spdi.tRCD;
        ram.tRP     = spdi.tRP;
        ram.tRAS    = spdi.tRAS;
    }
}

static void read_sku(char *sku, uint8_t slot_idx, uint16_t offset, uint8_t max_len)
{
    uint8_t sku_len;

    if (max_len > SPD_SKU_LEN)
        max_len = SPD_SKU_LEN;

    for (sku_len = 0; sku_len < max_len; sku_len++) {
        uint8_t sku_byte = get_spd(slot_idx, offset + sku_len);

        // Stop on the first non-ASCII char
        if (sku_byte < 0x20 || sku_byte > 0x7F)
            break;

        sku[sku_len] = (char)sku_byte;
    }

    // Trim spaces from the end
    while (sku_len && sku[sku_len - 1] == 0x20)
        sku_len--;

    sku[sku_len] = '\0';
}

static void parse_spd_ddr5(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "DDR5";

    // Compute module size for symmetric & asymmetric configuration
    for (int sbyte_adr = 1; sbyte_adr <= 2; sbyte_adr++) {
        uint32_t cur_rank = 0;
        uint8_t sbyte = get_spd(slot_idx, sbyte_adr * 4);

        // SDRAM Density per die
        switch (sbyte & 0x1F)
        {
          default:
            break;
          case 0b00001:
            cur_rank = 512;
            break;
          case 0b00010:
            cur_rank = 1024;
            break;
          case 0b00011:
            cur_rank = 1536;
            break;
          case 0b00100:
            cur_rank = 2048;
            break;
          case 0b00101:
            cur_rank = 3072;
            break;
          case 0b00110:
            cur_rank = 4096;
            break;
          case 0b00111:
            cur_rank = 6144;
            break;
          case 0b01000:
            cur_rank = 8192;
            break;
        }

        // Die per package
        if ((sbyte >> 5) > 1 && (sbyte >> 5) <= 5) {
            cur_rank *= 1U << (((sbyte >> 5) & 7) - 1);
        }

        sbyte = get_spd(slot_idx, 235);
        spdi->hasECC = (((sbyte >> 3) & 3) > 0);

        // Channels per DIMM
        if (((sbyte >> 5) & 3) == 1) {
            cur_rank *= 2;
        }

        // Primary Bus Width per Channel
        cur_rank *= 1U << ((sbyte & 3) + 3);

        // I/O Width
        sbyte = get_spd(slot_idx, (sbyte_adr * 4) + 2);
        cur_rank /= 1U << (((sbyte >> 5) & 3) + 2);

        sbyte = get_spd(slot_idx, 234);

        // Package ranks per Channel
        cur_rank *= 1U << ((sbyte >> 3) & 7);

        // Add current rank to total package size
        spdi->module_size += cur_rank;

        // If not Asymmetrical, don't process the second rank
        if ((sbyte >> 6) == 0) {
            break;
        }
    }

    // Compute Frequency (including XMP)
    uint16_t tCK, tCKtmp, tns;
    int xmp_offset = 0;

    spdi->XMP = ((get_spd(slot_idx, 640) == 0x0C && get_spd(slot_idx, 641) == 0x4A)) ? 3 : 0;

    if (spdi->XMP == 3) {
        // XMP 3.0 (enumerate all profiles to find the fastest)
        tCK = 0;
        for (int offset = 0; offset < 3*64; offset += 64) {
            tCKtmp = get_spd(slot_idx, 710 + offset) << 8 |
                     get_spd(slot_idx, 709 + offset);

            if (tCKtmp != 0 && (tCK == 0 || tCKtmp < tCK)) {
                xmp_offset = offset;
                tCK = tCKtmp;
            }
        }
    } else {
        // JEDEC
        tCK = get_spd(slot_idx, 21) << 8 |
              get_spd(slot_idx, 20);
    }

    if (tCK == 0) {
        return;
    }

    spdi->freq = (float)(1.0f / tCK * 2.0f * 1000.0f * 1000.0f);
    spdi->freq = (spdi->freq + 50) / 100 * 100;

    // Module Timings
    if (spdi->XMP == 3) {
        // ------------------
        // XMP Specifications
        // ------------------

        // CAS# Latency
        tns  = (uint16_t)get_spd(slot_idx, 718 + xmp_offset) << 8 |
               (uint16_t)get_spd(slot_idx, 717 + xmp_offset);
        spdi->tCL = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;
        spdi->tCL += spdi->tCL % 2; // if tCL is odd, round to upper even.

        // RAS# to CAS# Latency
        tns  = (uint16_t)get_spd(slot_idx, 720 + xmp_offset) << 8 |
               (uint16_t)get_spd(slot_idx, 719 + xmp_offset);
        spdi->tRCD = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;

        // RAS# Precharge
        tns  = (uint16_t)get_spd(slot_idx, 722 + xmp_offset) << 8 |
               (uint16_t)get_spd(slot_idx, 721 + xmp_offset);
        spdi->tRP = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;

        // Row Active Time
        tns  = (uint16_t)get_spd(slot_idx, 724 + xmp_offset) << 8 |
               (uint16_t)get_spd(slot_idx, 723 + xmp_offset);
        spdi->tRAS = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;

        // Row Cycle Time
        tns  = (uint16_t)get_spd(slot_idx, 726 + xmp_offset) << 8 |
               (uint16_t)get_spd(slot_idx, 725 + xmp_offset);
        spdi->tRC = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;
    } else {
        // --------------------
        // JEDEC Specifications
        // --------------------

        // CAS# Latency
        tns  = (uint16_t)get_spd(slot_idx, 31) << 8 |
               (uint16_t)get_spd(slot_idx, 30);
        spdi->tCL = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;
        spdi->tCL += spdi->tCL % 2;

        // RAS# to CAS# Latency
        tns  = (uint16_t)get_spd(slot_idx, 33) << 8 |
               (uint16_t)get_spd(slot_idx, 32);
        spdi->tRCD = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;

        // RAS# Precharge
        tns  = (uint16_t)get_spd(slot_idx, 35) << 8 |
               (uint16_t)get_spd(slot_idx, 34);
        spdi->tRP = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;

        // Row Active Time
        tns  = (uint16_t)get_spd(slot_idx, 37) << 8 |
               (uint16_t)get_spd(slot_idx, 36);
        spdi->tRAS = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;

        // Row Cycle Time
        tns  = (uint16_t)get_spd(slot_idx, 39) << 8 |
               (uint16_t)get_spd(slot_idx, 38);
        spdi->tRC = (tns + tCK - DDR5_ROUNDING_FACTOR) / tCK;
    }

    // Module manufacturer
    spdi->jedec_code = (get_spd(slot_idx, 512) & 0x1F) << 8;
    spdi->jedec_code |= get_spd(slot_idx, 513) & 0x7F;

    read_sku(spdi->sku, slot_idx, 521, 30);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 515));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 516));

    spdi->isValid = true;
}

static void parse_spd_ddr4(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "DDR4";

    // Compute module size in MB with shifts
    spdi->module_size = 1U << (
                               ((get_spd(slot_idx, 4) & 0xF) + 5)   +  // Total SDRAM capacity: (256 Mbits << byte4[3:0] with an oddity for values >= 8) / 1 KB
                               ((get_spd(slot_idx, 13) & 0x7) + 3)  -  // Primary Bus Width: 8 << byte13[2:0]
                               ((get_spd(slot_idx, 12) & 0x7) + 2)  +  // SDRAM Device Width: 4 << byte12[2:0]
                               ((get_spd(slot_idx, 12) >> 3) & 0x7) +  // Number of Ranks: byte12[5:3]
                               ((get_spd(slot_idx, 6) >> 4) & 0x7)     // Die count - 1: byte6[6:4]
                              );

    spdi->hasECC = (((get_spd(slot_idx, 13) >> 3) & 1) == 1);

    // Module max clock
    float tns, tCK, ramfreq, fround;

    if (get_spd(slot_idx, 384) == 0x0C && get_spd(slot_idx, 385) == 0x4A) {
        // Max XMP
        tCK = (uint8_t)get_spd(slot_idx, 396) * 0.125f +
              (int8_t)get_spd(slot_idx, 431)  * 0.001f;

        spdi->XMP = 2;

    } else {
        // Max JEDEC
        tCK = (uint8_t)get_spd(slot_idx, 18) * 0.125f +
              (int8_t)get_spd(slot_idx, 125) * 0.001f;
    }

    ramfreq = 1.0f / tCK * 2.0f * 1000.0f;

    // Round DRAM Freq to nearest x00/x33/x66
    fround = ((int)(ramfreq * 0.01 + .5) / 0.01) - ramfreq;
    ramfreq += fround;

    if (fround < -16.5) {
        ramfreq += 33;
    } else if (fround > 16.5) {
        ramfreq -= 34;
    }

    spdi->freq = ramfreq;

    // Module Timings
    if (spdi->XMP == 2) {
        // ------------------
        // XMP Specifications
        // ------------------

        // CAS# Latency
        tns  = (uint8_t)get_spd(slot_idx, 401) * 0.125f +
               (int8_t)get_spd(slot_idx, 430)  * 0.001f;
        spdi->tCL = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // RAS# to CAS# Latency
        tns  = (uint8_t)get_spd(slot_idx, 402) * 0.125f +
               (int8_t)get_spd(slot_idx, 429)  * 0.001f;
        spdi->tRCD = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // RAS# Precharge
        tns  = (uint8_t)get_spd(slot_idx, 403) * 0.125f +
               (int8_t)get_spd(slot_idx, 428)  * 0.001f;
        spdi->tRP = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // Row Active Time
        tns = (uint8_t)get_spd(slot_idx, 405) * 0.125f +
              (int8_t)get_spd(slot_idx, 427)  * 0.001f  +
              (uint8_t)(get_spd(slot_idx, 404) & 0x0F) * 32.0f;
        spdi->tRAS = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // Row Cycle Time
        tns = (uint8_t)get_spd(slot_idx, 406) * 0.125f +
              (uint8_t)(get_spd(slot_idx, 404) >> 4) * 32.0f;
        spdi->tRC = (uint16_t)(tns/tCK + ROUNDING_FACTOR);
    } else {
        // --------------------
        // JEDEC Specifications
        // --------------------

        // CAS# Latency
        tns  = (uint8_t)get_spd(slot_idx, 24) * 0.125f +
               (int8_t)get_spd(slot_idx, 123) * 0.001f;
        spdi->tCL = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // RAS# to CAS# Latency
        tns  = (uint8_t)get_spd(slot_idx, 25) * 0.125f +
               (int8_t)get_spd(slot_idx, 122) * 0.001f;
        spdi->tRCD = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // RAS# Precharge
        tns  = (uint8_t)get_spd(slot_idx, 26) * 0.125f +
               (int8_t)get_spd(slot_idx, 121) * 0.001f;
        spdi->tRP = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // Row Active Time
        tns = (uint8_t)get_spd(slot_idx, 28) * 0.125f +
              (uint8_t)(get_spd(slot_idx, 27) & 0x0F) * 32.0f;
        spdi->tRAS = (uint16_t)(tns/tCK + ROUNDING_FACTOR);

        // Row Cycle Time
        tns = (uint8_t)get_spd(slot_idx, 29) * 0.125f +
              (uint8_t)(get_spd(slot_idx, 27) >> 4) * 32.0f;
        spdi->tRC = (uint16_t)(tns/tCK + ROUNDING_FACTOR);
    }

    // Module manufacturer
    spdi->jedec_code  = ((uint16_t)(get_spd(slot_idx, 320) & 0x1F)) << 8;
    spdi->jedec_code |= get_spd(slot_idx, 321) & 0x7F;

    read_sku(spdi->sku, slot_idx, 329, 20);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 323));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 324));

    spdi->isValid = true;
}

static void parse_spd_ddr3(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "DDR3";

    // Compute module size in MB with shifts
    spdi->module_size = 1U << (
                               ((get_spd(slot_idx, 4) & 0xF) + 5)  +  // Total SDRAM capacity: (256 Mbits << byte4[3:0]) / 1 KB
                               ((get_spd(slot_idx, 8) & 0x7) + 3)  -  // Primary Bus Width: 8 << byte8[2:0]
                               ((get_spd(slot_idx, 7) & 0x7) + 2)     // SDRAM Device Width: 4 << byte7[2:0]
                              );

    spdi->module_size *= ((get_spd(slot_idx, 7) >> 3) & 0x7) + 1;     // Number of Ranks: byte7[5:3]

    spdi->hasECC = (((get_spd(slot_idx, 8) >> 3) & 1) == 1);

    uint8_t tck = get_spd(slot_idx, 12);
    uint8_t tck2 = get_spd(slot_idx, 221);

    if (get_spd(slot_idx, 176) == 0x0C && get_spd(slot_idx, 177) == 0x4A) {
        tck = get_spd(slot_idx, 186);

        // Check if profile #2 is faster
        if (tck2 > 5 && tck2 < tck) {
            tck = tck2;
        }

        spdi->XMP = 1;
    }

    // Module jedec speed
    switch (tck) {
        default:
            spdi->freq = 0;
            break;
        case 20:
            spdi->freq = 800;
            break;
        case 15:
            spdi->freq = 1066;
            break;
        case 12:
            spdi->freq = 1333;
            break;
        case 10:
            spdi->freq = 1600;
            break;
        case 9:
            spdi->freq = 1866;
            break;
        case 8:
            spdi->freq = 2133;
            break;
        case 7:
            spdi->freq = 2400;
            break;
        case 6:
            spdi->freq = 2666;
            break;
    }


    // Module Timings
    float tckns, tns, mtb;

    if (spdi->XMP == 1) {
        // ------------------
        // XMP Specifications
        // ------------------
        float mtb_dividend = get_spd(slot_idx, 180);
        float mtb_divisor = get_spd(slot_idx, 181);

        mtb = (mtb_divisor == 0) ? 0.125f : mtb_dividend / mtb_divisor;

        tckns = (float)get_spd(slot_idx, 186);

        // XMP Draft with non-standard MTB divisors (!= 0.125)
        if (mtb_divisor == 12.0f && tckns == 10.0f) {
            spdi->freq = 2400;
        } else if (mtb_divisor == 14.0f && tckns == 15.0f) {
            spdi->freq = 1866;
        }

        if (spdi->freq >= 1866 && mtb_divisor == 8.0f) {
            tckns -= 0.4f;
        }

        tckns *= mtb;

        // CAS# Latency
        tns  = get_spd(slot_idx, 187);
        spdi->tCL = (uint16_t)((tns*mtb)/tckns + ROUNDING_FACTOR);

        // RAS# to CAS# Latency
        tns  = get_spd(slot_idx, 192);
        spdi->tRCD = (uint16_t)((tns*mtb)/tckns + ROUNDING_FACTOR);

        // RAS# Precharge
        tns  = get_spd(slot_idx, 191);
        spdi->tRP = (uint16_t)((tns*mtb)/tckns + ROUNDING_FACTOR);

        // Row Active Time
        tns  = (uint16_t)((get_spd(slot_idx, 194) & 0x0F) << 8 |
               get_spd(slot_idx, 195));

        spdi->tRAS = (uint16_t)((tns*mtb)/tckns + ROUNDING_FACTOR);

        // Row Cycle Time
        tns  = (uint16_t)((get_spd(slot_idx, 194) & 0xF0) << 4 |
               get_spd(slot_idx, 196));
        spdi->tRC = (uint16_t)((tns*mtb)/tckns + ROUNDING_FACTOR);
    } else {
        // --------------------
        // JEDEC Specifications
        // --------------------
        mtb = 0.125f;

        tckns = (uint8_t)get_spd(slot_idx, 12) * mtb +
                (int8_t)get_spd(slot_idx, 34) * 0.001f;

        // CAS# Latency
        tns  = (uint8_t)get_spd(slot_idx, 16) * mtb +
               (int8_t)get_spd(slot_idx, 35) * 0.001f;
        spdi->tCL = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // RAS# to CAS# Latency
        tns  = (uint8_t)get_spd(slot_idx, 18) * mtb +
               (int8_t)get_spd(slot_idx, 36) * 0.001f;
        spdi->tRCD = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // RAS# Precharge
        tns  = (uint8_t)get_spd(slot_idx, 20) * mtb +
               (int8_t)get_spd(slot_idx, 37) * 0.001f;
        spdi->tRP = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // Row Active Time
        tns = (uint8_t)get_spd(slot_idx, 22) * mtb +
              (uint8_t)(get_spd(slot_idx, 21) & 0x0F) * 32.0f;
        spdi->tRAS = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // Row Cycle Time
        tns = (uint8_t)get_spd(slot_idx, 23) * mtb +
              (uint8_t)(get_spd(slot_idx, 21) >> 4) * 32.0f + 1;
        spdi->tRC = (uint16_t)(tns/tckns  + ROUNDING_FACTOR);
    }

    // Module manufacturer
    spdi->jedec_code  = ((uint16_t)(get_spd(slot_idx, 117) & 0x1F)) << 8;
    spdi->jedec_code |= get_spd(slot_idx, 118) & 0x7F;

    read_sku(spdi->sku, slot_idx, 128, 18);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 120));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 121));

    spdi->isValid = true;
}

static void parse_spd_ddr2(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "DDR2";

    // Compute module size in MB
    switch (get_spd(slot_idx, 31)) {
        case 1:
            spdi->module_size = 1024;
            break;
        case 2:
            spdi->module_size = 2048;
            break;
        case 4:
            spdi->module_size = 4096;
            break;
        case 8:
            spdi->module_size = 8192;
            break;
        case 16:
            spdi->module_size = 16384;
            break;
        case 32:
            spdi->module_size = 128;
            break;
        case 64:
            spdi->module_size = 256;
            break;
        default:
        case 128:
            spdi->module_size = 512;
            break;
    }

    spdi->module_size *= (get_spd(slot_idx, 5) & 7) + 1;

    spdi->hasECC = ((get_spd(slot_idx, 11) >> 1) == 1);

    float tckns, tns;
    uint8_t tbyte;

    // Module EPP Detection (we only support Full profiles)
    uint8_t epp_offset = 0;
    if (get_spd(slot_idx, 99) == 0x6D && get_spd(slot_idx, 102) == 0xB1) {
        epp_offset = (get_spd(slot_idx, 103) & 0x3) * 12;
        tbyte = get_spd(slot_idx, 109 + epp_offset);
        spdi->XMP = 20;
    } else {
        tbyte = get_spd(slot_idx, 9);
    }

    // Module speed
    tckns = (tbyte & 0xF0) >> 4;
    tbyte &= 0xF;

    if (tbyte < 10) {
        tckns += (tbyte & 0xF) * 0.1f;
    } else if (tbyte == 10) {
        tckns += 0.25f;
    } else if (tbyte == 11) {
        tckns += 0.33f;
    } else if (tbyte == 12) {
        tckns += 0.66f;
    } else if (tbyte == 13) {
        tckns += 0.75f;
    } else if (tbyte == 14) { // EPP Specific
        tckns += 0.875f;
    }

    spdi->freq = (float)(1.0f / tckns * 1000.0f * 2.0f);

    if (spdi->XMP == 20) {
        // Module Timings (EPP)
        // CAS# Latency
        tbyte = get_spd(slot_idx, 110 + epp_offset);
        for (int shft = 0; shft < 7; shft++) {
            if ((tbyte >> shft) & 1) {
                spdi->tCL = shft;
            }
        }

        // RAS# to CAS# Latency
        tbyte = get_spd(slot_idx, 111 + epp_offset);
        tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
        spdi->tRCD = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // RAS# Precharge
        tbyte = get_spd(slot_idx, 112 + epp_offset);
        tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
        spdi->tRP = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // Row Active Time
        tns = get_spd(slot_idx, 113 + epp_offset);
        spdi->tRAS = (uint16_t)(tns/tckns + ROUNDING_FACTOR);
    } else {
        // Module Timings (JEDEC)
        // CAS# Latency
        tbyte = get_spd(slot_idx, 18);
        for (int shft = 0; shft < 7; shft++) {
            if ((tbyte >> shft) & 1) {
                spdi->tCL = shft;
            }
        }

        // RAS# to CAS# Latency
        tbyte = get_spd(slot_idx, 29);
        tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
        spdi->tRCD = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // RAS# Precharge
        tbyte = get_spd(slot_idx, 27);
        tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
        spdi->tRP = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

        // Row Active Time
        tns = get_spd(slot_idx, 30);
        spdi->tRAS = (uint16_t)(tns/tckns + ROUNDING_FACTOR);
    }

    // Module manufacturer
    uint8_t contcode;
    for (contcode = 64; contcode < 72; contcode++) {
        if (get_spd(slot_idx, contcode) != 0x7F) {
            break;
        }
    }

    spdi->jedec_code  = ((uint16_t)(contcode - 64)) << 8;
    spdi->jedec_code |= get_spd(slot_idx, contcode) & 0x7F;

    read_sku(spdi->sku, slot_idx, 73, 18);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 93));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 94));

    spdi->isValid = true;
}

static void parse_spd_ddr(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "DDR";

    // Compute module size in MB
    switch (get_spd(slot_idx, 31)) {
        case 1:
            spdi->module_size = 1024;
            break;
        case 2:
            spdi->module_size = 2048;
            break;
        case 4:
            spdi->module_size = 4096;
            break;
        case 8:
            spdi->module_size = 32;
            break;
        case 16:
            spdi->module_size = 64;
            break;
        case 32:
            spdi->module_size = 128;
            break;
        case 64:
            spdi->module_size = 256;
            break;
        case 128:
            spdi->module_size = 512;
            break;
        default: // we don't support asymmetric banking
            spdi->module_size = 0;
            break;
    }

    spdi->module_size *= get_spd(slot_idx, 5);

    spdi->hasECC = ((get_spd(slot_idx, 11) >> 1) == 1);

    // Module speed
    float tns, tckns;
    uint8_t spd_byte9 = get_spd(slot_idx, 9);
    tckns = (spd_byte9 >> 4) + (spd_byte9 & 0xF) * 0.1f;

    spdi->freq = (uint16_t)(1.0f / tckns * 1000.0f * 2.0f);

    // Module Timings
    uint8_t spd_byte18 = get_spd(slot_idx, 18);
    for (int shft = 0; shft < 7; shft++) {
        if ((spd_byte18 >> shft) & 1) {
            spdi->tCL = 1.0f + shft * 0.5f;
            // Check tCL decimal (x.5 CAS)
            if (shft == 1 || shft == 3 || shft == 5) {
                spdi->tCL_dec = 5;
            } else {
                spdi->tCL_dec = 0;
            }
        }
    }

    tns = (get_spd(slot_idx, 29) >> 2) +
          (get_spd(slot_idx, 29) & 0x3) * 0.25f;
    spdi->tRCD = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

    tns = (get_spd(slot_idx, 27) >> 2) +
          (get_spd(slot_idx, 27) & 0x3) * 0.25f;
    spdi->tRP = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

    spdi->tRAS = (uint16_t)((float)get_spd(slot_idx, 30)/tckns + ROUNDING_FACTOR);

    // Module manufacturer
    uint8_t contcode;
    for (contcode = 64; contcode < 72; contcode++) {
        if (get_spd(slot_idx, contcode) != 0x7F) {
            break;
        }
    }

    spdi->jedec_code = (contcode - 64) << 8;
    spdi->jedec_code |= get_spd(slot_idx, contcode) & 0x7F;

    read_sku(spdi->sku, slot_idx, 73, 18);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 93));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 94));

    spdi->isValid = true;
}

static void parse_spd_rdram(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "RDRAM";

    // Compute module size in MB
    uint8_t tbyte = get_spd(slot_idx, 5);
    switch(tbyte) {
        case 0x84:
            spdi->module_size = 8;
            break;
        case 0xC5:
            spdi->module_size = 16;
            break;
        default:
            return;
    }

    spdi->module_size *= get_spd(slot_idx, 99);

    tbyte = get_spd(slot_idx, 4);
    if (tbyte > 0x96) {
        spdi->module_size *= 1 + (((tbyte & 0xF0) >> 4) - 9) + ((tbyte & 0xF) - 6);
    }

    spdi->hasECC = (get_spd(slot_idx, 100) == 0x12) ? true : false;

    // Module speed
    tbyte = get_spd(slot_idx, 15);
    switch(tbyte) {
        case 0x1A:
            spdi->freq = 600;
            break;
        case 0x15:
            spdi->freq = 711;
            break;
        case 0x13:
            spdi->freq = 800;
            break;
        case 0xe:
            spdi->freq = 1066;
            break;
        case 0xc:
            spdi->freq = 1200;
            break;
        default:
            return;
    }

    // Module Timings
    spdi->tCL = get_spd(slot_idx, 14);
    spdi->tRCD = get_spd(slot_idx, 12);
    spdi->tRP = get_spd(slot_idx, 10);
    spdi->tRAS = get_spd(slot_idx, 11);

    // Module manufacturer
    uint8_t contcode;
    for (contcode = 64; contcode < 72; contcode++) {
        if (get_spd(slot_idx, contcode) != 0x7F) {
            break;
        }
    }

    spdi->jedec_code  = ((uint16_t)(contcode - 64)) << 8;
    spdi->jedec_code |= get_spd(slot_idx, contcode) & 0x7F;

    read_sku(spdi->sku, slot_idx, 73, 18);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 93));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 94));

    spdi->isValid = true;
}

static void parse_spd_sdram(spd_info *spdi, uint8_t slot_idx)
{
    spdi->type = "SDRAM";

    uint8_t spd_byte3  = get_spd(slot_idx, 3) & 0x0F; // Number of Row Addresses (2 x 4 bits, upper part used if asymmetrical banking used)
    uint8_t spd_byte4  = get_spd(slot_idx, 4) & 0x0F; // Number of Column Addresses (2 x 4 bits, upper part used if asymmetrical banking used)
    uint8_t spd_byte5  = get_spd(slot_idx, 5);        // Number of Banks on module (8 bits)
    uint8_t spd_byte17 = get_spd(slot_idx, 17);       // SDRAM Device Attributes, Number of Banks on the discrete SDRAM Device (8 bits)

    // Size in MB
    if (   (spd_byte3 != 0)
        && (spd_byte4 != 0)
        && (spd_byte3 + spd_byte4 > 17)
        && (spd_byte3 + spd_byte4 <= 29)
        && (spd_byte5 <= 8)
        && (spd_byte17 <= 8)
       ) {
        spdi->module_size = (1U << (spd_byte3 + spd_byte4 - 17)) * ((uint16_t)spd_byte5 * spd_byte17);
    } else {
        spdi->module_size = 0;
    }

    spdi->hasECC = ((get_spd(slot_idx, 11) >> 1) == 1);

    // Module speed
    float tns, tckns;
    uint8_t spd_byte9 = get_spd(slot_idx, 9);
    tckns = (spd_byte9 >> 4) + (spd_byte9 & 0xF) * 0.1f;

    spdi->freq = (uint16_t)(1000.0f / tckns);

    // Module Timings
    uint8_t spd_byte18 = get_spd(slot_idx, 18);
    for (int shft = 0; shft < 7; shft++) {
        if ((spd_byte18 >> shft) & 1) {
            spdi->tCL = shft + 1;
        }
    }

    tns = get_spd(slot_idx, 29);
    spdi->tRCD = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

    tns = get_spd(slot_idx, 27);
    spdi->tRP = (uint16_t)(tns/tckns + ROUNDING_FACTOR);

    spdi->tRAS = (uint16_t)(get_spd(slot_idx, 30)/tckns + ROUNDING_FACTOR);

    // Module manufacturer
    uint8_t contcode;
    for (contcode = 64; contcode < 72; contcode++) {
        if (get_spd(slot_idx, contcode) != 0x7F) {
            break;
        }
    }

    spdi->jedec_code  = ((uint16_t)(contcode - 64)) << 8;
    spdi->jedec_code |= get_spd(slot_idx, contcode) & 0x7F;

    read_sku(spdi->sku, slot_idx, 73, 18);

    spdi->fab_year = bcd_to_ui8(get_spd(slot_idx, 93));
    spdi->fab_week = bcd_to_ui8(get_spd(slot_idx, 94));

    spdi->isValid = true;
}


// --------------------------
// SMBUS Controller Functions
// --------------------------

static bool setup_smb_controller(void)
{
    uint16_t vid, did;

    for (smbdev = 0; smbdev < 32; smbdev++) {
        for (smbfun = 0; smbfun < 8; smbfun++) {
            vid = pci_config_read16(0, smbdev, smbfun, 0);
            if (vid != 0xFFFF) {
                did = pci_config_read16(0, smbdev, smbfun, 2);
                if (did != 0xFFFF) {
                    if (find_smb_controller(vid, did)) {
                        return true;
                    }
                }
            }
        }
    }
    smbus_id = 0;
    return false;
}

// ----------------------------------------------------------
// WARNING: Be careful when adding a controller ID!
// Incorrect SMB accesses (ie: on bank switch) can brick your
// motherboard or your memory module.
//                           ----
// No Pull Request including a new SMBUS Controller will be
// accepted without a proof (screenshot) that it has been
// tested successfully on a real motherboard.
// ----------------------------------------------------------

// PCI device IDs for Intel i801 SMBus controller.
static const uint16_t intel_ich5_dids[] =
{
    0x2413,  // 82801AA (ICH)
    0x2423,  // 82801AB (ICH)
    0x2443,  // 82801BA (ICH2)
    0x2483,  // 82801CA (ICH3)
    0x24C3,  // 82801DB (ICH4)
    0x24D3,  // 82801E (ICH5)
    0x25A4,  // 6300ESB
    0x266A,  // 82801F (ICH6)
    0x269B,  // 6310ESB/6320ESB
    0x27DA,  // 82801G (ICH7)
    0x283E,  // 82801H (ICH8)
    0x2930,  // 82801I (ICH9)
    0x5032,  // EP80579 (Tolapai)
    0x3A30,  // ICH10
    0x3A60,  // ICH10
    0x3B30,  // 5/3400 Series (PCH)
    0x1C22,  // 6 Series (PCH)
    0x1D22,  // Patsburg (PCH)
    0x1D70,  // Patsburg (PCH) IDF
    0x1D71,  // Patsburg (PCH) IDF
    0x1D72,  // Patsburg (PCH) IDF
    0x2330,  // DH89xxCC (PCH)
    0x1E22,  // Panther Point (PCH)
    0x8C22,  // Lynx Point (PCH)
    0x9C22,  // Lynx Point-LP (PCH)
    0x1F3C,  // Avoton (SOC)
    0x8D22,  // Wellsburg (PCH)
    0x8D7D,  // Wellsburg (PCH) MS
    0x8D7E,  // Wellsburg (PCH) MS
    0x8D7F,  // Wellsburg (PCH) MS
    0x23B0,  // Coleto Creek (PCH)
    0x8CA2,  // Wildcat Point (PCH)
    0x9CA2,  // Wildcat Point-LP (PCH)
    0x0F12,  // BayTrail (SOC)
    0x2292,  // Braswell (SOC)
    0xA123,  // Sunrise Point-H (PCH)
    0x9D23,  // Sunrise Point-LP (PCH)
    0x19DF,  // Denverton  (SOC)
    0x1BC9,  // Emmitsburg (PCH)
    0xA1A3,  // Lewisburg (PCH)
    0xA223,  // Lewisburg Super (PCH)
    0xA2A3,  // Kaby Lake (PCH-H)
    0x31D4,  // Gemini Lake (SOC)
    0xA323,  // Cannon Lake-H (PCH)
    0x9DA3,  // Cannon Lake-LP (PCH)
    0x18DF,  // Cedar Fork (PCH)
    0x34A3,  // Ice Lake-LP (PCH)
    0x38A3,  // Ice Lake-N (PCH)
    0x02A3,  // Comet Lake (PCH)
    0x06A3,  // Comet Lake-H (PCH)
    0x4B23,  // Elkhart Lake (PCH)
    0xA0A3,  // Tiger Lake-LP (PCH)
    0x43A3,  // Tiger Lake-H (PCH)
    0x4DA3,  // Jasper Lake (SOC)
    0xA3A3,  // Comet Lake-V (PCH)
    0x7AA3,  // Alder Lake-S (PCH)
    0x51A3,  // Alder Lake-P (PCH)
    0x54A3,  // Alder Lake-M (PCH)
    0x7A23,  // Raptor Lake-S (PCH)
};

static bool find_in_did_array(uint16_t did, const uint16_t * ids, unsigned int size)
{
    for (unsigned int i = 0; i < size; i++) {
        if (*ids++ == did) {
            return true;
        }
    }
    return false;
}

static bool find_smb_controller(uint16_t vid, uint16_t did)
{
    smbus_id = (((uint32_t)vid) << 16) | did;
    switch(vid)
    {
        case PCI_VID_INTEL:
        {
            if (find_in_did_array(did, intel_ich5_dids, sizeof(intel_ich5_dids) / sizeof(intel_ich5_dids[0]))) {
                return ich5_get_smb();
            }
            if (did == 0x7113) { // 82371AB/EB/MB PIIX4
                return piix4_get_smb(PIIX4_SMB_BASE_ADR_DEFAULT);
            }
            // 0x719B 82440/82443MX PMC - PIIX4
            // 0x0F13 ValleyView SMBus Controller ?
            // 0x8119 US15W ?
            return false;
        }

        case PCI_VID_HYGON:
        case PCI_VID_AMD:
            switch(did)
            {
                // case 0x740B: // AMD756
                // case 0x7413: // AMD766
                // case 0x7443: // AMD768
                // case 0x746B: // AMD8111_SMBUS
                // case 0x746A: // AMD8111_SMBUS2
                case 0x780B: // AMD FCH (Pre-Zen)
                    return amd_sb_get_smb();
                case 0x790B: // AMD FCH (Zen 2/3)
                    return fch_zen_get_smb();
                default:
                return false;
            }
            break;

        case PCI_VID_ATI:
            switch(did)
            {
                // case 0x4353: // SB200
                // case 0x4363: // SB300
                case 0x4372:    // SB400
                    return piix4_get_smb(PIIX4_SMB_BASE_ADR_DEFAULT);
                case 0x4385:    // SB600+
                    return amd_sb_get_smb();
                default:
                    return false;
            }
            break;

        case PCI_VID_NVIDIA:
            switch(did)
            {
                // case 0x01B4: // nForce
                case 0x0064:    // nForce 2
                // case 0x0084: // nForce 2 Mobile
                // case 0x00E4: // nForce 3
                // case 0x0034: // MCP04
                // case 0x0052: // nForce 4
                case 0x0264:    // nForce 410/430 MCP
                case 0x03EB:    // nForce 630a
                // case 0x0446: // nForce 520
                // case 0x0542: // nForce 560
                case 0x0752:    // nForce 720a
                // case 0x07D8: // nForce 630i
                // case 0x0AA2: // nForce 730i
                // case 0x0D79: // MCP89
                case 0x0368:    // nForce 680a/680i/780i/790i
                    return nv_mcp_get_smb();
                default:
                    return false;
            }
            break;

        case PCI_VID_SIS:
            switch(did)
            {
                // case 0x0008:
                    // SiS5595, SiS630 or other SMBus controllers - it's complicated.
                // case 0x0016:
                    // SiS961/2/3, known as "SiS96x" SMBus controllers.
                // case 0x0018:
                // case 0x0964:
                    // SiS630 SMBus controllers.
                default:
                    return false;
            }
            break;

        case PCI_VID_VIA:
            switch(did)
            {
                // case 0x3040: // 82C586_3
                    // via SMBus controller.
                // case 0x3050: // 82C596_3
                    // Try SMB base address = 0x90, then SMB base address = 0x80
                    // viapro SMBus controller, i.e. PIIX4 with a small quirk.
                // case 0x3051: // 82C596B_3
                case 0x3057: // 82C686_4
                // case 0x8235: // 8231_4
                    // SMB base address = 0x90
                    // viapro SMBus controller, i.e. PIIX4.
                    return piix4_get_smb(PIIX4_SMB_BASE_ADR_DEFAULT);
                // case 0x3074: // 8233_0
                // case 0x3147: // 8233A
                case 0x3177: // 8235
                // case 0x3227: // 8237
                // case 0x3337: // 8237A
                // case 0x3372: // 8237S
                // case 0x3287: // 8251
                // case 0x8324: // CX700
                // case 0x8353: // VX800
                // case 0x8409: // VX855
                // case 0x8410: // VX900
                    // SMB base address = 0xD0
                    // viapro I2C controller, i.e. PIIX4 with a small quirk.
                    return piix4_get_smb(PIIX4_SMB_BASE_ADR_VIAPRO);
                default:
                    return false;
            }
            break;

        case PCI_VID_EFAR:
            switch(did)
            {
                // case 0x9463: // SLC90E66_3: PIIX4
                default:
                    return false;
            }
            break;

        case PCI_VID_ALI:
            switch(did)
            {
                // case 0x1563: // ali1563 (M1563) SMBus controller, nearly compatible with PIIX4 according to Linux i2c-ali1563 driver.
                // case 0x7101: // ali1535 (M1535) or ali15x3 (M1533/M1543) SMBus controllers
                default:
                    return false;
            }
            break;

        case PCI_VID_SERVERWORKS:
            switch(did)
            {
                case 0x0201: // CSB5
                    // From Linux i2c-piix4 driver: unlike its siblings, this model needs a quirk.
                    extra_initial_sleep_for_smb_transaction = 2100 - 500;
                // Fall through.
                // case 0x0200: // OSB4
                // case 0x0203: // CSB6
                // case 0x0205: // HT1000SB
                // case 0x0408: // HT1100LD
                    return piix4_get_smb(PIIX4_SMB_BASE_ADR_DEFAULT);
                default:
                    return false;
            }
            break;
        default:
            return false;
    }
    return false;
}

// ----------------------
// PIIX4 SMBUS Controller
// ----------------------

static bool piix4_get_smb(uint8_t address)
{
    uint16_t x = pci_config_read16(0, smbdev, smbfun, address) & 0xFFF0;

    if (x != 0) {
        smbusbase = x;
        return true;
    }

    return false;
}

// ----------------------------
// i801 / ICH5 SMBUS Controller
// ----------------------------

static bool ich5_get_smb(void)
{
    uint16_t x;

    x = pci_config_read16(0, smbdev, smbfun, 0x20);
    smbusbase = x & 0xFFF0;

    // Enable I2C Bus
    uint8_t temp = pci_config_read8(0, smbdev, smbfun, 0x40);
    if ((temp & 4) == 0) {
        pci_config_write8(0, smbdev, smbfun, 0x40, temp | 0x04);
    }

    // Reset SMBUS Controller
    __outb(__inb(SMBHSTSTS) & 0x1F, SMBHSTSTS);
    usleep(1000);

    return (smbusbase != 0);
}

// --------------------
// AMD SMBUS Controller
// --------------------

static bool amd_sb_get_smb(void)
{
    uint8_t rev_id;
    uint16_t pm_reg;

    rev_id = pci_config_read8(0, smbdev, smbfun, 0x08);

    if ((smbus_id & 0xFFFF) == 0x4385 && rev_id <= 0x3D) {
        // Older AMD SouthBridge (SB700 & older) use PIIX4 registers
        return piix4_get_smb(PIIX4_SMB_BASE_ADR_DEFAULT);
    } else if ((smbus_id & 0xFFFF) == 0x780B && rev_id == 0x42) {
        // Latest Pre-Zen APUs use the newer Zen PM registers
        return fch_zen_get_smb();
    } else {
         // AMD SB (SB800 up to pre-FT3/FP4/AM4) uses specific registers
        __outb(AMD_SMBUS_BASE_REG + 1, AMD_INDEX_IO_PORT);
        pm_reg = __inb(AMD_DATA_IO_PORT) << 8;
        __outb(AMD_SMBUS_BASE_REG, AMD_INDEX_IO_PORT);
        pm_reg |= __inb(AMD_DATA_IO_PORT) & 0xE0;

        if (pm_reg != 0xFFE0 && pm_reg != 0) {
            smbusbase = pm_reg;
            return true;
        }
    }

    return false;
}

static bool fch_zen_get_smb(void)
{
    uint16_t pm_reg;

    __outb(AMD_PM_INDEX + 1, AMD_INDEX_IO_PORT);
    pm_reg = __inb(AMD_DATA_IO_PORT) << 8;
    __outb(AMD_PM_INDEX, AMD_INDEX_IO_PORT);
    pm_reg |= __inb(AMD_DATA_IO_PORT);

    // Special case for AMD Family 19h & Extended Model > 4 (get smb address in memory)
    if ((imc_type == IMC_K19_CZN || imc_type == IMC_K19_RPL) && pm_reg == 0xFFFF) {
        smbusbase = ((*(const uint32_t *)(0xFED80000 + 0x300) >> 8) & 0xFF) << 8;
        return true;
    }

    // Check if IO Smbus is enabled.
    if ((pm_reg & 0x10) == 0) {
        return false;
    }

    if ((pm_reg & 0xFF00) != 0) {
        smbusbase = pm_reg & 0xFF00;
        return true;
    }

    return false;
}

// -----------------------
// nVidia SMBUS Controller
// -----------------------

static bool nv_mcp_get_smb(void)
{
    int smbus_base_adr;

    if ((smbus_id & 0xFFFF) >= 0x200) {
        smbus_base_adr = NV_SMBUS_ADR_REG;
    } else {
        smbus_base_adr = NV_OLD_SMBUS_ADR_REG;
    }

    // nForce SB has 2 I2C Busses. SPD is located on first I2C Bus.
    uint16_t x = pci_config_read16(0, smbdev, smbfun, smbus_base_adr) & 0xFFFC;

    if (x != 0) {
        smbusbase = x;
        return true;
    }

    return false;
}

// ------------------
// get_spd() function
// ------------------

static uint8_t get_spd(uint8_t slot_idx, uint16_t spd_adr)
{
    switch ((smbus_id >> 16) & 0xFFFF) {
      case PCI_VID_NVIDIA:
        return nf_read_spd_byte(slot_idx, (uint8_t)spd_adr);
      default:
        return ich5_read_spd_byte(slot_idx, spd_adr);
    }
}

/*************************************************************************************
/ *****************************         WARNING          *****************************
/ ************************************************************************************
/   Be absolutely sure to know what you're doing before changing the function below!
/       You can easily WRITE into the SPD (especially with DDR5) and corrupt it
/                /!\  Your RAM modules will not work anymore  /!\
/ *************************************************************************************/

static uint8_t ich5_read_spd_byte(uint8_t smbus_adr, uint16_t spd_adr)
{
    smbus_adr += 0x50;

    if (dmi_memory_device->type == DMI_DDR4) {
        // Switch page if needed (DDR4)
        if (spd_adr > 0xFF && spd_page != 1) {
            __outb((0x37 << 1) | I2C_WRITE, SMBHSTADD);
            __outb(SMBHSTCNT_BYTE_DATA, SMBHSTCNT);

            ich5_process(); // return should 0x42 or 0x44
            spd_page = 1;

        } else if (spd_adr <= 0xFF && spd_page != 0) {
            __outb((0x36 << 1) | I2C_WRITE, SMBHSTADD);
            __outb(SMBHSTCNT_BYTE_DATA, SMBHSTCNT);

            ich5_process();
            spd_page = 0;
        }

        if (spd_adr > 0xFF) {
            spd_adr -= 0x100;
        }
    } else if (dmi_memory_device->type == DMI_DDR5) {
        // Switch page if needed (DDR5)
        uint8_t adr_page = spd_adr / 128;

        if (adr_page != spd_page || last_adr != smbus_adr) {
            __outb((smbus_adr << 1) | I2C_WRITE, SMBHSTADD);
            __outb(SPD5_MR11 & 0x7F, SMBHSTCMD);
            __outb(adr_page, SMBHSTDAT0);
            __outb(SMBHSTCNT_BYTE_DATA, SMBHSTCNT);

            ich5_process();

            spd_page = adr_page;
            last_adr = smbus_adr;
         }

          spd_adr -= adr_page * 128;
          spd_adr |= 0x80;
    }

    __outb((smbus_adr << 1) | I2C_READ, SMBHSTADD);
    __outb(spd_adr, SMBHSTCMD);
    __outb(SMBHSTCNT_BYTE_DATA, SMBHSTCNT);

    if (ich5_process() == 0) {
        return __inb(SMBHSTDAT0);
    } else {
        return 0xFF;
    }
}

static uint8_t ich5_process(void)
{
    uint8_t status;
    uint16_t timeout = 0;

    status = __inb(SMBHSTSTS) & 0x1F;

    if (status != 0x00) {
        __outb(status, SMBHSTSTS);
        usleep(500);
        if ((status = (0x1F & __inb(SMBHSTSTS))) != 0x00) {
            return 1;
        }
    }

    __outb(__inb(SMBHSTCNT) | SMBHSTCNT_START, SMBHSTCNT);

    // Some SMB controllers need this quirk.
    if (extra_initial_sleep_for_smb_transaction) {
        usleep(extra_initial_sleep_for_smb_transaction);
    }

    do {
        usleep(500);
        status = __inb(SMBHSTSTS);
    } while ((status & 0x01) && (timeout++ < 100));

    if (timeout >= 100) {
        return 2;
    }

    if (status & 0x1C) {
        return status;
    }

    if ((__inb(SMBHSTSTS) & 0x1F) != 0x00) {
        __outb(inb(SMBHSTSTS), SMBHSTSTS);
    }

    return 0;
}

static uint8_t nf_read_spd_byte(uint8_t smbus_adr, uint8_t spd_adr)
{
    int i;

    smbus_adr += 0x50;

    // Set Slave ADR
    __outb(smbus_adr << 1, NVSMBADD);

    // Set Command (SPD Byte to Read)
    __outb(spd_adr, NVSMBCMD);

    // Start transaction
    __outb(NVSMBCNT_BYTE_DATA | NVSMBCNT_READ, NVSMBCNT);

    // Wait until transaction complete
    for (i = 500; i > 0; i--) {
        usleep(50);
        if (__inb(NVSMBCNT) == 0) {
            break;
        }
    }

    // If timeout or Error Status, quit
    if (i == 0 || __inb(NVSMBSTS) & NVSMBSTS_STATUS) {
        return 0xFF;
    }

    return __inb(NVSMBDAT(0));
}
