/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <acpi/acpigen.h>
#include <acpi/acpi_device.h>
#include <gpio.h>
#include <stdio.h>
#include "chip.h"
#include "soc/intel/common/block/include/intelblocks/acpi.h"
#include "soc/intel/common/block/pcie/rtd3/chip.h"

/* FCPO# to RESET# delay time during WWAN ON */
#define FM350GL_TN2B 20
/* RESET# to PERST# delay time during WWAN ON */
#define FM350GL_TB2R 80
/* The delay between de-assertion of PERST# to change of PDS state from 0 to 1 during WWAN ON */
#define FM350GL_TR2P 0
/* RESET# to FCPO# delay time during WWAN OFF */
#define FM350GL_TB2F 10
/* Time to allow the WWAN module to fully discharge any residual voltages before FCPO# could be
   de-asserted again. */
#define FM350GL_TFDI 500
/* The delay between assertion and de-assertion RESET# during FLDR */
#define FM350GL_TBTG 10
/* The delay between de-assertion of RESET# and change of PDS state from 0 to 1 after FLDR */
#define FM350GL_TBTP 170
/* PERST# to RESET# delay time during WWAN OFF */
#define FM350GL_TR2B 10
/* 20s HW initialization needed after de-assertion of PERST#
   However, it is not required and is not proper place to ensure HW initialization in ACPI. The
   delay here is to ensure the following reset or RTD3 _OFF method won't be called immediately.
 */
#define FM350GL_TIME_HW_INIT 100

enum reset_type {
	RESET_TYPE_WARM = 0,
	RESET_TYPE_COLD = 1
};

/*
 *  Returns the RTD3 PM methods requested and available to the device.
 */
static enum acpi_pcie_rp_pm_emit
wwan_fm350gl_get_rtd3_method_support(const struct drivers_wwan_fm_config *config)
{
	const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;

	rtd3_config = config_of(config->rtd3dev);

	return rtd3_config->ext_pm_support;
}

/*
 *  Generate first half reset flow (FHRF) method.
 *  Arg0 = 0; RESET_TYPE_WARM: warm reset
 *  Arg0 = 1; RESET_TYPE_COLD: cold reset
 */
