soc/amd/common/block/acpi/cpu_power_state: add get_cstate_info helper

Introduce the get_cstate_info helper function that populates the caller-
provided cstate_values array with the data returned by the SoC-specific
get_cstate_config_data function. From the array get_cstate_config_data
returns, only the ctype, latency and power fields are used, so the rest
can be left uninitialized. Those 3 fields are compile-time constants.
For each entry, write_cstate_entry will generate the corresponding
resource information from the given data. In the C1 case where ctype is
1, the state is entered via a MWAIT instruction, while the higher C
states are entered by doing an IO read from a specific IO address. This
IO address is x - 1 bytes into the IO region starting at
MSR_CSTATE_ADDRESS for the Cx state. So for example C2 is entered by
reading from the C state IO base address + 1. This resource information
is generated during runtime, since the contents of MSR_CSTATE_ADDRESS
aren't necessarily known at compile-time.

MAX_CSTATE_COUNT is introduced so that the caller can allocate and pass
a buffer with space for the maximum number of C state entries. This
maximum number corresponds to the number of IO addresses the CPU traps
beginning from MSR_CSTATE_ADDRESS. In practice, it's unlikely that more
than 3 or maybe 4 C states will be available though.

Signed-off-by: Felix Held <felix-coreboot@felixheld.de>
Change-Id: I2c36c1d604ced349c609882b9d9fe84d5f726a8d
Reviewed-on: https://review.coreboot.org/c/coreboot/+/73428
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Fred Reitberger <reitbergerfred@gmail.com>
Reviewed-by: Eric Lai <eric_lai@quanta.corp-partner.google.com>
diff --git a/src/soc/amd/common/block/acpi/Kconfig b/src/soc/amd/common/block/acpi/Kconfig
index 46251d6..039fed8 100644
--- a/src/soc/amd/common/block/acpi/Kconfig
+++ b/src/soc/amd/common/block/acpi/Kconfig
@@ -18,6 +18,9 @@
 config SOC_AMD_COMMON_BLOCK_ACPI_CPPC
 	bool
 
+config SOC_AMD_COMMON_BLOCK_ACPI_CPU_POWER_STATE
+	bool
+
 config SOC_AMD_COMMON_BLOCK_ACPI_GPIO
 	bool
 
diff --git a/src/soc/amd/common/block/acpi/Makefile.inc b/src/soc/amd/common/block/acpi/Makefile.inc
index 2c8f391..cf1e9c1 100644
--- a/src/soc/amd/common/block/acpi/Makefile.inc
+++ b/src/soc/amd/common/block/acpi/Makefile.inc
@@ -13,6 +13,7 @@
 ramstage-$(CONFIG_ACPI_BERT) += bert.c
 ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_ACPI_ALIB) += alib.c
 ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_ACPI_CPPC) += cppc.c
+ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_ACPI_CPU_POWER_STATE) += cpu_power_state.c
 ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_ACPI_GPIO) += gpio.c
 ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_ACPI_IVRS) += ivrs.c
 
diff --git a/src/soc/amd/common/block/acpi/cpu_power_state.c b/src/soc/amd/common/block/acpi/cpu_power_state.c
new file mode 100644
index 0000000..d8af0c9
--- /dev/null
+++ b/src/soc/amd/common/block/acpi/cpu_power_state.c
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpi.h>
+#include <acpi/acpigen.h>
+#include <amdblocks/cpu.h>
+#include <console/console.h>
+#include <cpu/amd/msr.h>
+#include <cpu/x86/msr.h>
+#include <types.h>
+
+static void write_cstate_entry(acpi_cstate_t *entry, const acpi_cstate_t *data,
+			       uint32_t cstate_io_base_address)
+{
+	if (!data->ctype) {
+		printk(BIOS_WARNING, "Invalid C-state data; skipping entry.\n");
+		return;
+	}
+
+	entry->ctype = data->ctype;
+	entry->latency = data->latency;
+	entry->power = data->power;
+
+	if (data->ctype == 1) {
+		entry->resource = (acpi_addr_t){
+			.space_id = ACPI_ADDRESS_SPACE_FIXED,
+			.bit_width = 2,
+			.bit_offset = 2,
+			.addrl = 0,
+			.addrh = 0,
+		};
+	} else {
+		entry->resource = (acpi_addr_t){
+			.space_id = ACPI_ADDRESS_SPACE_IO,
+			.bit_width = 8,
+			.bit_offset = 0,
+			/* ctype is 1-indexed while the offset into cstate_io_base_address is
+			   0-indexed */
+			.addrl = cstate_io_base_address + data->ctype - 1,
+			.addrh = 0,
+			.access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS,
+		};
+	}
+}
+
+size_t get_cstate_info(acpi_cstate_t *cstate_values)
+{
+	size_t i;
+	size_t cstate_count;
+	uint32_t cstate_io_base_address =
+		rdmsr(MSR_CSTATE_ADDRESS).lo & MSR_CSTATE_ADDRESS_MASK;
+	const acpi_cstate_t *cstate_config = get_cstate_config_data(&cstate_count);
+
+	if (cstate_count > MAX_CSTATE_COUNT) {
+		printk(BIOS_WARNING, "cstate_info array has too many entries. "
+			"Skipping last %zu entries.\n",
+			cstate_count - MAX_CSTATE_COUNT);
+		cstate_count = MAX_CSTATE_COUNT;
+	}
+
+	for (i = 0; i < cstate_count; i++) {
+		write_cstate_entry(&cstate_values[i], &cstate_config[i], cstate_io_base_address);
+	}
+
+	return i;
+}
diff --git a/src/soc/amd/common/block/include/amdblocks/cpu.h b/src/soc/amd/common/block/include/amdblocks/cpu.h
index de3fb84..a380a2d 100644
--- a/src/soc/amd/common/block/include/amdblocks/cpu.h
+++ b/src/soc/amd/common/block/include/amdblocks/cpu.h
@@ -3,10 +3,18 @@
 #ifndef AMD_BLOCK_CPU_H
 #define AMD_BLOCK_CPU_H
 
+#include <acpi/acpi.h>
+#include <types.h>
+
+#define MAX_CSTATE_COUNT	8
+
 void early_cache_setup(void);
 int get_cpu_count(void);
 unsigned int get_threads_per_core(void);
 void set_cstate_io_addr(void);
 void write_resume_eip(void);
 
+size_t get_cstate_info(acpi_cstate_t *cstate_values);
+const acpi_cstate_t *get_cstate_config_data(size_t *size);
+
 #endif /* AMD_BLOCK_CPU_H */