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

#include <device/pci_ids.h>

External (\_SB.PCI0.PMC.IPCS, MethodObj)

/* Voltage rail control signals */

#define GPIO_1V8_PWR_EN		GPP_E11

#define GPIO_NV33_PWR_EN	GPP_E1
#define GPIO_NV33_PG		GPP_E2

#define GPIO_1V8_PG		GPP_E20
#define GPIO_NV12_PWR_EN	GPP_D0
#define GPIO_NV12_PG		GPP_D1

#define GPIO_NVVDD_PWR_EN	GPP_E0
#define GPIO_PEXVDD_PWR_EN	GPP_E10
#define GPIO_PEXVDD_PG		GPP_E17
#define GPIO_FBVDD_PWR_EN	GPP_A19
#define GPIO_FBVDD_PG		GPP_E4

#define GPIO_GPU_PERST_L	GPP_B3
#define GPIO_GPU_ALLRAILS_PG	GPP_E5
#define GPIO_GPU_NVVDD_EN	GPP_A17

#define GC6_DEFER_TYPE_EXIT_GC6	3

/* 250ms in "Timer" units (i.e. 100ns increments) */
#define MIN_OFF_TIME_TIMERS	2500000

#define SRCCLK_DISABLE		0
#define SRCCLK_ENABLE		1

#define GPU_POWER_STATE_OFF	0
#define GPU_POWER_STATE_ON	1

/* Dynamically-assigned NVVDD PG GPIO, set in _INI in SSDT */
Name (NVPG, 0)
Name (GPEN, 0)

/* Optimus Power Control State */
Name (OPCS, OPTIMUS_POWER_CONTROL_DISABLE)

/* PCI configuration space Owner */
Name (PCIO, PCI_OWNER_DRIVER)

/* Saved PCI configuration space memory (VGA Buffer) */
Name (VGAB, Buffer (0xfb) { 0x00 })

/* Deferred GPU State */
Name (OPS0, OPTIMUS_CONTROL_NO_RUN_PS0)

/* GC6 Entry/Exit state */
Name (GC6E, GC6_STATE_EXITED)

/* Power State, GCOFF, GCON */
Name (GPPS, GPU_POWER_STATE_ON)

/* Defer GC6 entry / exit until D3-cold request */
Name (DFEN, 0)
/* Deferred GC6 Enter control */
Name (DFCI, 0)
/* Deferred GC6 Exit control */
Name (DFCO, 0)
/* GCOFF Timer */
Name (GCOT, 0)

#define PMC_SRCCLK_PIN	0x1
#define PMC_SRCCLK_ENABLE	0x1
#define PMC_SRCCLK_DISABLE	0x0

#define PMC_RP_IDX		(1 << 27)
#define PMC_RP_ENABLE		(1 << 27)
#define PMC_RP_DISABLE		0x0
/* Copy of LTR enable bit from PEG port */
Name (SLTR, 0)

/* Control the PCIe SRCCLK# for dGPU */
Method (SRCC, 1, Serialized)
{
	If (!Arg0)
	{
		Local0 = PMC_SRCCLK_DISABLE
		Local1 = PMC_RP_DISABLE
	}
	Else
	{
		Local0 = PMC_SRCCLK_ENABLE
		Local1 = PMC_RP_ENABLE
	}

	\_SB.PCI0.PMC.IPCS (0xac, 0, 16, PMC_SRCCLK_PIN,
			    Local0, PMC_RP_IDX, Local1)
}

/* "GC6 In", i.e. GC6 Entry Sequence */
Method (GC6I, 0, Serialized)
{
	GC6E = GC6_STATE_TRANSITION

	/* Save the PEG port's LTR setting */
	SLTR = LREN

	/* Put PCIe link into L2/3 */
	\_SB.PCI0.PEG0.DL23 ()

	/* Wait for GPU to deassert its GPIO4, i.e. GPU_NVVDD_EN */
	GPPL (GPIO_GPU_NVVDD_EN, 0, 20)

	/* Deassert PG_GPU_ALLRAILS */
	CTXS (GPIO_GPU_ALLRAILS_PG)

	/* Ramp down FBVDD - TODO: Remove Agah when board is dropped */
	CTXS (GPIO_FBVDD_PWR_EN)

	/* Ramp down PEXVDD */
	CTXS (GPIO_PEXVDD_PWR_EN)
	Sleep (2)

	/* Deassert EN_PPVAR_GPU_NVVDD */
	CTXS (GPIO_NVVDD_PWR_EN)
	Sleep (2)

	/* Assert GPU_PERST_L */
	CTXS (GPIO_GPU_PERST_L)

	/* Disable PCIe SRCCLK# */
	SRCC (SRCCLK_DISABLE)

	GC6E = GC6_STATE_ENTERED
}

