diff options
Diffstat (limited to 'src/northbridge')
4 files changed, 265 insertions, 0 deletions
diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.inc b/src/northbridge/intel/haswell/native_raminit/Makefile.inc index 90af951c5a..ebf7abc6ec 100644 --- a/src/northbridge/intel/haswell/native_raminit/Makefile.inc +++ b/src/northbridge/intel/haswell/native_raminit/Makefile.inc @@ -2,3 +2,4 @@ romstage-y += raminit_main.c romstage-y += raminit_native.c +romstage-y += spd_bitmunching.c diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c index 9b42c25b40..b06330c4eb 100644 --- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c +++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c @@ -20,6 +20,7 @@ struct task_entry { }; static const struct task_entry cold_boot[] = { + { collect_spd_info, true, "PROCSPD", }, }; /* Return a generic stepping value to make stepping checks simpler */ diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h index 885f0184f4..1a0793947e 100644 --- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h +++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h @@ -3,6 +3,15 @@ #ifndef HASWELL_RAMINIT_NATIVE_H #define HASWELL_RAMINIT_NATIVE_H +#include <device/dram/ddr3.h> +#include <northbridge/intel/haswell/haswell.h> + +#define SPD_LEN 256 + +/* 8 data lanes + 1 ECC lane */ +#define NUM_LANES 9 +#define NUM_LANES_NO_ECC 8 + enum raminit_boot_mode { BOOTMODE_COLD, BOOTMODE_WARM, @@ -12,6 +21,8 @@ enum raminit_boot_mode { enum raminit_status { RAMINIT_STATUS_SUCCESS = 0, + RAMINIT_STATUS_NO_MEMORY_INSTALLED, + RAMINIT_STATUS_UNSUPPORTED_MEMORY, RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/ }; @@ -21,14 +32,60 @@ enum generic_stepping { STEPPING_C0 = 3, }; +struct raminit_dimm_info { + spd_raw_data raw_spd; + struct dimm_attr_ddr3_st data; + uint8_t spd_addr; + bool valid; +}; + struct sysinfo { enum raminit_boot_mode bootmode; enum generic_stepping stepping; uint32_t cpu; /* CPUID value */ bool dq_pins_interleaved; + + /** TODO: ECC support untested **/ + bool is_ecc; + + /** + * FIXME: LPDDR support is incomplete. The largest chunks are missing, + * but some LPDDR-specific variations in algorithms have been handled. + * LPDDR-specific functions have stubs which will halt upon execution. + */ + bool lpddr; + + struct raminit_dimm_info dimms[NUM_CHANNELS][NUM_SLOTS]; + union dimm_flags_ddr3_st flags; + uint16_t cas_supported; + + /* Except for tCK, everything is eventually stored in DCLKs */ + uint32_t tCK; + uint32_t tAA; /* Also known as tCL */ + uint32_t tWR; + uint32_t tRCD; + uint32_t tRRD; + uint32_t tRP; + uint32_t tRAS; + uint32_t tRC; + uint32_t tRFC; + uint32_t tWTR; + uint32_t tRTP; + uint32_t tFAW; + uint32_t tCWL; + uint32_t tCMD; + + uint8_t lanes; /* 8 or 9 */ + uint8_t chanmap; + uint8_t dpc[NUM_CHANNELS]; /* DIMMs per channel */ + uint8_t rankmap[NUM_CHANNELS]; + uint8_t rank_mirrored[NUM_CHANNELS]; + uint32_t channel_size_mb[NUM_CHANNELS]; }; void raminit_main(enum raminit_boot_mode bootmode); +enum raminit_status collect_spd_info(struct sysinfo *ctrl); + #endif diff --git a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c new file mode 100644 index 0000000000..2dab8504c4 --- /dev/null +++ b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <cbfs.h> +#include <commonlib/bsd/clamp.h> +#include <console/console.h> +#include <device/dram/ddr3.h> +#include <device/smbus_host.h> +#include <northbridge/intel/haswell/haswell.h> +#include <northbridge/intel/haswell/raminit.h> +#include <string.h> +#include <types.h> + +#include "raminit_native.h" + +static const uint8_t *get_spd_data_from_cbfs(struct spd_info *spdi) +{ + if (!CONFIG(HAVE_SPD_IN_CBFS)) + return NULL; + + printk(RAM_DEBUG, "SPD index %u\n", spdi->spd_index); + + size_t spd_file_len; + uint8_t *spd_file = cbfs_map("spd.bin", &spd_file_len); + + if (!spd_file) { + printk(BIOS_ERR, "SPD data not found in CBFS\n"); + return NULL; + } + + if (spd_file_len < ((spdi->spd_index + 1) * SPD_LEN)) { + printk(BIOS_ERR, "SPD index override to 0 - old hardware?\n"); + spdi->spd_index = 0; + } + + if (spd_file_len < SPD_LEN) { + printk(BIOS_ERR, "Invalid SPD data in CBFS\n"); + return NULL; + } + + return spd_file + (spdi->spd_index * SPD_LEN); +} + +static void get_spd_for_dimm(struct raminit_dimm_info *const dimm, const uint8_t *cbfs_spd) +{ + if (dimm->spd_addr == SPD_MEMORY_DOWN) { + if (cbfs_spd) { + memcpy(dimm->raw_spd, cbfs_spd, SPD_LEN); + dimm->valid = true; + printk(RAM_DEBUG, "memory-down\n"); + return; + } else { + printk(RAM_DEBUG, "memory-down but no CBFS SPD data, ignoring\n"); + return; + } + } + printk(RAM_DEBUG, "slotted "); + const uint8_t spd_mem_type = smbus_read_byte(dimm->spd_addr, SPD_MEMORY_TYPE); + if (spd_mem_type != SPD_MEMORY_TYPE_SDRAM_DDR3) { + printk(RAM_DEBUG, "and not DDR3, ignoring\n"); + return; + } + printk(RAM_DEBUG, "and DDR3\n"); + if (i2c_eeprom_read(dimm->spd_addr, 0, SPD_LEN, dimm->raw_spd) != SPD_LEN) { + printk(BIOS_WARNING, "I2C block read failed, trying SMBus byte reads\n"); + for (uint32_t i = 0; i < SPD_LEN; i++) + dimm->raw_spd[i] = smbus_read_byte(dimm->spd_addr, i); + } + dimm->valid = true; +} + +static void get_spd_data(struct sysinfo *ctrl) +{ + struct spd_info spdi = {0}; + mb_get_spd_map(&spdi); + const uint8_t *cbfs_spd = get_spd_data_from_cbfs(&spdi); + for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) { + for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) { + struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot]; + dimm->spd_addr = spdi.addresses[NUM_SLOTS * channel + slot]; + if (!dimm->spd_addr) + continue; + + printk(RAM_DEBUG, "CH%uS%u is ", channel, slot); + get_spd_for_dimm(dimm, cbfs_spd); + } + } +} + +static void decode_spd(struct raminit_dimm_info *const dimm) +{ + /** TODO: Hook up somewhere, and handle lack of XMP data **/ + const bool enable_xmp = false; + memset(&dimm->data, 0, sizeof(dimm->data)); + if (enable_xmp) + spd_xmp_decode_ddr3(&dimm->data, dimm->raw_spd, DDR3_XMP_PROFILE_1); + else + spd_decode_ddr3(&dimm->data, dimm->raw_spd); + + if (CONFIG(DEBUG_RAM_SETUP)) + dram_print_spd_ddr3(&dimm->data); +} + +static enum raminit_status find_common_spd_parameters(struct sysinfo *ctrl) +{ + ctrl->cas_supported = 0xffff; + ctrl->flags.raw = 0xffffffff; + + ctrl->tCK = 0; + ctrl->tAA = 0; + ctrl->tWR = 0; + ctrl->tRCD = 0; + ctrl->tRRD = 0; + ctrl->tRP = 0; + ctrl->tRAS = 0; + ctrl->tRC = 0; + ctrl->tRFC = 0; + ctrl->tWTR = 0; + ctrl->tRTP = 0; + ctrl->tFAW = 0; + ctrl->tCWL = 0; + ctrl->tCMD = 0; + ctrl->chanmap = 0; + + bool yes_ecc = false; + bool not_ecc = false; + + for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) { + ctrl->dpc[channel] = 0; + ctrl->rankmap[channel] = 0; + ctrl->rank_mirrored[channel] = 0; + ctrl->channel_size_mb[channel] = 0; + for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) { + struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot]; + if (!dimm->valid) + continue; + + printk(RAM_DEBUG, "\nCH%uS%u SPD:\n", channel, slot); + decode_spd(dimm); + + ctrl->chanmap |= BIT(channel); + ctrl->dpc[channel]++; + ctrl->channel_size_mb[channel] += dimm->data.size_mb; + + /* The first rank of a populated slot is always present */ + const uint8_t rank = slot + slot; + assert(dimm->data.ranks); + ctrl->rankmap[channel] |= (BIT(dimm->data.ranks) - 1) << rank; + + if (dimm->data.flags.pins_mirrored) + ctrl->rank_mirrored[channel] |= BIT(rank + 1); + + /* Find common settings */ + ctrl->cas_supported &= dimm->data.cas_supported; + ctrl->flags.raw &= dimm->data.flags.raw; + ctrl->tCK = MAX(ctrl->tCK, dimm->data.tCK); + ctrl->tAA = MAX(ctrl->tAA, dimm->data.tAA); + ctrl->tWR = MAX(ctrl->tWR, dimm->data.tWR); + ctrl->tRCD = MAX(ctrl->tRCD, dimm->data.tRCD); + ctrl->tRRD = MAX(ctrl->tRRD, dimm->data.tRRD); + ctrl->tRP = MAX(ctrl->tRP, dimm->data.tRP); + ctrl->tRAS = MAX(ctrl->tRAS, dimm->data.tRAS); + ctrl->tRC = MAX(ctrl->tRC, dimm->data.tRC); + ctrl->tRFC = MAX(ctrl->tRFC, dimm->data.tRFC); + ctrl->tWTR = MAX(ctrl->tWTR, dimm->data.tWTR); + ctrl->tRTP = MAX(ctrl->tRTP, dimm->data.tRTP); + ctrl->tFAW = MAX(ctrl->tFAW, dimm->data.tFAW); + ctrl->tCWL = MAX(ctrl->tCWL, dimm->data.tCWL); + ctrl->tCMD = MAX(ctrl->tCMD, dimm->data.tCMD); + + yes_ecc |= dimm->data.flags.is_ecc; + not_ecc |= !dimm->data.flags.is_ecc; + } + } + + if (!ctrl->chanmap) { + printk(BIOS_ERR, "No DIMMs were found\n"); + return RAMINIT_STATUS_NO_MEMORY_INSTALLED; + } + if (!ctrl->cas_supported) { + printk(BIOS_ERR, "Could not resolve common CAS latency\n"); + return RAMINIT_STATUS_UNSUPPORTED_MEMORY; + } + /** TODO: Properly handle ECC support and ECC forced **/ + if (yes_ecc && not_ecc) { + /** TODO: Test if the ECC DIMMs can be operated as non-ECC DIMMs **/ + printk(BIOS_ERR, "Both ECC and non-ECC DIMMs present, this is unsupported\n"); + return RAMINIT_STATUS_UNSUPPORTED_MEMORY; + } + if (yes_ecc) + ctrl->lanes = NUM_LANES; + else + ctrl->lanes = NUM_LANES_NO_ECC; + + ctrl->is_ecc = yes_ecc; + + /** TODO: Complete LPDDR support **/ + ctrl->lpddr = false; + + return RAMINIT_STATUS_SUCCESS; +} + +enum raminit_status collect_spd_info(struct sysinfo *ctrl) +{ + get_spd_data(ctrl); + return find_common_spd_parameters(ctrl); +} |