mirror of
https://github.com/memtest86plus/memtest86plus
synced 2024-11-23 13:19:53 +03:00
Split SPD parsing and printing code from smbus.c to spd.c (#426)
* Split SPD parsing and printing code from smbus.c to spd.c Move all SPD parsing and printing code from smbus.{c,h} to spd.{c,h}. Introduce parse_spd() function, moving the parse_spd_* selection logic from print_smbus_startup_info(), allowing to keep parse_spd_* static. Remove static from get_spd() and update print_smbus_startup_info() to use parse_spd() which also simplifies the code flow. Move LINE_SPD into display.h and rename it to ROW_SPD. Update print_spdi() to use explicit row number where the SPD info needs to be printed. Rename ram_info into ram_info_t, rename print_smbus_startup_info() into print_spd_startup_info. Do not initialize ram.freq to 0, this is the initial value already. Do not set curspd.isValid to False, the first thing that parse_spd() does is setting the entire struct to 0, that also sets isValid to False. print_spd_startup_info() from smbus.c is technically a skeleton now so each arch can have its own version, adjusted as needed. Once LA64 changes land, we can think how we can even make it arch agnostic. * Add -fexcess-precision=standard to CFLAGS for build(32,64)/Makefile Recent switch from -std=c11 to -std=gnu11 done in53ca89f
("Add initial NUMA awareness support") introduced a regression in SPD parsing code (and potentially in other places) due to change of floating point precision. Restore the original behavior by adding -fexcess-precision=standard to CFLAGS. Bug: https://github.com/memtest86plus/memtest86plus/issues/425 Fixes:53ca89f8ae
This commit is contained in:
parent
e99ce97648
commit
771d6d4dca
@ -15,6 +15,7 @@
|
||||
#include "pmem.h"
|
||||
#include "smbios.h"
|
||||
#include "smbus.h"
|
||||
#include "spd.h"
|
||||
#include "temperature.h"
|
||||
#include "tsc.h"
|
||||
|
||||
@ -265,7 +266,7 @@ void display_cpu_topology(void)
|
||||
void post_display_init(void)
|
||||
{
|
||||
print_smbios_startup_info();
|
||||
print_smbus_startup_info();
|
||||
print_spd_startup_info();
|
||||
|
||||
if (imc.freq) {
|
||||
// Try to get RAM information from IMC
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#define ROW_SPD 13
|
||||
|
||||
#define ROW_MESSAGE_T 10
|
||||
#define ROW_MESSAGE_B (SCREEN_HEIGHT - 2)
|
||||
|
||||
|
@ -11,7 +11,8 @@ else
|
||||
endif
|
||||
|
||||
CFLAGS = -std=gnu11 -Wall -Wextra -Wshadow -m32 -march=i586 -fpic -fno-builtin \
|
||||
-ffreestanding -fomit-frame-pointer -fno-stack-protector -DARCH_BITS=32
|
||||
-ffreestanding -fomit-frame-pointer -fno-stack-protector \
|
||||
-fexcess-precision=standard -DARCH_BITS=32
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
CFLAGS+=-ggdb3 -DDEBUG_GDB
|
||||
@ -45,6 +46,7 @@ SYS_OBJS = system/acpi.o \
|
||||
system/serial.o \
|
||||
system/smbios.o \
|
||||
system/smbus.o \
|
||||
system/spd.o \
|
||||
system/smp.o \
|
||||
system/temperature.o \
|
||||
system/timers.o \
|
||||
|
@ -12,7 +12,7 @@ endif
|
||||
|
||||
CFLAGS = -std=gnu11 -Wall -Wextra -Wshadow -m64 -march=x86-64 -mno-mmx -mno-sse -mno-sse2 \
|
||||
-fpic -fno-builtin -ffreestanding -fomit-frame-pointer -fno-stack-protector \
|
||||
-DARCH_BITS=64
|
||||
-fexcess-precision=standard -DARCH_BITS=64
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
CFLAGS+=-ggdb3 -DDEBUG_GDB
|
||||
@ -46,6 +46,7 @@ SYS_OBJS = system/acpi.o \
|
||||
system/serial.o \
|
||||
system/smbios.o \
|
||||
system/smbus.o \
|
||||
system/spd.o \
|
||||
system/smp.o \
|
||||
system/temperature.o \
|
||||
system/timers.o \
|
||||
|
994
system/smbus.c
994
system/smbus.c
File diff suppressed because it is too large
Load Diff
@ -96,17 +96,6 @@
|
||||
#define ALI_OLD_SMBHSTDAT0 smbusbase + 4
|
||||
#define ALI_OLD_SMBHSTCMD smbusbase + 7
|
||||
|
||||
/** Rounding factors for timing computation
|
||||
*
|
||||
* These factors are used as a configurable CEIL() function
|
||||
* to get the upper int from a float past a specific decimal point.
|
||||
*/
|
||||
|
||||
#define DDR5_ROUNDING_FACTOR 30
|
||||
#define ROUNDING_FACTOR 0.9f
|
||||
|
||||
#define SPD_SKU_LEN 32
|
||||
|
||||
#define PIIX4_SMB_BASE_ADR_DEFAULT 0x90
|
||||
#define PIIX4_SMB_BASE_ADR_VIAPRO 0xD0
|
||||
#define PIIX4_SMB_BASE_ADR_ALI1563 0x80
|
||||
@ -118,42 +107,12 @@ struct pci_smbus_controller {
|
||||
void (*get_adr)(void);
|
||||
};
|
||||
|
||||
typedef struct spd_infos {
|
||||
bool isValid;
|
||||
uint8_t slot_num;
|
||||
uint16_t jedec_code;
|
||||
uint32_t module_size;
|
||||
char *type;
|
||||
char sku[SPD_SKU_LEN + 1];
|
||||
uint8_t XMP;
|
||||
uint16_t freq;
|
||||
bool hasECC;
|
||||
uint8_t fab_year;
|
||||
uint8_t fab_week;
|
||||
uint16_t tCL;
|
||||
uint8_t tCL_dec;
|
||||
uint16_t tRCD;
|
||||
uint16_t tRP;
|
||||
uint16_t tRAS;
|
||||
uint16_t tRC;
|
||||
} spd_info;
|
||||
|
||||
typedef struct ram_infos {
|
||||
uint16_t freq;
|
||||
uint16_t tCL;
|
||||
uint8_t tCL_dec;
|
||||
uint16_t tRCD;
|
||||
uint16_t tRP;
|
||||
uint16_t tRAS;
|
||||
char *type;
|
||||
} ram_info;
|
||||
|
||||
extern ram_info ram;
|
||||
|
||||
/**
|
||||
* Print SMBUS Info
|
||||
* Print SPD Info
|
||||
*/
|
||||
|
||||
void print_smbus_startup_info(void);
|
||||
void print_spd_startup_info(void);
|
||||
|
||||
uint8_t get_spd(uint8_t slot_idx, uint16_t spd_adr);
|
||||
|
||||
#endif // SMBUS_H
|
||||
|
977
system/spd.c
Normal file
977
system/spd.c
Normal file
@ -0,0 +1,977 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (C) 2004-2023 Sam Demeulemeester
|
||||
|
||||
#include "stdbool.h"
|
||||
#include "stdint.h"
|
||||
#include "string.h"
|
||||
|
||||
#include "smbus.h"
|
||||
#include "spd.h"
|
||||
#include "jedec_id.h"
|
||||
#include "print.h"
|
||||
|
||||
/** Rounding factors for timing computation
|
||||
*
|
||||
* These factors are used as a configurable CEIL() function
|
||||
* to get the upper int from a float past a specific decimal point.
|
||||
*/
|
||||
|
||||
#define DDR5_ROUNDING_FACTOR 30
|
||||
#define ROUNDING_FACTOR 0.9f
|
||||
|
||||
ram_info_t ram = { 0, 0, 0, 0, 0, 0, "N/A"};
|
||||
|
||||
static inline uint8_t bcd_to_ui8(uint8_t bcd)
|
||||
{
|
||||
return bcd - 6 * (bcd >> 4);
|
||||
}
|
||||
|
||||
void print_spdi(spd_info spdi, uint8_t row)
|
||||
{
|
||||
uint8_t curcol;
|
||||
uint16_t i;
|
||||
|
||||
// Print Slot Index, Module Size, type & Max frequency (Jedec or XMP)
|
||||
curcol = printf(row, 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(row, ++curcol, "ECC");
|
||||
}
|
||||
|
||||
// Print XMP/EPP Status
|
||||
if (spdi.XMP > 0 && spdi.XMP < 20) {
|
||||
curcol = prints(row, ++curcol, "XMP");
|
||||
} else if (spdi.XMP == 20) {
|
||||
curcol = prints(row, ++curcol, "EPP");
|
||||
}
|
||||
|
||||
// Print Manufacturer from JEDEC106
|
||||
for (i = 0; i < JEP106_CNT; i++) {
|
||||
if (spdi.jedec_code == jep106[i].jedec_code) {
|
||||
curcol = printf(row, ++curcol, "- %s", jep106[i].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not present in JEDEC106, display raw JEDEC ID
|
||||
if (spdi.jedec_code == 0) {
|
||||
curcol = prints(row, ++curcol, "- Noname");
|
||||
} else if (i == JEP106_CNT) {
|
||||
curcol = printf(row, ++curcol, "- Unknown (0x%x)", spdi.jedec_code);
|
||||
}
|
||||
|
||||
// Print SKU
|
||||
if (*spdi.sku)
|
||||
curcol = prints(row, 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(row, ++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;
|
||||
}
|
||||
|
||||
void parse_spd(spd_info *spdi, uint8_t slot_idx)
|
||||
{
|
||||
memset(spdi, 0, sizeof(*spdi)); // Also sets isValid to False
|
||||
spdi->slot_num = slot_idx;
|
||||
|
||||
if (get_spd(slot_idx, 0) == 0xFF)
|
||||
return;
|
||||
|
||||
switch(get_spd(slot_idx, 2))
|
||||
{
|
||||
case 0x12: // DDR5
|
||||
parse_spd_ddr5(spdi, slot_idx);
|
||||
break;
|
||||
case 0x0C: // DDR4
|
||||
parse_spd_ddr4(spdi, slot_idx);
|
||||
break;
|
||||
case 0x0B: // DDR3
|
||||
parse_spd_ddr3(spdi, slot_idx);
|
||||
break;
|
||||
case 0x08: // DDR2
|
||||
parse_spd_ddr2(spdi, slot_idx);
|
||||
break;
|
||||
case 0x07: // DDR
|
||||
parse_spd_ddr(spdi, slot_idx);
|
||||
break;
|
||||
case 0x04: // SDRAM
|
||||
parse_spd_sdram(spdi, slot_idx);
|
||||
break;
|
||||
case 0x01: // RAMBUS - RDRAM
|
||||
if (get_spd(slot_idx, 1) == 8) {
|
||||
parse_spd_rdram(spdi, slot_idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
50
system/spd.h
Normal file
50
system/spd.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef _SPD_H_
|
||||
#define _SPD_H_
|
||||
/**
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* \file
|
||||
*
|
||||
* Provides access to SPD parsing and printing functions.
|
||||
*
|
||||
* Copyright (C) 2004-2023 Sam Demeulemeester.
|
||||
*/
|
||||
|
||||
#define SPD_SKU_LEN 32
|
||||
|
||||
typedef struct spd_infos {
|
||||
bool isValid;
|
||||
uint8_t slot_num;
|
||||
uint16_t jedec_code;
|
||||
uint32_t module_size;
|
||||
char *type;
|
||||
char sku[SPD_SKU_LEN + 1];
|
||||
uint8_t XMP;
|
||||
uint16_t freq;
|
||||
bool hasECC;
|
||||
uint8_t fab_year;
|
||||
uint8_t fab_week;
|
||||
uint16_t tCL;
|
||||
uint8_t tCL_dec;
|
||||
uint16_t tRCD;
|
||||
uint16_t tRP;
|
||||
uint16_t tRAS;
|
||||
uint16_t tRC;
|
||||
} spd_info;
|
||||
|
||||
typedef struct ram_infos {
|
||||
uint16_t freq;
|
||||
uint16_t tCL;
|
||||
uint8_t tCL_dec;
|
||||
uint16_t tRCD;
|
||||
uint16_t tRP;
|
||||
uint16_t tRAS;
|
||||
char *type;
|
||||
} ram_info_t;
|
||||
|
||||
extern ram_info_t ram;
|
||||
|
||||
void print_spdi(spd_info spdi, uint8_t lidx);
|
||||
void parse_spd(spd_info *spdi, uint8_t slot_idx);
|
||||
|
||||
#endif // SPD_H
|
Loading…
Reference in New Issue
Block a user