diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/soc/intel/common/block/include/intelblocks/meminit.h | 159 | ||||
-rw-r--r-- | src/soc/intel/common/block/memory/Kconfig | 31 | ||||
-rw-r--r-- | src/soc/intel/common/block/memory/Makefile.inc | 1 | ||||
-rw-r--r-- | src/soc/intel/common/block/memory/meminit.c | 171 |
4 files changed, 362 insertions, 0 deletions
diff --git a/src/soc/intel/common/block/include/intelblocks/meminit.h b/src/soc/intel/common/block/include/intelblocks/meminit.h new file mode 100644 index 0000000000..cbec04ddb2 --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/meminit.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__ +#define __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__ + +#include <stddef.h> +#include <stdint.h> +#include <types.h> + +/* + * Calculates the number of channels depending upon the data bus width of the + * platform and the channel width. + */ +#define CHANNEL_COUNT(ch_width) (CONFIG_DATA_BUS_WIDTH / (ch_width)) + +/* + * UPDs for FSP-M are organized depending upon the MRC's view of channel. Thus, + * the number of channels as seen by the MRC are dependent on the channel width + * assumption in the UPDs. These channels might not necessarily be the same as + * the physical channels supported by the platform. + */ +#define MRC_CHANNELS CHANNEL_COUNT(CONFIG_MRC_CHANNEL_WIDTH) + +/* Different memory topologies supported by the platform. */ +enum mem_topology { + MEM_TOPO_MEMORY_DOWN = BIT(0), + MEM_TOPO_DIMM_MODULE = BIT(1), + MEM_TOPO_MIXED = MEM_TOPO_MEMORY_DOWN | MEM_TOPO_DIMM_MODULE, +}; + +/* + * SPD provides information about the memory module. Depending upon the memory + * topology, the SPD data can be obtained from different sources. Example: for + * memory down topology, SPD is read from CBFS using cbfs_index. For DIMM + * modules, SPD is read from EEPROM using the DIMM addresses provided by the + * mainboard. + */ +struct mem_spd { + enum mem_topology topo; + /* + * SPD data is read from CBFS spd.bin file using cbfs_index to locate + * the entry. This is used in case of MEM_TOPO_MEMORY_DOWN and + * MEM_TOPO_MIXED topologies. + */ + size_t cbfs_index; + + /* + * SPD data is read from on-module EEPROM using the DIMM addresses + * provided by the mainboard. This is used in case of + * MEM_TOPO_DIMM_MODULE and MEM_TOPO_MIXED topologies. + * + * Up to a maximum of MRC_CHANNELS * CONFIG_DIMMS_PER_CHANNEL addresses + * can be provided by mainboard. However, depending upon the memory + * technology being used and the number of physical channels supported + * by that technology, the actual channels might be less than + * MRC_CHANNELS. + */ + struct { + uint8_t addr_dimm[CONFIG_DIMMS_PER_CHANNEL]; + } smbus[MRC_CHANNELS]; +}; + +/* Information about memory technology supported by SoC */ +struct soc_mem_cfg { + /* + * Number of physical channels that are supported by the memory + * technology. + */ + size_t num_phys_channels; + + /* + * Map of physical channel numbers to MRC channel numbers. This is + * helpful in identifying what SPD entries need to be filled for a + * physical channel. + * + * Example: MRC supports 8 channels 0 - 7, but a memory technology + * supports only 2 physical channels 0 - 1. In this case, the map could + * be: + * [0] = 0, + * [1] = 4, + * indicating that physical channel 0 is mapped to MRC channel 0 and + * physical channel 1 is mapped to MRC channel 4. + */ + size_t phys_to_mrc_map[MRC_CHANNELS]; + + /* + * Masks to be applied in case of memory down topology. For memory down + * topology, there is no separate EEPROM. Thus, the masks need to be + * hard-coded by the SoC to indicate what combinations are supported. + * This is a mask of physical channels for the memory technology. + * + * Example: For the memory technology supporting 2 physical channels, + * where the population rules restrict use of channel 0 for + * half-channel, half_channel mask would be set to 0x1 indicating + * channel 0 is always populated. + */ + struct { + /* + * Mask of physical channels that are populated in case of + * half-channel configuration. + */ + uint32_t half_channel; + /* + * Mask of physical channels that are populated with memory + * down parts in case of mixed topology. + */ + uint32_t mixed_topo; + } md_phy_masks; +}; + +/* Flags indicating how the channels are populated. */ +enum channel_population { + NO_CHANNEL_POPULATED = 0, + TOP_HALF_POPULATED = BIT(0), + BOTTOM_HALF_POPULATED = BIT(1), + FULLY_POPULATED = TOP_HALF_POPULATED | BOTTOM_HALF_POPULATED, +}; + +/* + * Data for the memory channels that can be used by SoC code to populate FSP + * UPDs. + */ +struct mem_channel_data { + /* Pointer to SPD data for each DIMM of each channel */ + uintptr_t spd[MRC_CHANNELS][CONFIG_DIMMS_PER_CHANNEL]; + /* Length of SPD data */ + size_t spd_len; + /* Flags indicating how channels are populated */ + enum channel_population ch_population_flags; +}; + +/* + * This change populates data regarding memory channels in `struct + * mem_channel_data` using the following inputs from SoC code: + * soc_mem_cfg : SoC-specific information about the memory technology used by + * the mainboard. + * spd_info : Information about the memory topology. + * half_populated: Hint from mainboard if channels are half populated. + */ +void mem_populate_channel_data(const struct soc_mem_cfg *soc_mem_cfg, + const struct mem_spd *spd_info, + bool half_populated, + struct mem_channel_data *data); + +/* + * Given a channel number and the maximum number of supported channels, this + * function returns if a channel is populated. This is useful for populating + * DQ/DQS UPDs by the SoC code. + */ +static inline bool channel_is_populated(size_t curr_ch, size_t max_ch, + enum channel_population flags) +{ + if ((curr_ch * 2) < max_ch) + return !!(flags & BOTTOM_HALF_POPULATED); + + return !!(flags & TOP_HALF_POPULATED); +} + +#endif /* __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__ */ diff --git a/src/soc/intel/common/block/memory/Kconfig b/src/soc/intel/common/block/memory/Kconfig new file mode 100644 index 0000000000..fdcccbf742 --- /dev/null +++ b/src/soc/intel/common/block/memory/Kconfig @@ -0,0 +1,31 @@ +config SOC_INTEL_COMMON_BLOCK_MEMINIT + bool + help + Intel common block support for performing initialization + of FSPM UPDs. + +if SOC_INTEL_COMMON_BLOCK_MEMINIT + +config DIMMS_PER_CHANNEL + int + default 0 + help + Maximum number of DIMMs per channel if the memory controller + supports DIMM modules for any memory technology. + +config DATA_BUS_WIDTH + int + default 0 + help + Data bus width of the platform. + +config MRC_CHANNEL_WIDTH + int + default 0 + help + Width of the memory channel from the perspective of MRC. This + determines the UPD organization. SoC using this common block + support is expected to set MRC_CHANNEL_WIDTH as per the FSP + MRC expectation. + +endif diff --git a/src/soc/intel/common/block/memory/Makefile.inc b/src/soc/intel/common/block/memory/Makefile.inc new file mode 100644 index 0000000000..1009872702 --- /dev/null +++ b/src/soc/intel/common/block/memory/Makefile.inc @@ -0,0 +1 @@ +romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_MEMINIT) += meminit.c diff --git a/src/soc/intel/common/block/memory/meminit.c b/src/soc/intel/common/block/memory/meminit.c new file mode 100644 index 0000000000..84987d612b --- /dev/null +++ b/src/soc/intel/common/block/memory/meminit.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <assert.h> +#include <console/console.h> +#include <intelblocks/meminit.h> +#include <commonlib/region.h> +#include <spd_bin.h> +#include <string.h> + +_Static_assert(CONFIG_MRC_CHANNEL_WIDTH > 0, "MRC channel width must be >0!"); +_Static_assert(CONFIG_DATA_BUS_WIDTH > 0, "Data bus width must be >0!"); +_Static_assert(CONFIG_DIMMS_PER_CHANNEL > 0, "DIMMS per channel must be >0!"); + +/* + * Given mask of channels that are populated, this function returns the flags + * indicating which half of the channels are populated. + */ +static enum channel_population populated_mask_to_flag(uint32_t pop_mask, size_t max_channels) +{ + uint32_t full_mask = BIT(max_channels) - 1; + uint32_t bottom_mask = BIT(max_channels / 2) - 1; + uint32_t top_mask = ~bottom_mask & full_mask; + + if (pop_mask == full_mask) + return FULLY_POPULATED; + else if (pop_mask == bottom_mask) + return BOTTOM_HALF_POPULATED; + else if (pop_mask == top_mask) + return TOP_HALF_POPULATED; + else if (pop_mask == 0) + return NO_CHANNEL_POPULATED; + + die("Unsupported channel population mask(0x%x)\n", pop_mask); +} + +static void read_spd_md(const struct soc_mem_cfg *soc_mem_cfg, const struct mem_spd *info, + bool half_populated, struct mem_channel_data *channel_data, + size_t *spd_len) +{ + size_t ch; + size_t num_phys_ch = soc_mem_cfg->num_phys_channels; + struct region_device spd_rdev; + uintptr_t spd_data; + + /* + * For memory down topologies, start with full mask as per the number + * of physical channels and mask out any channels based on mixed + * topology or half populated flag as set by the mainboard. + */ + uint32_t pop_mask = BIT(num_phys_ch) - 1; + + if (!(info->topo & MEM_TOPO_MEMORY_DOWN)) + return; + + if (info->topo == MEM_TOPO_MIXED) + pop_mask &= soc_mem_cfg->md_phy_masks.mixed_topo; + + if (half_populated) + pop_mask &= soc_mem_cfg->md_phy_masks.half_channel; + + if (pop_mask == 0) + die("Memory technology does not support the selected configuration!\n"); + + printk(BIOS_DEBUG, "SPD index = %zu\n", info->cbfs_index); + + if (get_spd_cbfs_rdev(&spd_rdev, info->cbfs_index) < 0) + die("SPD not found in CBFS or incorrect index!\n"); + + /* Memory leak is ok as long as we have memory mapped boot media */ + _Static_assert(CONFIG(BOOT_DEVICE_MEMORY_MAPPED), + "Function assumes memory-mapped boot media"); + + spd_data = (uintptr_t)rdev_mmap_full(&spd_rdev); + *spd_len = region_device_sz(&spd_rdev); + + print_spd_info((uint8_t *)spd_data); + + for (ch = 0; ch < num_phys_ch; ch++) { + if (!(pop_mask & BIT(ch))) + continue; + + int mrc_ch = soc_mem_cfg->phys_to_mrc_map[ch]; + + /* + * Memory down topology simulates a DIMM. So, the assumption is + * that there is a single DIMM per channel when using memory + * down topology. As SPD describes a DIMM, only DIMM0 for each + * physical channel is filled here. + */ + channel_data->spd[mrc_ch][0] = spd_data; + } + + channel_data->ch_population_flags |= populated_mask_to_flag(pop_mask, num_phys_ch); +} + +#define CH_DIMM_OFFSET(ch, dimm) ((ch) * CONFIG_DIMMS_PER_CHANNEL + (dimm)) + +static void read_spd_dimm(const struct soc_mem_cfg *soc_mem_cfg, const struct mem_spd *info, + bool half_populated, struct mem_channel_data *channel_data, + size_t *spd_len) +{ + size_t ch, dimm; + struct spd_block blk = { 0 }; + size_t num_phys_ch = soc_mem_cfg->num_phys_channels; + + /* + * For DIMM modules, start with mask set to no channels populated. If + * SPD is read successfully from EEPROM for any channel, then that + * channel is marked as populated. + */ + uint32_t pop_mask = 0; + + if (!(info->topo & MEM_TOPO_DIMM_MODULE)) + return; + + for (ch = 0; ch < num_phys_ch; ch++) { + for (dimm = 0; dimm < CONFIG_DIMMS_PER_CHANNEL; dimm++) { + blk.addr_map[CH_DIMM_OFFSET(ch, dimm)] = + info->smbus[ch].addr_dimm[dimm]; + } + } + + get_spd_smbus(&blk); + *spd_len = blk.len; + + for (ch = 0; ch < num_phys_ch; ch++) { + size_t mrc_ch = soc_mem_cfg->phys_to_mrc_map[ch]; + + for (dimm = 0; dimm < CONFIG_DIMMS_PER_CHANNEL; dimm++) { + uint8_t *spd_data = blk.spd_array[CH_DIMM_OFFSET(ch, dimm)]; + if (spd_data == NULL) + continue; + + print_spd_info(spd_data); + + channel_data->spd[mrc_ch][dimm] = (uintptr_t)(void *)spd_data; + pop_mask |= BIT(ch); + } + } + + channel_data->ch_population_flags |= populated_mask_to_flag(pop_mask, num_phys_ch); +} + +void mem_populate_channel_data(const struct soc_mem_cfg *soc_mem_cfg, + const struct mem_spd *spd_info, + bool half_populated, + struct mem_channel_data *data) +{ + size_t spd_md_len = 0, spd_dimm_len = 0; + + memset(data, 0, sizeof(*data)); + + read_spd_md(soc_mem_cfg, spd_info, half_populated, data, &spd_md_len); + read_spd_dimm(soc_mem_cfg, spd_info, half_populated, data, &spd_dimm_len); + + if (data->ch_population_flags == NO_CHANNEL_POPULATED) + die("No channels are populated. Incorrect memory configuration!\n"); + + if (spd_info->topo == MEM_TOPO_MEMORY_DOWN) { + data->spd_len = spd_md_len; + } else if (spd_info->topo == MEM_TOPO_DIMM_MODULE) { + data->spd_len = spd_dimm_len; + } else { + /* + * SPD lengths must match for CBFS and EEPROM SPD for mixed + * topology. + */ + if (spd_md_len != spd_dimm_len) + die("Length of SPD does not match for mixed topology!\n"); + } +} |