summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/soc/intel/common/block/include/intelblocks/meminit.h159
-rw-r--r--src/soc/intel/common/block/memory/Kconfig31
-rw-r--r--src/soc/intel/common/block/memory/Makefile.inc1
-rw-r--r--src/soc/intel/common/block/memory/meminit.c171
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");
+ }
+}