// SPDX-License-Identifier: GPL-2.0+
/*
 * Handles writing the declared ACPI tables
 *
 * Copyright 2021 Google LLC
 */

#define LOG_CATEGORY LOGC_ACPI

#include <common.h>
#include <log.h>
#include <malloc.h>
#include <mapmem.h>
#include <acpi/acpi_table.h>
#include <asm/global_data.h>
#include <dm/acpi.h>

DECLARE_GLOBAL_DATA_PTR;

int acpi_write_one(struct acpi_ctx *ctx, const struct acpi_writer *entry)
{
	int ret;

	log_debug("%s: writing table '%s'\n", entry->name,
		  entry->table);
	ctx->tab_start = ctx->current;
	ret = entry->h_write(ctx, entry);
	if (ret == -ENOENT) {
		log_debug("%s: Omitted due to being empty\n",
			  entry->name);
		ret = 0;
		ctx->current = ctx->tab_start;	/* drop the table */
		return ret;
	}
	if (ret)
		return log_msg_ret("write", ret);

	if (entry->flags & ACPIWF_ALIGN64)
		acpi_align64(ctx);
	else
		acpi_align(ctx);

	/* Add the item to the internal list */
	ret = acpi_add_other_item(ctx, entry, ctx->tab_start);
	if (ret)
		return log_msg_ret("add", ret);

	return 0;
}

#ifndef CONFIG_QEMU
static int acpi_write_all(struct acpi_ctx *ctx)
{
	const struct acpi_writer *writer =
		 ll_entry_start(struct acpi_writer, acpi_writer);
	const int n_ents = ll_entry_count(struct acpi_writer, acpi_writer);
	const struct acpi_writer *entry;
	int ret;

	for (entry = writer; entry != writer + n_ents; entry++) {
		ret = acpi_write_one(ctx, entry);
		if (ret && ret != -ENOENT)
			return log_msg_ret("one", ret);
	}

	return 0;
}

/*
 * QEMU's version of write_acpi_tables is defined in drivers/misc/qfw.c
 */
ulong write_acpi_tables(ulong start_addr)
{
	struct acpi_ctx *ctx;
	ulong addr;
	int ret;

	ctx = malloc(sizeof(*ctx));
	if (!ctx)
		return log_msg_ret("mem", -ENOMEM);

	log_debug("ACPI: Writing ACPI tables at %lx\n", start_addr);

	acpi_reset_items();
	acpi_setup_ctx(ctx, start_addr);

	ret = acpi_write_all(ctx);
	if (ret) {
		log_err("Failed to write ACPI tables (err=%d)\n", ret);
		return log_msg_ret("write", -ENOMEM);
	}

	addr = map_to_sysmem(ctx->current);
	log_debug("ACPI current = %lx\n", addr);

	return addr;
}

int write_dev_tables(struct acpi_ctx *ctx, const struct acpi_writer *entry)
{
	int ret;

	ret = acpi_write_dev_tables(ctx);
	if (ret)
		return log_msg_ret("write", ret);

	return 0;
}
ACPI_WRITER(8dev, NULL, write_dev_tables, 0);

ulong acpi_get_rsdp_addr(void)
{
	if (!gd->acpi_ctx)
		return 0;

	return map_to_sysmem(gd->acpi_ctx->rsdp);
}
#endif /* QEMU */

void acpi_setup_ctx(struct acpi_ctx *ctx, ulong start)
{
	gd->acpi_ctx = ctx;
	memset(ctx, '\0', sizeof(*ctx));

	/* Align ACPI tables to 16-byte boundary */
	start = ALIGN(start, 16);
	ctx->base = map_sysmem(start, 0);
	ctx->current = ctx->base;

	gd_set_acpi_start(start);
}
