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

#include <amdblocks/biosram.h>
#include <acpi/acpi.h>
#include <arch/cpu.h>
#include <arch/romstage.h>
#include <cbmem.h>
#include <console/console.h>
#include <cpu/x86/lapic.h>
#include <halt.h>
#include <program_loading.h>
#include <romstage_handoff.h>
#include <smp/node.h>
#include <string.h>
#include <timestamp.h>
#include <northbridge/amd/agesa/agesa_helper.h>
#include <northbridge/amd/agesa/state_machine.h>

void __weak board_BeforeAgesa(struct sysinfo *cb) { }

static void fill_sysinfo(struct sysinfo *cb)
{
	memset(cb, 0, sizeof(*cb));
	cb->s3resume = acpi_is_wakeup_s3();

	agesa_set_interface(cb);
}

/* APs will enter directly here from bootblock, bypassing verstage
 * and potential fallback / normal bootflow detection.
 */
static void ap_romstage_main(void);

static void romstage_main(void)
{
	struct postcar_frame pcf;
	struct sysinfo romstage_state;
	struct sysinfo *cb = &romstage_state;
	unsigned int initial_apic_id = initial_lapicid();
	int cbmem_initted = 0;

	fill_sysinfo(cb);

	if (initial_apic_id == 0) {

		timestamp_add_now(TS_START_ROMSTAGE);

		board_BeforeAgesa(cb);

		console_init();
	}

	printk(BIOS_DEBUG, "APIC %02u: CPU Family_Model = %08x\n",
		initial_apic_id, cpuid_eax(1));

	set_ap_entry_ptr(ap_romstage_main);

	agesa_execute_state(cb, AMD_INIT_RESET);

	agesa_execute_state(cb, AMD_INIT_EARLY);

	timestamp_add_now(TS_BEFORE_INITRAM);

	if (!cb->s3resume)
		agesa_execute_state(cb, AMD_INIT_POST);
	else
		agesa_execute_state(cb, AMD_INIT_RESUME);

	timestamp_add_now(TS_AFTER_INITRAM);

	/* Work around AGESA setting all memory as WB on normal
	 * boot path.
	 */
	fixup_cbmem_to_UC(cb->s3resume);

	cbmem_initted = !cbmem_recovery(cb->s3resume);

	if (cb->s3resume && !cbmem_initted) {
		printk(BIOS_EMERG, "Unable to recover CBMEM\n");
		halt();
	}

	romstage_handoff_init(cb->s3resume);

	postcar_frame_init(&pcf, HIGH_ROMSTAGE_STACK_SIZE);
	recover_postcar_frame(&pcf, cb->s3resume);

	run_postcar_phase(&pcf);
	/* We do not return. */
}

static void ap_romstage_main(void)
{
	struct sysinfo romstage_state;
	struct sysinfo *cb = &romstage_state;

	fill_sysinfo(cb);

	agesa_execute_state(cb, AMD_INIT_RESET);

	agesa_execute_state(cb, AMD_INIT_EARLY);

	/* Not reached. */
	halt();
}

asmlinkage void car_stage_entry(void)
{
	romstage_main();
}

void *cbmem_top_chipset(void)
{
	/* Top of CBMEM is at highest usable DRAM address below 4GiB. */
	return (void *)restore_top_of_low_cacheable();
}
