soc/intel/common: Improve Type16 SMBIOS tables

Use CAPID0_A to provide information closer to reality.

* Correctly advertise ECC support, max DIMM count and max capacity
* CAPID0_A hasn't changed since SNB, but most EDS mark the bits as
  reserved even though they are still used by FSP.
* Assume the same bits for Tiger Lake as for Ice Lake
* Assume the same bits for Skylake as for Coffee Lake
* Add CAPID0_A to Icelake headers

The lastest complete documentation can be found in Document: 341078-002.

Change-Id: I0d8fbb512fccbd99a6cfdacadc496d8266ae4cc7
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/41334
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net>
Reviewed-by: Christian Walter <christian.walter@9elements.com>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
diff --git a/src/soc/intel/apollolake/systemagent.c b/src/soc/intel/apollolake/systemagent.c
index 258376a..e05a470 100644
--- a/src/soc/intel/apollolake/systemagent.c
+++ b/src/soc/intel/apollolake/systemagent.c
@@ -88,3 +88,9 @@
 
 	return 0;
 }
+
+uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz)
+{
+	/* Max 4GiB per rank, 2 ranks per channel. Intel Document: 332092-002 */
+	return 8192;
+}
diff --git a/src/soc/intel/cannonlake/systemagent.c b/src/soc/intel/cannonlake/systemagent.c
index c225651..d57b3e9 100644
--- a/src/soc/intel/cannonlake/systemagent.c
+++ b/src/soc/intel/cannonlake/systemagent.c
@@ -73,3 +73,17 @@
 	soc_config = &config->power_limits_config;
 	set_power_limits(MOBILE_SKU_PL1_TIME_SEC, soc_config);
 }
+
+uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz)
+{
+	switch (capid0_a_ddrsz) {
+	case 1:
+		return 8192;
+	case 2:
+		return 4096;
+	case 3:
+		return 2048;
+	default:
+		return 32768;
+	}
+}
diff --git a/src/soc/intel/common/block/include/intelblocks/systemagent.h b/src/soc/intel/common/block/include/intelblocks/systemagent.h
index 30c892b..ae72116 100644
--- a/src/soc/intel/common/block/include/intelblocks/systemagent.h
+++ b/src/soc/intel/common/block/include/intelblocks/systemagent.h
@@ -90,4 +90,8 @@
 /* SoC specific APIs to get UNCORE PRMRR base and mask values
  * returns 0, if able to get base and mask values; otherwise returns -1 */
 int soc_get_uncore_prmmr_base_and_mask(uint64_t *base, uint64_t *mask);
+
+/* Returns the maximum supported capacity of a channel as encoded by DDRSZ in MiB */
+uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz);
+
 #endif	/* SOC_INTEL_COMMON_BLOCK_SA_H */
diff --git a/src/soc/intel/common/block/systemagent/systemagent.c b/src/soc/intel/common/block/systemagent/systemagent.c
index 269236b..d25e1aa 100644
--- a/src/soc/intel/common/block/systemagent/systemagent.c
+++ b/src/soc/intel/common/block/systemagent/systemagent.c
@@ -41,6 +41,11 @@
 	return current;
 }
 
+__weak uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz)
+{
+	return 32768;	/* 32 GiB per channel */
+}
+
 /*
  * Add all known fixed MMIO ranges that hang off the host bridge/memory
  * controller device.
@@ -258,11 +263,27 @@
 }
 
 #if CONFIG(GENERATE_SMBIOS_TABLES)
+static bool sa_supports_ecc(const uint32_t capida)
+{
+	return !(capida & CAPID_ECCDIS);
+}
+
+static size_t sa_slots_per_channel(const uint32_t capida)
+{
+	return !(capida & CAPID_DDPCD) + 1;
+}
+
+static size_t sa_number_of_channels(const uint32_t capida)
+{
+	return !(capida & CAPID_PDCD) + 1;
+}
+
 static int sa_smbios_write_type_16(struct device *dev, int *handle,
 		unsigned long *current)
 {
 	struct smbios_type16 *t = (struct smbios_type16 *)*current;
 	int len = sizeof(struct smbios_type16);
+	const uint32_t capida = pci_read_config32(dev, CAPID0_A);
 
 	struct memory_info *meminfo;
 	meminfo = cbmem_find(CBMEM_ID_MEMINFO);
@@ -275,12 +296,14 @@
 	t->length = len - 2;
 	t->location = MEMORY_ARRAY_LOCATION_SYSTEM_BOARD;
 	t->use = MEMORY_ARRAY_USE_SYSTEM;
-	/* TBD, meminfo hob have information about ECC */
-	t->memory_error_correction = MEMORY_ARRAY_ECC_NONE;
+	t->memory_error_correction = sa_supports_ecc(capida) ? MEMORY_ARRAY_ECC_SINGLE_BIT :
+		MEMORY_ARRAY_ECC_NONE;
 	/* no error information handle available */
 	t->memory_error_information_handle = 0xFFFE;
