// SPDX-License-Identifier: GPL-2.0+
/*
 * Generic WiFi ACPI info
 *
 * Copyright 2019 Google LLC
 * Modified from coreboot src/drivers/wifi/generic.c
 */

#include <log.h>
#include <acpi/acpigen.h>
#include <acpi/acpi_device.h>
#include <dm.h>
#include <dm/acpi.h>

/* WRDS Spec Revision */
#define WRDS_REVISION 0x0

/* EWRD Spec Revision */
#define EWRD_REVISION 0x0

/* WRDS Domain type */
#define WRDS_DOMAIN_TYPE_WIFI 0x7

/* EWRD Domain type */
#define EWRD_DOMAIN_TYPE_WIFI 0x7

/* WGDS Domain type */
#define WGDS_DOMAIN_TYPE_WIFI 0x7

/*
 * WIFI ACPI NAME = "WF" + hex value of last 8 bits of dev_path_encode + '\0'
 * The above representation returns unique and consistent name every time
 * generate_wifi_acpi_name is invoked. The last 8 bits of dev_path_encode is
 * chosen since it contains the bus address of the device.
 */
#define WIFI_ACPI_NAME_MAX_LEN 5

/**
 * struct generic_wifi_config - Data structure to contain common wifi config
 * @wake: Wake pin for ACPI _PRW
 * @maxsleep: Maximum sleep state to wake from
 */
struct generic_wifi_config {
	unsigned int wake;
	unsigned int maxsleep;
};

static int generic_wifi_fill_ssdt(struct acpi_ctx *ctx,
				  const struct udevice *dev,
				  const struct generic_wifi_config *config)
{
	char name[ACPI_NAME_MAX];
	char path[ACPI_PATH_MAX];
	pci_dev_t bdf;
	u32 address;
	int ret;

	ret = acpi_device_path(dev_get_parent(dev), path, sizeof(path));
	if (ret)
		return log_msg_ret("path", ret);
	ret = acpi_get_name(dev, name);
	if (ret)
		return log_msg_ret("name", ret);

	/* Device */
	acpigen_write_scope(ctx, path);
	acpigen_write_device(ctx, name);
	acpigen_write_name_integer(ctx, "_UID", 0);
	acpigen_write_name_string(ctx, "_DDN",
				  dev_read_string(dev, "acpi,ddn"));

	/* Address */
	bdf = dm_pci_get_bdf(dev);
	address = (PCI_DEV(bdf) << 16) | PCI_FUNC(bdf);
	acpigen_write_name_dword(ctx, "_ADR", address);

	/* Wake capabilities */
	if (config)
		acpigen_write_prw(ctx, config->wake, config->maxsleep);

	acpigen_pop_len(ctx); /* Device */
	acpigen_pop_len(ctx); /* Scope */

	return 0;
}

static int intel_wifi_acpi_fill_ssdt(const struct udevice *dev,
				     struct acpi_ctx *ctx)
{
	struct generic_wifi_config config;
	bool have_config;
	int ret;

	ret = dev_read_u32(dev, "acpi,wake", &config.wake);
	have_config = !ret;
	/* By default, all intel wifi chips wake from S3 */
	config.maxsleep = 3;
	ret = generic_wifi_fill_ssdt(ctx, dev, have_config ? &config : NULL);
	if (ret)
		return log_msg_ret("wifi", ret);

	return 0;
}

struct acpi_ops wifi_acpi_ops = {
	.fill_ssdt	= intel_wifi_acpi_fill_ssdt,
};

static const struct udevice_id intel_wifi_ids[] = {
	{ .compatible = "intel,generic-wifi" },
	{ }
};

U_BOOT_DRIVER(intel_wifi) = {
	.name		= "intel_wifi",
	.id		= UCLASS_MISC,
	.of_match	= intel_wifi_ids,
	ACPI_OPS_PTR(&wifi_acpi_ops)
};
