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

#include <cbmem.h>
#include <console/console.h>
#include <string.h>

#include <soc/hob_mem.h>

#include <smbios.h>
#include <memory_info.h>
#include <soc/ramstage.h>

/* Save the DIMM information for SMBIOS table 17 */
void soc_save_dimm_info(void)
{
	int channel;
	const CHANNEL_INFO *channel_info;
	int dimm;
	const DIMM_INFO *dimm_info;
	int dimm_max;
	int index;
	struct memory_info *mem_info;
	const FSP_SMBIOS_MEMORY_INFO *memory_info_hob;

	/* Get the memory info HOB */
	memory_info_hob = soc_get_fsp_smbios_memory_info_hob();

	if (memory_info_hob == NULL)
		return;

	/* Display the data in the FSP_SMBIOS_MEMORY_INFO HOB */
	if (CONFIG(DISPLAY_HOBS))
		soc_display_fsp_smbios_memory_info_hob(memory_info_hob);

	/*
	 * Allocate CBMEM area for DIMM information used to populate SMBIOS
	 * table 17
	 */
	mem_info = cbmem_add(CBMEM_ID_MEMINFO, sizeof(*mem_info));
	printk(BIOS_DEBUG, "CBMEM entry for DIMM info: %p\n", mem_info);
	if (mem_info == NULL)
		return;
	memset(mem_info, 0, sizeof(*mem_info));

	/* Describe the first N DIMMs in the system */
	index = 0;
	dimm_max = ARRAY_SIZE(mem_info->dimm);
	for (channel = 0; channel < memory_info_hob->ChannelCount; channel++) {
		if (index >= dimm_max)
			break;
		channel_info = &memory_info_hob->ChannelInfo[channel];
		for (dimm = 0; dimm < channel_info->DimmCount; dimm++) {
			if (index >= dimm_max)
				break;
			dimm_info = &channel_info->DimmInfo[dimm];

			/* Populate the DIMM information */
			if (!dimm_info->SizeInMb)
				continue;

			mem_info->dimm[index].dimm_size =
				dimm_info->SizeInMb;
			mem_info->dimm[index].ddr_type =
				memory_info_hob->MemoryType;
			mem_info->dimm[index].ddr_frequency =
				memory_info_hob->MemoryFrequencyInMHz;
			mem_info->dimm[index].channel_num =
				channel_info->ChannelId;
			mem_info->dimm[index].dimm_num =
				dimm_info->DimmId;

			strncpy((char *)
				mem_info->dimm[index].module_part_number,
				(char *)dimm_info->ModulePartNum, 18);
			mem_info->dimm[index].mod_id =
				dimm_info->MfgId;
			switch (memory_info_hob->DataWidth) {
			default:
			case 8:
				mem_info->dimm[index].bus_width =
					MEMORY_BUS_WIDTH_8;
				break;

			case 16:
				mem_info->dimm[index].bus_width =
					MEMORY_BUS_WIDTH_16;
				break;

			case 32:
				mem_info->dimm[index].bus_width =
					MEMORY_BUS_WIDTH_32;
				break;

			case 64:
				mem_info->dimm[index].bus_width =
					MEMORY_BUS_WIDTH_64;
				break;

			case 128:
				mem_info->dimm[index].bus_width =
					MEMORY_BUS_WIDTH_128;
				break;
			}

			/* Add any mainboard specific information */
			mainboard_add_dimm_info(mem_info, channel, dimm, index);
			index++;
		}
	}
	mem_info->dimm_cnt = index;
	printk(BIOS_DEBUG, "%d DIMMs found\n", mem_info->dimm_cnt);
}

/* Add any mainboard specific information */
__weak void mainboard_add_dimm_info(struct memory_info *mem_info,
						   int channel, int dimm,
						   int index)
{
	printk(BIOS_DEBUG, "WEAK: %s/%s called\n", __FILE__, __func__);
}