-	t->maximum_capacity = 32 * (GiB / KiB); /* 32GB as default */
-	t->number_of_memory_devices = meminfo->dimm_cnt;
+	t->maximum_capacity = soc_systemagent_max_chan_capacity_mib(CAPID_DDRSZ(capida)) *
+			      sa_number_of_channels(capida) * (MiB / KiB);
+	t->number_of_memory_devices = sa_slots_per_channel(capida) *
+				      sa_number_of_channels(capida);
 
 	*current += len;
 	*handle += 1;
diff --git a/src/soc/intel/common/block/systemagent/systemagent_def.h b/src/soc/intel/common/block/systemagent/systemagent_def.h
index a652dfd..a7823c3 100644
--- a/src/soc/intel/common/block/systemagent/systemagent_def.h
+++ b/src/soc/intel/common/block/systemagent/systemagent_def.h
@@ -17,6 +17,11 @@
 #define  DPR_EPM	(1 << 2)
 #define  DPR_PRS	(1 << 1)
 #define  DPR_SIZE_MASK	0xff0
+/* CAPID0_A */
+#define  CAPID_ECCDIS	(1 << 25)
+#define  CAPID_DDPCD	(1 << 14)
+#define  CAPID_PDCD	(1 << 12)
+#define  CAPID_DDRSZ(x)	(((x) >> 19) & 0x3)
 
 #define  PCIEXBAR_LENGTH_64MB	2
 #define  PCIEXBAR_LENGTH_128MB	1
diff --git a/src/soc/intel/icelake/include/soc/systemagent.h b/src/soc/intel/icelake/include/soc/systemagent.h
index 297ee5d..90465a24 100644
--- a/src/soc/intel/icelake/include/soc/systemagent.h
+++ b/src/soc/intel/icelake/include/soc/systemagent.h
@@ -15,6 +15,7 @@
 #define  D_LCK		(1 << 4)
 #define  G_SMRAME	(1 << 3)
 #define  C_BASE_SEG	((0 << 2) | (1 << 1) | (0 << 0))
+#define CAPID0_A	0xe4
 
 #define BIOS_RESET_CPL		0x5da8
 #define EDRAMBAR		0x5408
diff --git a/src/soc/intel/icelake/systemagent.c b/src/soc/intel/icelake/systemagent.c
index ac96d81..c9fe0a9 100644
--- a/src/soc/intel/icelake/systemagent.c
+++ b/src/soc/intel/icelake/systemagent.c
@@ -52,3 +52,17 @@
 	/* Enable BIOS Reset CPL */
 	enable_bios_reset_cpl();
 }
+
+uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz)
+{
+	switch (capid0_a_ddrsz) {
+	case 1:
+		return 8192;
+	case 2:
+		return 4096;
+	case 3:
+		return 2048;
+	default:
+		return 65536;
+	}
+}
diff --git a/src/soc/intel/skylake/systemagent.c b/src/soc/intel/skylake/systemagent.c
index 8e58bf6..f28bd25 100644
--- a/src/soc/intel/skylake/systemagent.c
+++ b/src/soc/intel/skylake/systemagent.c
@@ -87,3 +87,17 @@
 	*prmrr_mask = (uint64_t) msr.hi << 32 | msr.lo;
 	return 0;
 }
+
+uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz)
+{
+	switch (capid0_a_ddrsz) {
+	case 1:
+		return 8192;
+	case 2:
+		return 4096;
+	case 3:
+		return 2048;
+	default:
+		return 32768;
+	}
+}
diff --git a/src/soc/intel/tigerlake/systemagent.c b/src/soc/intel/tigerlake/systemagent.c
index 8c0a42a..08d1ef3 100644
--- a/src/soc/intel/tigerlake/systemagent.c
+++ b/src/soc/intel/tigerlake/systemagent.c
@@ -67,3 +67,17 @@
 	soc_config = &config->power_limits_config;
 	set_power_limits(MOBILE_SKU_PL1_TIME_SEC, soc_config);
 }
+
+uint32_t soc_systemagent_max_chan_capacity_mib(u8 capid0_a_ddrsz)
+{
+	switch (capid0_a_ddrsz) {
+	case 1:
+		return 8192;
+	case 2:
+		return 4096;
+	case 3:
+		return 2048;
+	default:
+		return 65536;
+	}
+}