/* "GC6 Out", i.e. GC6 Exit Sequence */
Method (GC6O, 0, Serialized)
{
	GC6E = GC6_STATE_TRANSITION

	/* Re-enable PCIe SRCCLK# */
	SRCC (SRCCLK_ENABLE)

	/* Deassert GPU_PERST_L */
	STXS (GPIO_GPU_PERST_L)

	/* Wait for GPU to assert GPU_NVVDD_EN */
	GPPL (GPIO_GPU_NVVDD_EN, 1, 20)

	/* Ramp up NVVDD */
	STXS (GPIO_NVVDD_PWR_EN)
	GPPL (NVPG, 1, 4)

	/* Ramp up PEXVDD */
	STXS (GPIO_PEXVDD_PWR_EN)
	GPPL (GPIO_PEXVDD_PG, 1, 4)

	/* Ramp up FBVDD - TODO: Remove Agah when board is dropped */
	STXS (GPIO_FBVDD_PWR_EN)


	/* Assert PG_GPU_ALLRAILS */
	STXS (GPIO_GPU_ALLRAILS_PG)

	/* Restore PCIe link back to L0 state */
	\_SB.PCI0.PEG0.LD23 ()

	/* Wait for dGPU to reappear on the bus */
	Local0 = 50
	While (NVID != PCI_VID_NVIDIA)
	{
		Stall (100)
		Local0--
		If (Local0 == 0)
		{
			Break
		}
	}

	/* Restore the PEG LTR enable bit */
	LREN = SLTR

	/* Clear recoverable errors detected bit */
	CEDR = 1

	GC6E = GC6_STATE_EXITED
}

/* GCOFF exit sequence */
Method (PGON, 0, Serialized)
{
	Local0 = Timer - GCOT
	If (Local0 < MIN_OFF_TIME_TIMERS)
	{
		Local1 = (MIN_OFF_TIME_TIMERS - Local0) / 10000
		Printf("Sleeping %o to ensure min GCOFF time", Local1)
		Sleep (Local1)
	}

	/* Assert PERST# */
	CTXS (GPIO_GPU_PERST_L)

	/* Ramp up 1.2V rail on boards with support */
	STXS (GPIO_NV12_PWR_EN)
	GPPL (GPIO_NV12_PG, 1, 5)

	/* Ramp up 1.8V rail */
	STXS (GPEN)
	GPPL (GPIO_1V8_PG, 1, 20)

	/* Ramp up NV33 rail */
	STXS (GPIO_NV33_PWR_EN)
	GPPL (GPIO_NV33_PG, 1, 20)

	/* Ramp up NVVDD rail */
	STXS (GPIO_NVVDD_PWR_EN)
	GPPL (NVPG, 1, 5)

	/* Ramp up PEXVDD rail */
	STXS (GPIO_PEXVDD_PWR_EN)
	GPPL (GPIO_PEXVDD_PG, 1, 5)

	/* Ramp up FBVDD rail */
	STXS (GPIO_FBVDD_PWR_EN)

	GPPL (GPIO_FBVDD_PG, 1, 5)

	/* All rails are good */
	STXS (GPIO_GPU_ALLRAILS_PG)
	Sleep (1)

	/* Deassert PERST# */
	STXS (GPIO_GPU_PERST_L)


	GC6E = GC6_STATE_EXITED
	GPPS = GPU_POWER_STATE_ON
}

/* GCOFF entry sequence */
Method (PGOF, 0, Serialized)
{
	/* Assert PERST# */
	CTXS (GPIO_GPU_PERST_L)

	/* All rails are about to go down */
	CTXS (GPIO_GPU_ALLRAILS_PG)
	Sleep (1)

	/* Ramp down FBVDD */
	CTXS (GPIO_FBVDD_PWR_EN)

	/* Ramp down PEXVDD and let rail discharge to <10% */
	CTXS (GPIO_PEXVDD_PWR_EN)
	Sleep (2)

	/* Ramp down NVVDD */
	CTXS (GPIO_NVVDD_PWR_EN)
	Sleep (2)

	/* Ramp down NV33 */
	CTXS (GPIO_NV33_PWR_EN)
	Sleep (4)

	/* Ramp down 1.8V */
	CTXS (GPEN)

	/* Ramp down 1.2V rail on boards with support */
	CTXS (GPIO_NV12_PWR_EN)

	GCOT = Timer

	GPPS = GPU_POWER_STATE_OFF
}

