nb/intel/sandybridge/raminit: Add XMP support
Some vendors store lower frequency profiles in the regular SPD, if the SPD contains a XMP profile. To make use of the board's and DIMM's maximum supported DRAM frequency, try to parse the XMP profile and use it instead. Validate the XMP profile to make sure that the installed DIMM count per channel is supported and the requested voltage is supported. To reduce complexity only XMP Profile 1 is read. Allows my DRAM to run at 800Mhz instead of 666Mhz as encoded in the default SPD. Test system: * Gigabyte GA-B75M-D3H * Intel Pentium CPU G2130 Change-Id: Ib4dd68debfdcfdce138e813ad5b0e8e2ce3a40b2 Signed-off-by: Patrick Rudolph <siro@das-labor.org> Reviewed-on: https://review.coreboot.org/13486 Reviewed-by: Martin Roth <martinroth@google.com> Tested-by: build bot (Jenkins)
This commit is contained in:
parent
c3b0b72813
commit
bd1fdc6e84
|
@ -116,6 +116,8 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
|
||||||
|
|
||||||
/* Don't assume we memset 0 dimm struct. Clear all our flags */
|
/* Don't assume we memset 0 dimm struct. Clear all our flags */
|
||||||
dimm->flags.raw = 0;
|
dimm->flags.raw = 0;
|
||||||
|
dimm->dimms_per_channel = 3;
|
||||||
|
|
||||||
/* Make sure that the SPD dump is indeed from a DDR3 module */
|
/* Make sure that the SPD dump is indeed from a DDR3 module */
|
||||||
if (spd[2] != SPD_MEMORY_TYPE_SDRAM_DDR3) {
|
if (spd[2] != SPD_MEMORY_TYPE_SDRAM_DDR3) {
|
||||||
printram("Not a DDR3 SPD!\n");
|
printram("Not a DDR3 SPD!\n");
|
||||||
|
@ -180,14 +182,17 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
|
||||||
printram(" Supported voltages:");
|
printram(" Supported voltages:");
|
||||||
if (reg8 & (1 << 2)) {
|
if (reg8 & (1 << 2)) {
|
||||||
dimm->flags.operable_1_25V = 1;
|
dimm->flags.operable_1_25V = 1;
|
||||||
|
dimm->voltage = 1250;
|
||||||
printram(" 1.25V");
|
printram(" 1.25V");
|
||||||
}
|
}
|
||||||
if (reg8 & (1 << 1)) {
|
if (reg8 & (1 << 1)) {
|
||||||
dimm->flags.operable_1_35V = 1;
|
dimm->flags.operable_1_35V = 1;
|
||||||
|
dimm->voltage = 1300;
|
||||||
printram(" 1.35V");
|
printram(" 1.35V");
|
||||||
}
|
}
|
||||||
if (!(reg8 & (1 << 0))) {
|
if (!(reg8 & (1 << 0))) {
|
||||||
dimm->flags.operable_1_50V = 1;
|
dimm->flags.operable_1_50V = 1;
|
||||||
|
dimm->voltage = 1500;
|
||||||
printram(" 1.5V");
|
printram(" 1.5V");
|
||||||
}
|
}
|
||||||
printram("\n");
|
printram("\n");
|
||||||
|
@ -338,6 +343,118 @@ int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Decode the raw SPD XMP data
|
||||||
|
*
|
||||||
|
* Decodes a raw SPD XMP data from a DDR3 DIMM, and organizes it into a
|
||||||
|
* @ref dimm_attr structure. The SPD data must first be read in a contiguous
|
||||||
|
* array, and passed to this function.
|
||||||
|
*
|
||||||
|
* @param dimm pointer to @ref dimm_attr structure where the decoded data is to
|
||||||
|
* be stored
|
||||||
|
* @param spd array of raw data previously read from the SPD.
|
||||||
|
*
|
||||||
|
* @param profile select one of the profiles to load
|
||||||
|
*
|
||||||
|
* @return @ref spd_status enumerator
|
||||||
|
* SPD_STATUS_OK -- decoding was successful
|
||||||
|
* SPD_STATUS_INVALID -- invalid SPD or not a DDR3 SPD
|
||||||
|
* SPD_STATUS_CRC_ERROR -- CRC did not verify
|
||||||
|
* SPD_STATUS_INVALID_FIELD -- A field with an invalid value was
|
||||||
|
* detected.
|
||||||
|
*/
|
||||||
|
int spd_xmp_decode_ddr3(dimm_attr *dimm,
|
||||||
|
spd_raw_data spd,
|
||||||
|
enum ddr3_xmp_profile profile)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u32 mtb; /* medium time base */
|
||||||
|
u8 *xmp; /* pointer to XMP profile data */
|
||||||
|
|
||||||
|
/* need a valid SPD */
|
||||||
|
ret = spd_decode_ddr3(dimm, spd);
|
||||||
|
if (ret != SPD_STATUS_OK)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* search for magic header */
|
||||||
|
if (spd[176] != 0x0C || spd[177] != 0x4A) {
|
||||||
|
printram("Not a DDR3 XMP profile!\n");
|
||||||
|
dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED;
|
||||||
|
return SPD_STATUS_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile == DDR3_XMP_PROFILE_1) {
|
||||||
|
if (!(spd[178] & 1)) {
|
||||||
|
printram("Selected XMP profile disabled!\n");
|
||||||
|
dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED;
|
||||||
|
return SPD_STATUS_INVALID;
|
||||||
|
}
|
||||||
|
printram(" XMP Profile 1\n");
|
||||||
|
xmp = &spd[185];
|
||||||
|
|
||||||
|
/* Medium Timebase =
|
||||||
|
* Medium Timebase (MTB) Dividend /
|
||||||
|
* Medium Timebase (MTB) Divisor */
|
||||||
|
mtb = (((u32) spd[180]) << 8) / spd[181];
|
||||||
|
|
||||||
|
dimm->dimms_per_channel = ((spd[178] >> 2) & 0x3) + 1;
|
||||||
|
} else {
|
||||||
|
if (!(spd[178] & 2)) {
|
||||||
|
printram("Selected XMP profile disabled!\n");
|
||||||
|
dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED;
|
||||||
|
return SPD_STATUS_INVALID;
|
||||||
|
}
|
||||||
|
printram(" XMP Profile 2\n");
|
||||||
|
xmp = &spd[220];
|
||||||
|
|
||||||
|
/* Medium Timebase =
|
||||||
|
* Medium Timebase (MTB) Dividend /
|
||||||
|
* Medium Timebase (MTB) Divisor */
|
||||||
|
mtb = (((u32) spd[182]) << 8) / spd[183];
|
||||||
|
|
||||||
|
dimm->dimms_per_channel = ((spd[178] >> 4) & 0x3) + 1;
|
||||||
|
}
|
||||||
|
printram(" Max DIMMs per channel: %u\n",
|
||||||
|
dimm->dimms_per_channel);
|
||||||
|
|
||||||
|
printram(" XMP Revision: %u.%u\n", spd[179] >> 4, spd[179] & 0xf);
|
||||||
|
|
||||||
|
/* calculate voltage in mV */
|
||||||
|
dimm->voltage = (xmp[0] & 1) * 50;
|
||||||
|
dimm->voltage += ((xmp[0] >> 1) & 0xf) * 100;
|
||||||
|
dimm->voltage += ((xmp[0] >> 5) & 0x3) * 1000;
|
||||||
|
printram(" Requested voltage: %u mV\n", dimm->voltage);
|
||||||
|
|
||||||
|
/* SDRAM Minimum Cycle Time (tCKmin) */
|
||||||
|
dimm->tCK = xmp[1] * mtb;
|
||||||
|
/* CAS Latencies Supported */
|
||||||
|
dimm->cas_supported = (xmp[9] << 8) + xmp[8];
|
||||||
|
/* Minimum CAS Latency Time (tAAmin) */
|
||||||
|
dimm->tAA = xmp[2] * mtb;
|
||||||
|
/* Minimum Write Recovery Time (tWRmin) */
|
||||||
|
dimm->tWR = xmp[8] * mtb;
|
||||||
|
/* Minimum RAS# to CAS# Delay Time (tRCDmin) */
|
||||||
|
dimm->tRCD = xmp[7] * mtb;
|
||||||
|
/* Minimum Row Active to Row Active Delay Time (tRRDmin) */
|
||||||
|
dimm->tRRD = xmp[17] * mtb;
|
||||||
|
/* Minimum Row Precharge Delay Time (tRPmin) */
|
||||||
|
dimm->tRP = xmp[6] * mtb;
|
||||||
|
/* Minimum Active to Precharge Delay Time (tRASmin) */
|
||||||
|
dimm->tRAS = (((xmp[9] & 0x0f) << 8) + xmp[10]) * mtb;
|
||||||
|
/* Minimum Active to Active/Refresh Delay Time (tRCmin) */
|
||||||
|
dimm->tRC = (((xmp[9] & 0xf0) << 4) + xmp[11]) * mtb;
|
||||||
|
/* Minimum Refresh Recovery Delay Time (tRFCmin) */
|
||||||
|
dimm->tRFC = ((xmp[15] << 8) + xmp[14]) * mtb;
|
||||||
|
/* Minimum Internal Write to Read Command Delay Time (tWTRmin) */
|
||||||
|
dimm->tWTR = xmp[20] * mtb;
|
||||||
|
/* Minimum Internal Read to Precharge Command Delay Time (tRTPmin) */
|
||||||
|
dimm->tRTP = xmp[16] * mtb;
|
||||||
|
/* Minimum Four Activate Window Delay Time (tFAWmin) */
|
||||||
|
dimm->tFAW = (((xmp[18] & 0x0f) << 8) + xmp[19]) * mtb;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The information printed below has a more informational character, and is not
|
* The information printed below has a more informational character, and is not
|
||||||
* necessarily tied in to RAM init debugging. Hence, we stop using printram(),
|
* necessarily tied in to RAM init debugging. Hence, we stop using printram(),
|
||||||
|
|
|
@ -161,6 +161,10 @@ typedef struct dimm_attr_st {
|
||||||
u32 tFAW;
|
u32 tFAW;
|
||||||
|
|
||||||
u8 reference_card;
|
u8 reference_card;
|
||||||
|
/* XMP: Module voltage in mV */
|
||||||
|
u16 voltage;
|
||||||
|
/* XMP: max DIMMs per channel supported (1-4) */
|
||||||
|
u8 dimms_per_channel;
|
||||||
} dimm_attr;
|
} dimm_attr;
|
||||||
|
|
||||||
/** Result of the SPD decoding process */
|
/** Result of the SPD decoding process */
|
||||||
|
@ -171,12 +175,20 @@ enum spd_status {
|
||||||
SPD_STATUS_INVALID_FIELD,
|
SPD_STATUS_INVALID_FIELD,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ddr3_xmp_profile {
|
||||||
|
DDR3_XMP_PROFILE_1 = 0,
|
||||||
|
DDR3_XMP_PROFILE_2 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
typedef u8 spd_raw_data[256];
|
typedef u8 spd_raw_data[256];
|
||||||
|
|
||||||
u16 spd_ddr3_calc_crc(u8 *spd, int len);
|
u16 spd_ddr3_calc_crc(u8 *spd, int len);
|
||||||
int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd_data);
|
int spd_decode_ddr3(dimm_attr * dimm, spd_raw_data spd_data);
|
||||||
int dimm_is_registered(enum spd_dimm_type type);
|
int dimm_is_registered(enum spd_dimm_type type);
|
||||||
void dram_print_spd_ddr3(const dimm_attr * dimm);
|
void dram_print_spd_ddr3(const dimm_attr * dimm);
|
||||||
|
int spd_xmp_decode_ddr3(dimm_attr * dimm,
|
||||||
|
spd_raw_data spd,
|
||||||
|
enum ddr3_xmp_profile profile);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Read double word from specified address
|
* \brief Read double word from specified address
|
||||||
|
|
|
@ -284,7 +284,7 @@ void read_spd(spd_raw_data * spd, u8 addr)
|
||||||
static void dram_find_spds_ddr3(spd_raw_data * spd, dimm_info * dimm,
|
static void dram_find_spds_ddr3(spd_raw_data * spd, dimm_info * dimm,
|
||||||
ramctr_timing * ctrl)
|
ramctr_timing * ctrl)
|
||||||
{
|
{
|
||||||
int dimms = 0;
|
int dimms = 0, dimms_on_channel;
|
||||||
int channel, slot, spd_slot;
|
int channel, slot, spd_slot;
|
||||||
|
|
||||||
memset (ctrl->rankmap, 0, sizeof (ctrl->rankmap));
|
memset (ctrl->rankmap, 0, sizeof (ctrl->rankmap));
|
||||||
|
@ -295,9 +295,37 @@ static void dram_find_spds_ddr3(spd_raw_data * spd, dimm_info * dimm,
|
||||||
FOR_ALL_CHANNELS {
|
FOR_ALL_CHANNELS {
|
||||||
ctrl->channel_size_mb[channel] = 0;
|
ctrl->channel_size_mb[channel] = 0;
|
||||||
|
|
||||||
|
dimms_on_channel = 0;
|
||||||
|
/* count dimms on channel */
|
||||||
for (slot = 0; slot < NUM_SLOTS; slot++) {
|
for (slot = 0; slot < NUM_SLOTS; slot++) {
|
||||||
spd_slot = 2 * channel + slot;
|
spd_slot = 2 * channel + slot;
|
||||||
spd_decode_ddr3(&dimm->dimm[channel][slot], spd[spd_slot]);
|
spd_decode_ddr3(&dimm->dimm[channel][slot], spd[spd_slot]);
|
||||||
|
if (dimm->dimm[channel][slot].dram_type == SPD_MEMORY_TYPE_SDRAM_DDR3)
|
||||||
|
dimms_on_channel++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (slot = 0; slot < NUM_SLOTS; slot++) {
|
||||||
|
spd_slot = 2 * channel + slot;
|
||||||
|
/* search for XMP profile */
|
||||||
|
spd_xmp_decode_ddr3(&dimm->dimm[channel][slot],
|
||||||
|
spd[spd_slot],
|
||||||
|
DDR3_XMP_PROFILE_1);
|
||||||
|
|
||||||
|
if (dimm->dimm[channel][slot].dram_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
|
||||||
|
printram("No valid XMP profile found.\n");
|
||||||
|
spd_decode_ddr3(&dimm->dimm[channel][slot], spd[spd_slot]);
|
||||||
|
} else if (dimms_on_channel > dimm->dimm[channel][slot].dimms_per_channel) {
|
||||||
|
printram("XMP profile supports %u DIMMs, but %u DIMMs are installed.\n",
|
||||||
|
dimm->dimm[channel][slot].dimms_per_channel,
|
||||||
|
dimms_on_channel);
|
||||||
|
spd_decode_ddr3(&dimm->dimm[channel][slot], spd[spd_slot]);
|
||||||
|
} else if (dimm->dimm[channel][slot].voltage != 1500) {
|
||||||
|
/* TODO: support other DDR3 voltage than 1500mV */
|
||||||
|
printram("XMP profile's requested %u mV is unsupported.\n",
|
||||||
|
dimm->dimm[channel][slot].voltage);
|
||||||
|
spd_decode_ddr3(&dimm->dimm[channel][slot], spd[spd_slot]);
|
||||||
|
}
|
||||||
|
|
||||||
if (dimm->dimm[channel][slot].dram_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
|
if (dimm->dimm[channel][slot].dram_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
|
||||||
// set dimm invalid
|
// set dimm invalid
|
||||||
dimm->dimm[channel][slot].ranks = 0;
|
dimm->dimm[channel][slot].ranks = 0;
|
||||||
|
|
Loading…
Reference in New Issue