static void wwan_fm350gl_acpi_method_fhrf(const struct device *parent_dev,
	const struct drivers_wwan_fm_config *config)
{
	acpigen_write_method_serialized("FHRF", 1);
	{
		char mutex_path[128];
		const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;

		rtd3_config = config_of(config->rtd3dev);
		if (rtd3_config->use_rp_mutex) {
			snprintf(mutex_path, sizeof(mutex_path), "%s",
				acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
			/* Acquire root port mutex in case FHRF is called directly and not called from _RST */
			acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
		}

		/* LOCAL0 = PERST# */
		acpigen_get_tx_gpio(&config->perst_gpio);
		acpigen_write_if_lequal_op_int(LOCAL0_OP, 0);
		{
			if (wwan_fm350gl_get_rtd3_method_support(config) &
				ACPI_PCIE_RP_EMIT_L23) {
				acpigen_emit_namestring(acpi_device_path_join(parent_dev,
					"DL23"));
			}
			/* assert PERST# pin */
			acpigen_enable_tx_gpio(&config->perst_gpio);
		}
		acpigen_write_if_end(); /* If */
		acpigen_write_sleep(FM350GL_TR2B);
		/* assert RESET# pin */
		acpigen_enable_tx_gpio(&config->reset_gpio);
		/* warm reset */
		acpigen_write_if_lequal_op_int(ARG0_OP, RESET_TYPE_WARM);
		{
			acpigen_write_sleep(FM350GL_TBTG);
		}
		/* cold reset */
		acpigen_write_else();
		{
			acpigen_write_if_lequal_op_int(ARG0_OP, RESET_TYPE_COLD);
			{
				/* disable source clock */
				if (wwan_fm350gl_get_rtd3_method_support(config) &
					ACPI_PCIE_RP_EMIT_SRCK) {
					acpigen_emit_namestring(acpi_device_path_join(
						parent_dev, "SRCK"));
					acpigen_emit_byte(ZERO_OP);
				}
				acpigen_write_sleep(FM350GL_TB2F);
				/* assert FCPO# pin */
				acpigen_enable_tx_gpio(&config->fcpo_gpio);
				acpigen_write_sleep(FM350GL_TFDI);
			}
			acpigen_write_if_end(); /* If */
		}
		acpigen_pop_len(); /* Else */

		if (rtd3_config->use_rp_mutex)
			acpigen_write_release(mutex_path);
	}
	acpigen_write_method_end(); /* Method */
}

/*
 *  Generate second half reset flow (SHRF) method.
 */
static void wwan_fm350gl_acpi_method_shrf(const struct device *parent_dev,
		const struct drivers_wwan_fm_config *config)
{
	acpigen_write_method_serialized("SHRF", 0);
	{
		char mutex_path[128];
		const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;

		rtd3_config = config_of(config->rtd3dev);
		if (rtd3_config->use_rp_mutex) {
			snprintf(mutex_path, sizeof(mutex_path), "%s",
				acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
			/* Acquire root port mutex */
			acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
		}

		/* call rtd3 method to Disable ModPHY Power Gating. */
		if (wwan_fm350gl_get_rtd3_method_support(config) &
			ACPI_PCIE_RP_EMIT_PSD0) {
			acpigen_emit_namestring(acpi_device_path_join(parent_dev,
				"PSD0"));
		}
		/* call rtd3 method to Enable SRC Clock. */
		if (wwan_fm350gl_get_rtd3_method_support(config) &
			ACPI_PCIE_RP_EMIT_SRCK) {
			acpigen_emit_namestring(acpi_device_path_join(parent_dev,
				"SRCK"));
			acpigen_emit_byte(ONE_OP);
		}
		/* De-assert FCPO# GPIO. */
		acpigen_disable_tx_gpio(&config->fcpo_gpio);
		acpigen_write_sleep(FM350GL_TN2B);
		/* De-assert RESET# GPIO. */
		acpigen_disable_tx_gpio(&config->reset_gpio);
		acpigen_write_sleep(FM350GL_TB2R);
		/* De-assert PERST# GPIO. */
		acpigen_disable_tx_gpio(&config->perst_gpio);
		/* Call rtd3 method to trigger L2/L3 ready exit flow in root port */
		if (wwan_fm350gl_get_rtd3_method_support(config) &
			ACPI_PCIE_RP_EMIT_L23) {
			acpigen_emit_namestring(acpi_device_path_join(parent_dev,
				"L23D"));
		}
		acpigen_write_sleep(FM350GL_TIME_HW_INIT);

		if (rtd3_config->use_rp_mutex)
			acpigen_write_release(mutex_path);
	}
	acpigen_write_method_end(); /* Method */
}

/*
 * Generate _RST method. This is to perform a soft reset. It is added under
 * PXSX. This is called during device driver removal.
 */
static void wwan_fm350gl_acpi_method_rst(const struct device *parent_dev,
			 const struct drivers_wwan_fm_config *config)
{
	acpigen_write_method_serialized("_RST", 0);
	{
		char mutex_path[128];
		const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;

		rtd3_config = config_of(config->rtd3dev);
		if (rtd3_config->use_rp_mutex) {
			snprintf(mutex_path, sizeof(mutex_path), "%s",
				acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
			/* Acquire root port mutex */
			acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
		}

		/* Perform 1st Half of FLDR Flow for soft reset: FHRF(0) */
		acpigen_emit_namestring("FHRF");
		acpigen_emit_byte(RESET_TYPE_WARM);
		/* Perform 2nd Half of FLDR Flow: SHRF() */
		acpigen_emit_namestring("SHRF");
		/* Indicates that the following _Off will be skipped. */
		acpigen_emit_byte(INCREMENT_OP);
		acpigen_emit_namestring(acpi_device_path_join(parent_dev, "RTD3.OFSK"));

		if (rtd3_config->use_rp_mutex)
			acpigen_write_release(mutex_path);
	}
	acpigen_write_method_end(); /* Method */
}

/*
 * Generate _RST method. This is to perform a cold reset. This reset will be
 * included under PXSX.MRST. This method is used during device firmware update.
 */
static void wwan_fm350gl_acpi_method_mrst_rst(const struct device *parent_dev,
			 const struct drivers_wwan_fm_config *config)
{
	acpigen_write_method_serialized("_RST", 0);
	{
		char mutex_path[128];
		const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;

		rtd3_config = config_of(config->rtd3dev);
		if (rtd3_config->use_rp_mutex) {
			snprintf(mutex_path, sizeof(mutex_path), "%s",
				acpi_device_path_join(parent_dev, RP_MUTEX_NAME));
			/* Acquire root port mutex */
			acpigen_write_acquire(mutex_path, ACPI_MUTEX_NO_TIMEOUT);
		}

		/* Perform 1st Half of FLDR Flow for cold reset: FHRF (1) */
		acpigen_emit_namestring("FHRF");
		acpigen_emit_byte(RESET_TYPE_COLD);
		/* Perform 2nd Half of FLDR Flow: SHRF () */
		acpigen_emit_namestring("SHRF");
		/* Indicate kernel ACPI PM to skip _off RTD3 after reset at the end of
		   driver removal */
		acpigen_emit_byte(INCREMENT_OP);
		acpigen_emit_namestring(acpi_device_path_join(parent_dev, "RTD3.OFSK"));

		if (rtd3_config->use_rp_mutex)
			acpigen_write_release(mutex_path);
	}
	acpigen_write_method_end(); /* Method */
}

/*
 * Generate DPTS (Device Prepare To Seep) Method. This is called in
 *  \.SB.MPTS Method.
 */
static void wwan_fm350gl_acpi_method_dpts(const struct device *parent_dev,
			 const struct drivers_wwan_fm_config *config)
{
	acpigen_write_method_serialized("DPTS", 1);
	{
		/* Perform 1st Half of FLDR Flow for cold reset: FHRF (1) */
		acpigen_emit_namestring("FHRF");
		acpigen_emit_byte(RESET_TYPE_COLD);
	}
	acpigen_write_method_end(); /* Method */
}

static const char *wwan_fm350gl_acpi_name(const struct device *dev)
{
	/* Attached device name must be "PXSX" for the Linux Kernel to recognize it. */
	return "PXSX";
}

static void
wwan_fm350gl_acpi_event_interrupts(const struct acpi_gpio *wake_gpio)
{
	acpigen_write_name("_AEI");
	acpigen_write_resourcetemplate_header();
	acpi_device_write_gpio(wake_gpio);
	acpigen_write_resourcetemplate_footer();
}

static void
wwan_fm350gl_acpi_event_method(const struct device *dev,
			       const struct acpi_gpio *wake_gpio)
{
	char name[5];
	uint16_t pin;

	pin = wake_gpio->pins[0];
	if (CONFIG(GENERIC_GPIO_LIB))
		pin = gpio_acpi_pin(pin);

	if (pin > 0xff) {
		printk(BIOS_ERR, "%s: pins above 0xFF are unsupported (pin %u)\n",
		       __func__, pin);
		return;
	}

	snprintf(name, sizeof(name), "_%c%02X",
		 wake_gpio->irq.mode == ACPI_IRQ_EDGE_TRIGGERED ? 'E' : 'L', pin);

	acpigen_write_method_serialized(name, 0);
	acpigen_notify(acpi_device_path(dev), 0x02); /* NOTIFY_DEVICE_WAKE */
	acpigen_write_method_end();
}

static void wwan_fm350gl_acpi_gpio_events(const struct device *dev)
{
	const struct drivers_wwan_fm_config *config = config_of(dev);
	const struct acpi_gpio *wake_gpio = &config->wake_gpio;

	/* Write into GPIO controller's scope */
	if (CONFIG(GENERIC_GPIO_LIB))
		acpigen_write_scope(wake_gpio->resource ? : gpio_acpi_path(wake_gpio->pins[0]));
	else
		acpigen_write_scope(wake_gpio->resource);
	wwan_fm350gl_acpi_event_interrupts(wake_gpio);
	wwan_fm350gl_acpi_event_method(dev, wake_gpio);
	acpigen_write_scope_end();
}

static void wwan_fm350gl_acpi_fill_ssdt(const struct device *dev)
{
	const struct drivers_wwan_fm_config *config = config_of(dev);
	const struct device *parent = dev->upstream->dev;
	const char *scope = acpi_device_path(parent);
	const struct soc_intel_common_block_pcie_rtd3_config *rtd3_config;

	if (!is_dev_enabled(parent)) {
		printk(BIOS_ERR, "%s: root port not enabled\n", __func__);
		return;
	}
	if (!scope) {
		printk(BIOS_ERR, "%s: root port scope not found\n", __func__);
		return;
	}
	if (!config->fcpo_gpio.pin_count && !config->reset_gpio.pin_count &&
		!config->perst_gpio.pin_count) {
		printk(BIOS_ERR, "%s: FCPO, RESET, PERST GPIO required for %s.\n",
			 __func__, scope);
		return;
	}

	rtd3_config = config_of(config->rtd3dev);
	if (!rtd3_config->use_rp_mutex)
		printk(BIOS_WARNING, "%s: RTD3 must use root port mutex.\n",
			__func__);

	printk(BIOS_INFO, "%s: Enable WWAN for %s (%s)\n", scope, dev_path(parent),
		config->desc ?: dev->chip_ops->name);
	acpigen_write_scope(scope);
	{
		acpigen_write_device(wwan_fm350gl_acpi_name(dev));
		{
			acpigen_write_ADR(0);
			if (config->name)
				acpigen_write_name_string("_DDN", config->name);
			if (config->desc)
				acpigen_write_name_unicode("_STR", config->desc);
			wwan_fm350gl_acpi_method_fhrf(parent, config);
			wwan_fm350gl_acpi_method_shrf(parent, config);
			wwan_fm350gl_acpi_method_rst(parent, config);
			wwan_fm350gl_acpi_method_dpts(parent, config);

			if (config->add_acpi_dma_property)
				acpi_device_add_dma_property(NULL);

			/* NOTE: the 5G driver will call MRST._RST to trigger a cold reset
			 * during firmware update.
			 */
			acpigen_write_device("MRST");
			{
				acpigen_write_ADR(0);
				wwan_fm350gl_acpi_method_mrst_rst(parent, config);
			}

			acpigen_write_device_end(); /* Device */
		}
		acpigen_write_device_end(); /* Device */
	}
	acpigen_write_scope_end(); /* Scope */

	if (config->wake_gpio.pin_count && config->wake_gpio.type == ACPI_GPIO_TYPE_INTERRUPT)
		wwan_fm350gl_acpi_gpio_events(dev);
}

static struct device_operations wwan_fm350gl_ops = {
	.read_resources  = noop_read_resources,
	.set_resources   = noop_set_resources,
	.acpi_fill_ssdt  = wwan_fm350gl_acpi_fill_ssdt,
	.acpi_name       = wwan_fm350gl_acpi_name
};

static void wwan_fm350gl_acpi_enable(struct device *dev)
{
	dev->ops = &wwan_fm350gl_ops;
}

struct chip_operations drivers_wwan_fm_ops = {
	 .name = "Fibocom FM-350-GL",
	.enable_dev = wwan_fm350gl_acpi_enable
};