mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2024-11-27 10:00:17 -06:00
1659 lines
46 KiB
C
1659 lines
46 KiB
C
// 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 "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, "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 spd_info parse_spd_rdram (uint8_t slot_idx);
|
|
static spd_info parse_spd_sdram (uint8_t slot_idx);
|
|
static spd_info parse_spd_ddr (uint8_t slot_idx);
|
|
static spd_info parse_spd_ddr2 (uint8_t slot_idx);
|
|
static spd_info parse_spd_ddr3 (uint8_t slot_idx);
|
|
static spd_info parse_spd_ddr4 (uint8_t slot_idx);
|
|
static spd_info parse_spd_ddr5 (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(void);
|
|
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);
|
|
|
|
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++) {
|
|
|
|
if (get_spd(spdidx, 0) != 0xFF) {
|
|
switch(get_spd(spdidx, 2))
|
|
{
|
|
default:
|
|
continue;
|
|
case 0x12: // DDR5
|
|
curspd = parse_spd_ddr5(spdidx);
|
|
break;
|
|
case 0x0C: // DDR4
|
|
curspd = parse_spd_ddr4(spdidx);
|
|
break;
|
|
case 0x0B: // DDR3
|
|
curspd = parse_spd_ddr3(spdidx);
|
|
break;
|
|
case 0x08: // DDR2
|
|
curspd = parse_spd_ddr2(spdidx);
|
|
break;
|
|
case 0x07: // DDR
|
|
curspd = parse_spd_ddr(spdidx);
|
|
break;
|
|
case 0x04: // SDRAM
|
|
curspd = parse_spd_sdram(spdidx);
|
|
break;
|
|
case 0x01: // RAMBUS - RDRAM
|
|
if (get_spd(spdidx, 1) == 8) {
|
|
curspd = parse_spd_rdram(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
|
|
for(i = 0; i < spdi.sku_len; i++) {
|
|
curcol = printc(LINE_SPD+lidx, i ? curcol : ++curcol, spdi.sku[i]);
|
|
}
|
|
|
|
// 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.tRCD = spdi.tRCD;
|
|
ram.tRP = spdi.tRP;
|
|
ram.tRAS = spdi.tRAS;
|
|
}
|
|
}
|
|
|
|
static spd_info parse_spd_ddr5(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
spdi.isValid = false;
|
|
spdi.type = "DDR5";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
spdi.module_size = 0;
|
|
|
|
// Compute module size for symmetric & assymetric 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);
|
|
|
|
// Add current rank to total package size
|
|
spdi.module_size += cur_rank;
|
|
|
|
// If not Asymmetrical, don't process the second rank
|
|
if ((get_spd(slot_idx, 234) >> 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;
|
|
}
|
|
|
|
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 = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// 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 = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// RAS# Precharge
|
|
tns = (uint16_t)get_spd(slot_idx, 722 + xmp_offset) << 8 |
|
|
(uint16_t)get_spd(slot_idx, 721 + xmp_offset);
|
|
spdi.tRP = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// 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 = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// 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 = (uint16_t)(tns/tCK + 0.5f);
|
|
} else {
|
|
// --------------------
|
|
// JEDEC Specifications
|
|
// --------------------
|
|
|
|
// CAS# Latency
|
|
tns = (uint16_t)get_spd(slot_idx, 31) << 8 |
|
|
(uint16_t)get_spd(slot_idx, 30);
|
|
spdi.tCL = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// RAS# to CAS# Latency
|
|
tns = (uint16_t)get_spd(slot_idx, 33) << 8 |
|
|
(uint16_t)get_spd(slot_idx, 32);
|
|
spdi.tRCD = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// RAS# Precharge
|
|
tns = (uint16_t)get_spd(slot_idx, 35) << 8 |
|
|
(uint16_t)get_spd(slot_idx, 34);
|
|
spdi.tRP = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// Row Active Time
|
|
tns = (uint16_t)get_spd(slot_idx, 37) << 8 |
|
|
(uint16_t)get_spd(slot_idx, 36);
|
|
spdi.tRAS = (uint16_t)(tns/tCK + 0.5f);
|
|
|
|
// Row Cycle Time
|
|
tns = (uint16_t)get_spd(slot_idx, 39) << 8 |
|
|
(uint16_t)get_spd(slot_idx, 38);
|
|
spdi.tRC = (uint16_t)(tns/tCK + 0.5f);
|
|
}
|
|
|
|
// Module manufacturer
|
|
spdi.jedec_code = (get_spd(slot_idx, 512) & 0x1F) << 8;
|
|
spdi.jedec_code |= get_spd(slot_idx, 513) & 0x7F;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j <= 29; j++) {
|
|
sku_byte = get_spd(slot_idx, 521+j);
|
|
|
|
if ((sku_byte <= 0x20 || sku_byte == 0xFF) && j > 0
|
|
&& (spdi.sku[j - 1] <= 0x20 || spdi.sku[j - 1] == 0xFF)) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
// Week & Date (BCD to Int)
|
|
uint8_t bcd = get_spd(slot_idx, 515);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 516);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
static spd_info parse_spd_ddr4(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
spdi.type = "DDR4";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
|
|
// 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, tckns, ramfreq;
|
|
|
|
if (get_spd(slot_idx, 384) == 0x0C && get_spd(slot_idx, 385) == 0x4A) {
|
|
// Max XMP
|
|
tckns = (uint8_t)get_spd(slot_idx, 396) * 0.125f +
|
|
(int8_t)get_spd(slot_idx, 431) * 0.001f;
|
|
|
|
ramfreq = 1.0f / tckns * 2.0f * 1000.0f;
|
|
|
|
spdi.freq = (ramfreq+50)/100;
|
|
spdi.freq *= 100;
|
|
|
|
spdi.XMP = 2;
|
|
|
|
} else {
|
|
// Max JEDEC
|
|
tckns = (uint8_t)get_spd(slot_idx, 18) * 0.125f +
|
|
(int8_t)get_spd(slot_idx, 125) * 0.001f;
|
|
|
|
ramfreq = 1.0f / tckns * 2.0f * 1000.0f;
|
|
spdi.freq = (uint16_t)ramfreq;
|
|
|
|
spdi.XMP = 0;
|
|
}
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
} 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/tckns);
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
|
|
// 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/tckns);
|
|
}
|
|
|
|
// Module manufacturer
|
|
spdi.jedec_code = ((uint16_t)(get_spd(slot_idx, 320) & 0x1F)) << 8;
|
|
spdi.jedec_code |= get_spd(slot_idx, 321) & 0x7F;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j <= 20; j++) {
|
|
sku_byte = get_spd(slot_idx, 329+j);
|
|
|
|
if ((sku_byte <= 0x20 || sku_byte == 0xFF) && j > 0
|
|
&& (spdi.sku[j - 1] <= 0x20 || spdi.sku[j - 1] == 0xFF)) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
// Week & Date (BCD to Int)
|
|
uint8_t bcd = get_spd(slot_idx, 323);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 324);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
static spd_info parse_spd_ddr3(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
spdi.type = "DDR3";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
spdi.XMP = 0;
|
|
|
|
// 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]
|
|
((get_spd(slot_idx, 7) >> 3) & 0x7) // Number of Ranks: byte7[5:3]
|
|
);
|
|
|
|
spdi.hasECC = (((get_spd(slot_idx, 8) >> 3) & 1) == 1);
|
|
|
|
uint8_t tck = get_spd(slot_idx, 12);
|
|
|
|
if (get_spd(slot_idx, 176) == 0x0C && get_spd(slot_idx, 177) == 0x4A) {
|
|
tck = get_spd(slot_idx, 186);
|
|
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;
|
|
if (spdi.XMP == 1) {
|
|
// ------------------
|
|
// XMP Specifications
|
|
// ------------------
|
|
tckns = get_spd(slot_idx, 186);
|
|
|
|
// CAS# Latency
|
|
tns = get_spd(slot_idx, 187);
|
|
spdi.tCL = (uint16_t)(tns/tckns);
|
|
|
|
// RAS# to CAS# Latency
|
|
tns = get_spd(slot_idx, 192);
|
|
spdi.tRCD = (uint16_t)(tns/tckns);
|
|
|
|
// RAS# Precharge
|
|
tns = get_spd(slot_idx, 191);
|
|
spdi.tRP = (uint16_t)(tns/tckns);
|
|
|
|
// Row Active Time
|
|
tns = (uint16_t)(get_spd(slot_idx, 194) & 0xF0) << 4 |
|
|
get_spd(slot_idx, 195);
|
|
;
|
|
spdi.tRAS = (uint16_t)(tns/tckns);
|
|
|
|
// Row Cycle Time
|
|
tns = (uint16_t)(get_spd(slot_idx, 194) & 0x0F) << 8 |
|
|
get_spd(slot_idx, 196);
|
|
spdi.tRC = (uint16_t)(tns/tckns);
|
|
} else {
|
|
// --------------------
|
|
// JEDEC Specifications
|
|
// --------------------
|
|
tckns = (uint8_t)get_spd(slot_idx, 12) * 0.125f +
|
|
(int8_t)get_spd(slot_idx, 134) * 0.001f;
|
|
|
|
// CAS# Latency
|
|
tns = (uint8_t)get_spd(slot_idx, 16) * 0.125f +
|
|
(int8_t)get_spd(slot_idx, 35) * 0.001f;
|
|
spdi.tCL = (uint16_t)(tns/tckns);
|
|
|
|
// RAS# to CAS# Latency
|
|
tns = (uint8_t)get_spd(slot_idx, 18) * 0.125f +
|
|
(int8_t)get_spd(slot_idx, 36) * 0.001f;
|
|
spdi.tRCD = (uint16_t)(tns/tckns);
|
|
|
|
// RAS# Precharge
|
|
tns = (uint8_t)get_spd(slot_idx, 20) * 0.125f +
|
|
(int8_t)get_spd(slot_idx, 37) * 0.001f;
|
|
spdi.tRP = (uint16_t)(tns/tckns);
|
|
|
|
// Row Active Time
|
|
tns = (uint8_t)get_spd(slot_idx, 22) * 0.125f +
|
|
(uint8_t)(get_spd(slot_idx, 21) & 0x0F) * 32.0f;
|
|
spdi.tRAS = (uint16_t)(tns/tckns);
|
|
|
|
// Row Cycle Time
|
|
tns = (uint8_t)get_spd(slot_idx, 23) * 0.125f +
|
|
(uint8_t)(get_spd(slot_idx, 21) >> 4) * 32.0f;
|
|
spdi.tRC = (uint16_t)(tns/tckns);
|
|
}
|
|
|
|
// Module manufacturer
|
|
spdi.jedec_code = ((uint16_t)(get_spd(slot_idx, 117) & 0x1F)) << 8;
|
|
spdi.jedec_code |= get_spd(slot_idx, 118) & 0x7F;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j <= 20; j++) {
|
|
sku_byte = get_spd(slot_idx, 128+j);
|
|
|
|
if ((sku_byte <= 0x20 || sku_byte == 0xFF) && j > 0
|
|
&& (spdi.sku[j - 1] <= 0x20 || spdi.sku[j - 1] == 0xFF)) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
uint8_t bcd = get_spd(slot_idx, 120);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 121);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
static spd_info parse_spd_ddr2(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
spdi.type = "DDR2";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
spdi.XMP = 0;
|
|
|
|
// 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;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// RAS# Precharge
|
|
tbyte = get_spd(slot_idx, 112 + epp_offset);
|
|
tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
|
|
spdi.tRP = (uint16_t)(tns/tckns);
|
|
|
|
// Row Active Time
|
|
tns = get_spd(slot_idx, 113 + epp_offset);
|
|
spdi.tRAS = (uint16_t)(tns/tckns);
|
|
|
|
// Row Cycle Time
|
|
spdi.tRC = 0;
|
|
} 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;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// RAS# to CAS# Latency
|
|
tbyte = get_spd(slot_idx, 29);
|
|
tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
|
|
spdi.tRCD = (uint16_t)(tns/tckns);
|
|
|
|
// RAS# Precharge
|
|
tbyte = get_spd(slot_idx, 27);
|
|
tns = ((tbyte & 0xFC) >> 2) + (tbyte & 0x3) * 0.25f;
|
|
spdi.tRP = (uint16_t)(tns/tckns);
|
|
|
|
// Row Active Time
|
|
tns = get_spd(slot_idx, 30);
|
|
spdi.tRAS = (uint16_t)(tns/tckns);
|
|
|
|
// Row Cycle Time
|
|
spdi.tRC = 0;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j < 18; j++) {
|
|
sku_byte = get_spd(slot_idx, 73 + j);
|
|
|
|
if ((sku_byte <= 0x20 || sku_byte == 0xFF) && j > 0
|
|
&& (spdi.sku[j - 1] <= 0x20 || spdi.sku[j - 1] == 0xFF)) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
uint8_t bcd = get_spd(slot_idx, 93);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 94);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
static spd_info parse_spd_ddr(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
spdi.type = "DDR";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
spdi.XMP = 0;
|
|
|
|
// 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 asymetric 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; // TODO: .5 CAS
|
|
break;
|
|
}
|
|
}
|
|
|
|
tns = (get_spd(slot_idx, 29) >> 2) +
|
|
(get_spd(slot_idx, 29) & 0x3) * 0.25f;
|
|
spdi.tRCD = (uint16_t)(tns/tckns);
|
|
|
|
tns = (get_spd(slot_idx, 27) >> 2) +
|
|
(get_spd(slot_idx, 27) & 0x3) * 0.25f;
|
|
spdi.tRP = (uint16_t)(tns/tckns);
|
|
|
|
spdi.tRAS = (uint16_t)(get_spd(slot_idx, 30)/tckns);
|
|
spdi.tRC = 0;
|
|
|
|
// 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;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j < 18; j++) {
|
|
sku_byte = get_spd(slot_idx, 73 + j);
|
|
|
|
if ((sku_byte <= 0x20 || sku_byte == 0xFF) && j > 0
|
|
&& (spdi.sku[j - 1] <= 0x20 || spdi.sku[j - 1] == 0xFF)) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
uint8_t bcd = get_spd(slot_idx, 93);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 94);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
static spd_info parse_spd_rdram(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
spdi.isValid = false;
|
|
spdi.type = "RDRAM";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
spdi.XMP = 0;
|
|
|
|
// 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;
|
|
}
|
|
|
|
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 spdi;
|
|
}
|
|
|
|
// 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);
|
|
spdi.tRC = 0;
|
|
|
|
// 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;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j < 18; j++) {
|
|
sku_byte = get_spd(slot_idx, 73 + j);
|
|
|
|
if ((sku_byte <= 0x20 || sku_byte == 0xFF) && j > 0
|
|
&& (spdi.sku[j - 1] <= 0x20 || spdi.sku[j - 1] == 0xFF)) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
uint8_t bcd = get_spd(slot_idx, 93);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 94);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
static spd_info parse_spd_sdram(uint8_t slot_idx)
|
|
{
|
|
spd_info spdi;
|
|
|
|
uint8_t bcd;
|
|
|
|
spdi.type = "SDRAM";
|
|
spdi.slot_num = slot_idx;
|
|
spdi.sku_len = 0;
|
|
spdi.XMP = 0;
|
|
|
|
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;
|
|
break;
|
|
}
|
|
}
|
|
|
|
tns = get_spd(slot_idx, 29);
|
|
spdi.tRCD = (uint16_t)(tns/tckns);
|
|
|
|
tns = get_spd(slot_idx, 27);
|
|
spdi.tRP = (uint16_t)(tns/tckns);
|
|
|
|
spdi.tRAS = (uint16_t)(get_spd(slot_idx, 30)/tckns);
|
|
spdi.tRC = 0;
|
|
|
|
// 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;
|
|
|
|
// Module SKU
|
|
uint8_t sku_byte;
|
|
for (int j = 0; j < 18; j++) {
|
|
sku_byte = get_spd(slot_idx, 73 + j);
|
|
|
|
if (sku_byte <= 0x20 && j > 0 && spdi.sku[j - 1] <= 0x20) {
|
|
spdi.sku_len--;
|
|
break;
|
|
} else {
|
|
spdi.sku[j] = sku_byte;
|
|
spdi.sku_len++;
|
|
}
|
|
}
|
|
|
|
bcd = get_spd(slot_idx, 93);
|
|
spdi.fab_year = bcd - 6 * (bcd >> 4);
|
|
|
|
bcd = get_spd(slot_idx, 94);
|
|
spdi.fab_week = bcd - 6 * (bcd >> 4);
|
|
|
|
spdi.isValid = true;
|
|
|
|
return spdi;
|
|
}
|
|
|
|
|
|
// --------------------------
|
|
// 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 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();
|
|
}
|
|
// 0x719B 82440/82443MX PMC ?
|
|
// 0x0F13 ValleyView SMBus Controller ?
|
|
// 0x8119 US15W ?
|
|
return false;
|
|
}
|
|
|
|
case VID_HYGON:
|
|
case 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 VID_ATI:
|
|
switch(did)
|
|
{
|
|
// case 0x4353: // SB200
|
|
// case 0x4363: // SB300
|
|
// case 0x4372: // SB400
|
|
case 0x4385: // SB600+
|
|
return amd_sb_get_smb();
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 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 790i Ultra
|
|
return nv_mcp_get_smb();
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VID_SIS:
|
|
switch(did)
|
|
{
|
|
case 0x0016: // SiS961/2/3
|
|
// There are also SiS630 and SiS5595 SMBus controllers.
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 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.
|
|
// case 0x3051: // 82C596B_3
|
|
// case 0x3057: // 82C686_4
|
|
// case 0x8235: // 8231_4
|
|
// SMB base address = 0x90
|
|
// viapro SMBus controller.
|
|
// 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.
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 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();
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ----------------------
|
|
// PIIX4 SMBUS Controller
|
|
// ----------------------
|
|
|
|
static bool piix4_get_smb(void)
|
|
{
|
|
uint16_t x = pci_config_read16(0, smbdev, smbfun, 0x90) & 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();
|
|
} 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 Cezanne (get smb address in memory)
|
|
if (imc_type == IMC_K19_CZN && 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 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 transction 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));
|
|
}
|