/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <acpi/acpi.h>
#include <acpi/acpigen.h>
#include <bootstate.h>
#include <commonlib/sort.h>
#include <cpu/x86/lapic.h>
#include <cpu/x86/mp.h>
#include <cpu/cpu.h>
#include <device/path.h>
#include <intelblocks/acpi.h>
#include <soc/cpu.h>
#include <types.h>

#define CPPC_NOM_FREQ_IDX	22
#define CPPC_NOM_PERF_IDX	3

enum cpu_perf_eff_type {
	CPU_TYPE_EFF,
	CPU_TYPE_PERF,
};

struct cpu_apic_info_type {
	/*
	 * Ordered APIC IDs based on core type.
	 * Array begins with Performance Cores' APIC IDs,
	 * then followed by Efficeint Cores's APIC IDs.
	 */
	int32_t apic_ids[CONFIG_MAX_CPUS];

	/* Total CPU count */
	uint16_t total_cpu_cnt;

	/*
	 * Total Performance core count. This will be used
	 * to identify the start of Efficient Cores's
	 * APIC ID list
	 */
	uint16_t perf_cpu_cnt;
};

static struct cpu_apic_info_type cpu_apic_info;

/*
 * The function orders APIC IDs such that orders first Performance cores and then
 * Efficient cores' APIC IDs in ascending order. Also calculates total number of
 * Performance cores and all cores count in the system and populates the information
 * in the cpu_apic_info sturct.
 */
static void acpi_set_hybrid_cpu_apicid_order(void *unused)
{
	size_t perf_core_cnt = 0, eff_core_cnt = 0;
	int32_t eff_apic_ids[CONFIG_MAX_CPUS] = {0};
	extern struct cpu_info cpu_infos[];
	uint32_t i, j = 0;

	for (i = 0; i < ARRAY_SIZE(cpu_apic_info.apic_ids); i++) {
		if (!cpu_infos[i].cpu)
			continue;
		if (cpu_infos[i].cpu->path.apic.core_type == CPU_TYPE_PERF)
			cpu_apic_info.apic_ids[perf_core_cnt++] =
				cpu_infos[i].cpu->path.apic.apic_id;
		else
			eff_apic_ids[eff_core_cnt++] =
				cpu_infos[i].cpu->path.apic.apic_id;
	}

	if (perf_core_cnt > 1)
		bubblesort(cpu_apic_info.apic_ids, perf_core_cnt, NUM_ASCENDING);

	for (i = perf_core_cnt; j < eff_core_cnt; i++, j++)
		cpu_apic_info.apic_ids[i] = eff_apic_ids[j];

	if (eff_core_cnt > 1)
		bubblesort(&cpu_apic_info.apic_ids[perf_core_cnt], eff_core_cnt, NUM_ASCENDING);

	/* Populate total core count */
	cpu_apic_info.total_cpu_cnt = perf_core_cnt + eff_core_cnt;

	cpu_apic_info.perf_cpu_cnt = perf_core_cnt;
}

static unsigned long acpi_create_madt_lapics_hybrid(unsigned long current)
{
	size_t index;

	for (index = 0; index < cpu_apic_info.total_cpu_cnt; index++)
		current = acpi_create_madt_one_lapic(current, index,
						     cpu_apic_info.apic_ids[index]);

	return current;
}

unsigned long acpi_create_madt_lapics_with_nmis_hybrid(unsigned long current)
{
	current = acpi_create_madt_lapics_hybrid(current);
	current = acpi_create_madt_lapic_nmis(current);
	return current;
}

static enum cpu_perf_eff_type get_core_type(void)
{
	return (get_soc_cpu_type() == CPUID_CORE_TYPE_INTEL_CORE) ?
		CPU_TYPE_PERF : CPU_TYPE_EFF;
}

void set_dev_core_type(void)
{
	struct cpu_info *info = cpu_info();
	info->cpu->path.apic.core_type = get_core_type();
}

static void acpi_get_cpu_nomi_perf(u16 *eff_core_nom_perf, u16 *perf_core_nom_perf)
{
	u8 max_non_turbo_ratio = cpu_get_max_non_turbo_ratio();

	_Static_assert(CONFIG_SOC_INTEL_PERFORMANCE_CORE_SCALE_FACTOR != 0,
		       "CONFIG_SOC_INTEL_PERFORMANCE_CORE_SCALE_FACTOR must not be zero");

	_Static_assert(CONFIG_SOC_INTEL_EFFICIENT_CORE_SCALE_FACTOR != 0,
		       "CONFIG_SOC_INTEL_EFFICIENT_CORE_SCALE_FACTOR must not be zero");

	*perf_core_nom_perf = (u16)((max_non_turbo_ratio *
				CONFIG_SOC_INTEL_PERFORMANCE_CORE_SCALE_FACTOR) / 100);

	*eff_core_nom_perf = (u16)((max_non_turbo_ratio *
				CONFIG_SOC_INTEL_EFFICIENT_CORE_SCALE_FACTOR) / 100);
}

static u16 acpi_get_cpu_nominal_freq(void)
{
	return cpu_get_max_non_turbo_ratio() * cpu_get_bus_frequency();
}

/* Updates Nominal Frequency and Nominal Performance */
static void acpigen_cppc_update_nominal_freq_perf(const char *pkg_path, s32 core_id)
{
	u16 eff_core_nom_perf, perf_core_nom_perf;

	if (!soc_is_nominal_freq_supported())
		return;

	acpi_get_cpu_nomi_perf(&eff_core_nom_perf, &perf_core_nom_perf);

	if (core_id < cpu_apic_info.perf_cpu_cnt)
		acpigen_set_package_element_int(pkg_path, CPPC_NOM_PERF_IDX, perf_core_nom_perf);
	else
		acpigen_set_package_element_int(pkg_path, CPPC_NOM_PERF_IDX,
						eff_core_nom_perf);

	/* Update CPU's nominal frequency */
	acpigen_set_package_element_int(pkg_path, CPPC_NOM_FREQ_IDX,
					acpi_get_cpu_nominal_freq());
}

void acpigen_write_CPPC_hybrid_method(s32 core_id)
{
	char pkg_path[16];

	if (core_id == 0)
		snprintf(pkg_path, sizeof(pkg_path), CPPC_PACKAGE_NAME, 0);
	else
		snprintf(pkg_path, sizeof(pkg_path),
			 "\\_SB." CONFIG_ACPI_CPU_STRING "." CPPC_PACKAGE_NAME, 0);

	acpigen_write_method("_CPC", 0);

	/* Update nominal performance and nominal frequency */
	acpigen_cppc_update_nominal_freq_perf(pkg_path, core_id);
	acpigen_emit_byte(RETURN_OP);
	acpigen_emit_namestring(pkg_path);
	acpigen_pop_len();
}

BOOT_STATE_INIT_ENTRY(BS_DEV_INIT_CHIPS, BS_ON_EXIT, acpi_set_hybrid_cpu_apicid_order, NULL);
