// SPDX-License-Identifier: GPL-2.0
/*
 * Generation of tables for particular device types
 *
 * Copyright 2019 Google LLC
 * Mostly taken from coreboot file acpi_device.c
 */

#include <common.h>
#include <dm.h>
#include <log.h>
#include <malloc.h>
#include <uuid.h>
#include <acpi/acpigen.h>
#include <acpi/acpi_dp.h>
#include <dm/acpi.h>

static void acpi_dp_write_array(struct acpi_ctx *ctx,
				const struct acpi_dp *array);

static void acpi_dp_write_value(struct acpi_ctx *ctx,
				const struct acpi_dp *prop)
{
	switch (prop->type) {
	case ACPI_DP_TYPE_INTEGER:
		acpigen_write_integer(ctx, prop->integer);
		break;
	case ACPI_DP_TYPE_STRING:
	case ACPI_DP_TYPE_CHILD:
		acpigen_write_string(ctx, prop->string);
		break;
	case ACPI_DP_TYPE_REFERENCE:
		acpigen_emit_namestring(ctx, prop->string);
		break;
	case ACPI_DP_TYPE_ARRAY:
		acpi_dp_write_array(ctx, prop->array);
		break;
	default:
		break;
	}
}

/* Package (2) { "prop->name", VALUE } */
static void acpi_dp_write_property(struct acpi_ctx *ctx,
				   const struct acpi_dp *prop)
{
	acpigen_write_package(ctx, 2);
	acpigen_write_string(ctx, prop->name);
	acpi_dp_write_value(ctx, prop);
	acpigen_pop_len(ctx);
}

/* Write array of Device Properties */
static void acpi_dp_write_array(struct acpi_ctx *ctx,
				const struct acpi_dp *array)
{
	const struct acpi_dp *dp;
	char *pkg_count;

	/* Package element count determined as it is populated */
	pkg_count = acpigen_write_package(ctx, 0);

	/*
	 * Only acpi_dp of type DP_TYPE_TABLE is allowed to be an array.
	 * DP_TYPE_TABLE does not have a value to be written. Thus, start
	 * the loop from next type in the array.
	 */
	for (dp = array->next; dp; dp = dp->next) {
		acpi_dp_write_value(ctx, dp);
		(*pkg_count)++;
	}

	acpigen_pop_len(ctx);
}

static void acpi_dp_free(struct acpi_dp *dp)
{
	assert(dp);
	while (dp) {
		struct acpi_dp *p = dp->next;

		switch (dp->type) {
		case ACPI_DP_TYPE_CHILD:
			acpi_dp_free(dp->child);
			break;
		case ACPI_DP_TYPE_ARRAY:
			acpi_dp_free(dp->array);
			break;
		default:
			break;
		}

		free(dp);
		dp = p;
	}
}

static int acpi_dp_write_internal(struct acpi_ctx *ctx, struct acpi_dp *table)
{
	struct acpi_dp *dp, *prop;
	char *dp_count, *prop_count = NULL;
	int child_count = 0;
	int ret;

	assert(table);
	if (table->type != ACPI_DP_TYPE_TABLE)
		return 0;

	/* Name (name) */
	acpigen_write_name(ctx, table->name);

	/* Device Property list starts with the next entry */
	prop = table->next;

	/* Package (DP), default to assuming no properties or children */
	dp_count = acpigen_write_package(ctx, 0);

	/* Print base properties */
	for (dp = prop; dp; dp = dp->next) {
		if (dp->type == ACPI_DP_TYPE_CHILD) {
			child_count++;
		} else {
			/*
			 * The UUID and package is only added when
			 * we come across the first property.  This
			 * is to avoid creating a zero-length package
			 * in situations where there are only children.
			 */
			if (!prop_count) {
				*dp_count += 2;
				/* ToUUID (ACPI_DP_UUID) */
				ret = acpigen_write_uuid(ctx, ACPI_DP_UUID);
				if (ret)
					return log_msg_ret("touuid", ret);
				/*
				 * Package (PROP), element count determined as
				 * it is populated
				 */
				prop_count = acpigen_write_package(ctx, 0);
			}
			(*prop_count)++;
			acpi_dp_write_property(ctx, dp);
		}
	}

	if (prop_count) {
		/* Package (PROP) length, if a package was written */
		acpigen_pop_len(ctx);
	}

	if (child_count) {
		/* Update DP package count to 2 or 4 */
		*dp_count += 2;
		/* ToUUID (ACPI_DP_CHILD_UUID) */
		ret = acpigen_write_uuid(ctx, ACPI_DP_CHILD_UUID);
		if (ret)
			return log_msg_ret("child uuid", ret);

		/* Print child pointer properties */
		acpigen_write_package(ctx, child_count);

		for (dp = prop; dp; dp = dp->next)
			if (dp->type == ACPI_DP_TYPE_CHILD)
				acpi_dp_write_property(ctx, dp);
		/* Package (CHILD) length */
		acpigen_pop_len(ctx);
	}

	/* Package (DP) length */
	acpigen_pop_len(ctx);

	/* Recursively parse children into separate tables */
	for (dp = prop; dp; dp = dp->next) {
		if (dp->type == ACPI_DP_TYPE_CHILD) {
			ret = acpi_dp_write_internal(ctx, dp->child);
			if (ret)
				return log_msg_ret("dp child", ret);
		}
	}

	return 0;
}