/* GCOFF Out, i.e. full power-on sequence */
Method (GCOO, 0, Serialized)
{
	If (GPPS == GPU_POWER_STATE_ON)
	{
		Printf ("PGON: GPU already on")
		Return
	}

	SRCC (SRCCLK_ENABLE)
	PGON ()
	\_SB.PCI0.PEG0.LD23 ()

	/* Wait for dGPU to reappear on the bus */
	Local0 = 50
	While (NVID != PCI_VID_NVIDIA)
	{
		Stall (100)
		Local0--
		If (Local0 == 0)
		{
			Break
		}
	}

	/* Restore the PEG LTR enable bit */
	LREN = SLTR

	/* Clear recoverable errors detected bit */
	CEDR = 1

	/* Restore the PEG LTR enable bit */
	LREN = SLTR

	/* Clear recoverable errors detected bit */
	CEDR = 1
}

/* GCOFF In, i.e. full power-off sequence */
Method (GCOI, 0, Serialized)
{
	If (GPPS == GPU_POWER_STATE_OFF)
	{
		Printf ("GPU already off")
		Return
	}

	/* Save the PEG port's LTR setting */
	SLTR = LREN
	\_SB.PCI0.PEG0.DL23 ()
	PGOF ()
	SRCC (SRCCLK_DISABLE)
}

/* Handle deferred GC6 vs. poweron request */
Method (NPON, 0, Serialized)
{
	If (DFEN == GC6_DEFER_ENABLE)
	{
		If (DFCO == GC6_DEFER_TYPE_EXIT_GC6)
		{
			GC6O ()
		}

		DFEN = GC6_DEFER_DISABLE
	}
	Else
	{
		GCOO ()
	}
}

/* Handle deferred GC6 vs. poweroff request */
Method (NPOF, 0, Serialized)
{
	/* Don't touch the `DFEN` flag until the GC6 exit. */
	If (DFEN == GC6_DEFER_ENABLE)
	{
		/* Deferred GC6 entry */
		If (DFCI == NVJT_GPC_EGNS || DFCI == NVJT_GPC_EGIS)
		{
			GC6I ()
		}
	}
	Else
	{
		GCOI ()
	}
}

Method (_ON, 0, Serialized)
{
	PGON ()
}

Method (_OFF, 0, Serialized)
{
	PGOF ()
}

/* Put device into D0 */
Method (_PS0, 0, NotSerialized)
{
	If (OPS0 == OPTIMUS_CONTROL_RUN_PS0)
	{
		/* Restore PCI config space */
		If (PCIO == PCI_OWNER_SBIOS)
		{
			VGAR = VGAB
		}

		/* Poweron or deferred GC6 exit */
		NPON ()

		OPS0 = OPTIMUS_CONTROL_NO_RUN_PS0
	}
}

/* Put device into D3 */
Method (_PS3, 0,  NotSerialized)
{
	If (OPCS == OPTIMUS_POWER_CONTROL_ENABLE)
	{
		/* Save PCI config space to ACPI buffer */
		If (PCIO == PCI_OWNER_SBIOS)
		{
			VGAB = VGAR
		}

		/* Poweroff or deferred GC6 entry */
		NPOF ()

		/* Because _PS3 ran NPOF, _PS0 must run NPON */
		OPS0 = OPTIMUS_CONTROL_RUN_PS0

		/* OPCS is one-shot, so reset it */
		OPCS = OPTIMUS_POWER_CONTROL_DISABLE
	}
}

Method (PSTA, 0, Serialized)
{
	If (GC6E == GC6_STATE_EXITED &&
	    \_SB.PCI0.GTXS(GPIO_GPU_ALLRAILS_PG) == 1)
	{
		Return (1)
	}
	Else
	{
		Return (0)
	}
}

Method (_STA, 0, Serialized)
{
	Return (0xF)
}