/*
 * NPLL - Hollywood/Latte Hardware - GPIO
 *
 * Copyright (C) 2025-2026 Techflash
 */

#define MODULE "GPIO"

#include <npll/log.h>
#include <npll/console.h>
#include <npll/drivers.h>
#include <npll/irq.h>
#include <npll/input.h>
#include <npll/log.h>
#include <npll/regs.h>
#include <npll/types.h>
#include <npll/hollywood/gpio.h>

static REGISTER_DRIVER(gpioDrv);

static u32 prevIn = 0;
static const u32 gpioIrqMask = GPIO_POWER | GPIO_EJECT_BTN | GPIO_SLOT_IN;

static void gpioIRQHandler(enum irqDev dev) {
	u32 in, mask, set, clearred;
	u8 i, numSet, numClearred, setIdx[32], clearredIdx[32];
	inputEvent_t ev;
	bool irqs;
	(void)dev;

	in = HW_GPIOB_IN;
	HW_GPIOB_INTFLAG = HW_GPIOB_INTFLAG;
	HW_GPIOB_INTLVL = ~in;

	set = in & ~prevIn;
	clearred = prevIn & ~in;
	numSet = numClearred = 0;
	ev = 0;

	if (set & GPIO_POWER) {
		log_puts("Power button pressed");
		ev |= INPUT_EV_DOWN;
	}
	if (set & GPIO_EJECT_BTN) {
		log_puts("Eject button pressed");
		ev |= INPUT_EV_SELECT;
	}
	if (set & GPIO_SLOT_IN)
		log_puts("Disc inserted");
	else if (clearred & GPIO_SLOT_IN)
		log_puts("Disc removed");

	if (in != prevIn) {
		for (i = 0; i < 32; i++) {
			mask = 1u << i;
			if (set & mask)      setIdx[numSet++] = i;
			if (clearred & mask) clearredIdx[numClearred++] = i;
		}

		/* ensure that this looks right */
		irqs = IRQ_DisableSave();
		if (numSet) {
			log_printf("Now high:");
			for (i = 0; i < numSet; i++)
				_log_printf(" %d", setIdx[i]);
			_log_puts("");
		}
		if (numClearred) {
			log_printf("Now low:");
			for (i = 0; i < numClearred; i++)
				_log_printf(" %d", clearredIdx[i]);
			_log_puts("");
		}
		IRQ_Restore(irqs);
	}

	if (ev)
		IN_NewEvent(ev);

	prevIn = in;
}

static void gpioInit(void) {
	u32 dir, out;
	bool irqs;
#if 0
	log_printf(
"=== GPIO REGISTER DUMP ===\r\n\
HW_GPIO_OWNER:  0x%08x\r\n\
HW_GPIO_ENABLE: 0x%08x\r\n\
HW_GPIOB_DIR:   0x%08x\r\n\
HW_GPIOB_OUT:   0x%08x\r\n\
HW_GPIOB_IN:    0x%08x\r\n\
HW_GPIO_DIR:    0x%08x\r\n\
HW_GPIO_OUT:    0x%08x\r\n\
HW_GPIO_IN:     0x%08x\r\n",
HW_GPIO_OWNER, HW_GPIO_ENABLE, HW_GPIOB_DIR, HW_GPIOB_OUT,
HW_GPIOB_IN, HW_GPIO_DIR, HW_GPIO_OUT, HW_GPIO_IN);
#endif

	/* Broadway owns all */
	HW_GPIO_OWNER = 0xffffffff;

	/* all enabled */
	HW_GPIO_ENABLE = 0xffffffff;

	/* set up outlog_puts properly */
	out = GPIO_DC_DC | GPIO_FAN | GPIO_SENSOR_BAR | GPIO_DI_SPIN;
	if (H_ConsoleType == CONSOLE_TYPE_WII_U || H_WiiIsvWii)
		out |= GPIO_GAMEPAD_EN | GPIO_PADPD;
	HW_GPIOB_OUT = out;
	HW_GPIO_OUT  = out;

	/* set proper directions */
	dir = ~(GPIO_POWER | GPIO_EJECT_BTN | GPIO_SLOT_IN | GPIO_EEP_MISO | GPIO_AVE_SDA);
	if (H_ConsoleType == CONSOLE_TYPE_WII_U || H_WiiIsvWii)
		dir |= GPIO_GAMEPAD_EN | GPIO_PADPD;

	HW_GPIOB_DIR = dir;
	HW_GPIO_DIR  = dir;

	/* register the current state of the inlog_puts */
	prevIn = HW_GPIOB_IN;

	/* register our IRQ handler */
	irqs = IRQ_DisableSave();
	IRQ_RegisterHandler(IRQDEV_GPIOB, gpioIRQHandler);
	IRQ_Unmask(IRQDEV_GPIOB);
	IRQ_RegisterHandler(IRQDEV_GPIO, gpioIRQHandler);
	IRQ_Unmask(IRQDEV_GPIO);

	/* set up the interrupts properly */
	HW_GPIOB_INTLVL = ~prevIn;
	HW_GPIOB_INTMASK = gpioIrqMask;

	/* ack all existing interrupts */
	HW_GPIOB_INTFLAG = HW_GPIOB_INTFLAG;
	IRQ_Restore(irqs);

	/* we're all good */
	gpioDrv.state = DRIVER_STATE_READY;
}

static void gpioCleanup(void) {
	gpioDrv.state = DRIVER_STATE_NOT_READY;
}

static REGISTER_DRIVER(gpioDrv) = {
	.name = "Hollywood/Latte GPIO",
	.mask = DRIVER_ALLOW_WII | DRIVER_ALLOW_WIIU,
	.state = DRIVER_STATE_NOT_READY,
	.type = DRIVER_TYPE_OTHER,
	.init = gpioInit,
	.cleanup = gpioCleanup
};