int acpi_dp_write(struct acpi_ctx *ctx, struct acpi_dp *table)
{
	int ret;

	ret = acpi_dp_write_internal(ctx, table);

	/* Clean up */
	acpi_dp_free(table);

	if (ret)
		return log_msg_ret("write", ret);

	return 0;
}

static struct acpi_dp *acpi_dp_new(struct acpi_dp *dp, enum acpi_dp_type type,
				   const char *name)
{
	struct acpi_dp *new;

	new = malloc(sizeof(struct acpi_dp));
	if (!new)
		return NULL;

	memset(new, '\0', sizeof(*new));
	new->type = type;
	new->name = name;

	if (dp) {
		/* Add to end of property list */
		while (dp->next)
			dp = dp->next;
		dp->next = new;
	}

	return new;
}

struct acpi_dp *acpi_dp_new_table(const char *name)
{
	return acpi_dp_new(NULL, ACPI_DP_TYPE_TABLE, name);
}

struct acpi_dp *acpi_dp_add_integer(struct acpi_dp *dp, const char *name,
				    u64 value)
{
	struct acpi_dp *new;

	assert(dp);
	new = acpi_dp_new(dp, ACPI_DP_TYPE_INTEGER, name);

	if (new)
		new->integer = value;

	return new;
}

struct acpi_dp *acpi_dp_add_string(struct acpi_dp *dp, const char *name,
				   const char *string)
{
	struct acpi_dp *new;

	assert(dp);
	new = acpi_dp_new(dp, ACPI_DP_TYPE_STRING, name);
	if (new)
		new->string = string;

	return new;
}

struct acpi_dp *acpi_dp_add_reference(struct acpi_dp *dp, const char *name,
				      const char *reference)
{
	struct acpi_dp *new;

	assert(dp);
	new = acpi_dp_new(dp, ACPI_DP_TYPE_REFERENCE, name);
	if (new)
		new->string = reference;

	return new;
}

struct acpi_dp *acpi_dp_add_child(struct acpi_dp *dp, const char *name,
				  struct acpi_dp *child)
{
	struct acpi_dp *new;

	assert(dp);
	if (child->type != ACPI_DP_TYPE_TABLE)
		return NULL;

	new = acpi_dp_new(dp, ACPI_DP_TYPE_CHILD, name);
	if (new) {
		new->child = child;
		new->string = child->name;
	}

	return new;
}

struct acpi_dp *acpi_dp_add_array(struct acpi_dp *dp, struct acpi_dp *array)
{
	struct acpi_dp *new;

	assert(dp);
	assert(array);
	if (array->type != ACPI_DP_TYPE_TABLE)
		return NULL;

	new = acpi_dp_new(dp, ACPI_DP_TYPE_ARRAY, array->name);
	if (new)
		new->array = array;

	return new;
}

struct acpi_dp *acpi_dp_add_integer_array(struct acpi_dp *dp, const char *name,
					  u64 *array, int len)
{
	struct acpi_dp *dp_array;
	int i;

	assert(dp);
	if (len <= 0)
		return NULL;

	dp_array = acpi_dp_new_table(name);
	if (!dp_array)
		return NULL;

	for (i = 0; i < len; i++)
		if (!acpi_dp_add_integer(dp_array, NULL, array[i]))
			break;

	if (!acpi_dp_add_array(dp, dp_array))
		return NULL;

	return dp_array;
}

struct acpi_dp *acpi_dp_add_gpio(struct acpi_dp *dp, const char *name,
				 const char *ref, int index, int pin,
				 enum acpi_gpio_polarity polarity)
{
	struct acpi_dp *gpio;

	assert(dp);
	gpio = acpi_dp_new_table(name);
	if (!gpio)
		return NULL;

	if (!acpi_dp_add_reference(gpio, NULL, ref) ||
	    !acpi_dp_add_integer(gpio, NULL, index) ||
	    !acpi_dp_add_integer(gpio, NULL, pin) ||
	    !acpi_dp_add_integer(gpio, NULL, polarity == ACPI_GPIO_ACTIVE_LOW))
		return NULL;

	if (!acpi_dp_add_array(dp, gpio))
		return NULL;

	return gpio;
}

int acpi_dp_ofnode_copy_int(ofnode node, struct acpi_dp *dp, const char *prop)
{
	int ret;
	u32 val = 0;

	ret = ofnode_read_u32(node, prop, &val);
	if (ret)
		return ret;
	if (!acpi_dp_add_integer(dp, prop, val))
		return log_ret(-ENOMEM);

	return 0;
}

int acpi_dp_ofnode_copy_str(ofnode node, struct acpi_dp *dp, const char *prop)
{
	const char *val;

	val = ofnode_read_string(node, prop);
	if (!val)
		return -EINVAL;
	if (!acpi_dp_add_string(dp, prop, val))
		return log_ret(-ENOMEM);

	return 0;
}

int acpi_dp_dev_copy_int(const struct udevice *dev, struct acpi_dp *dp,
			 const char *prop)
{
	int ret;
	u32 val = 0;

	ret = dev_read_u32(dev, prop, &val);
	if (ret)
		return ret;
	if (!acpi_dp_add_integer(dp, prop, val))
		return log_ret(-ENOMEM);

	return ret;
}

int acpi_dp_dev_copy_str(const struct udevice *dev, struct acpi_dp *dp,
			 const char *prop)
{
	const char *val;

	val = dev_read_string(dev, prop);
	if (!val)
		return -EINVAL;
	if (!acpi_dp_add_string(dp, prop, val))
		return log_ret(-ENOMEM);

	return 0;
}
