/* SPDX-License-Identifier: GPL-2.0-only */

#ifndef __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__
#define __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__

#include <fsp/api.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), // memory is soldered onto board
	MEM_TOPO_DIMM_MODULE = BIT(1), // memory is composed of DIMM modules
	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];

	/* If the SPD data is provided in a memory buffer, spd_data.in_mem needs to be
	 * set to 'true'. In this case spd_data.ptr points to the memory buffer where the
	 * SPD data is stored in and spd_data.len stores the length of the SPD data
	 * in that buffer. In this case cbfs_index is ignored.
	 */
	struct {
		bool in_mem;
		size_t len;
		uintptr_t ptr;
	} spd_data;
};

/* 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:
 * memupd        : FSP-M UPD configuration.
 * 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(FSPM_UPD *memupd, 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__ */
