/*
 * Copyright (c) 2025 ITE Corporation. All Rights Reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ite_it51xxx_ecpm

#include <soc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/dt-bindings/clock/ite-it51xxx-clock.h>
#include <zephyr/dt-bindings/interrupt-controller/ite-it51xxx-intc.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(clock_control_it51xxx, LOG_LEVEL_ERR);
#include "soc_espi.h"

BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
	     "only one ite,it51xxx-ecpm compatible node can be supported");

/* it51xxx ECPM registers definition */
/* 0x02: Clock Gating Control 2 register */
#define ECPM_CGCTRL2R     0x02
#define ECPM_CIRCG        BIT(5)
#define ECPM_SWUCCG       BIT(4)
/* 0x03: PLL Control */
#define ECPM_PLLCTRL      0x03
/* 0x04: Auto Clock Gating */
#define ECPM_AUTOCG       0x04
#define ECPM_AUART1CG     BIT(6)
#define ECPM_AUART2CG     BIT(5)
#define ECPM_ASSPICG      BIT(4)
#define ECPM_ACIRCG       BIT(2)
/* 0x02: Clock Gating Control 5 register */
#define ECPM_CGCTRL3R     0x05
#define ECPM_PECICG       BIT(3)
#define ECPM_SSPICG       BIT(1)
/* 0x06: PLL Frequency */
#define ECPM_PLLFREQR     0x06
#define ECPM_PLLFREQ_MASK GENMASK(3, 0)

static const uint8_t pll_cfg[] = {
	[PLL_18400_KHZ] = 0x01,
	[PLL_32300_KHZ] = 0x03,
	[PLL_64500_KHZ] = 0x07,
	[PLL_48000_KHZ] = 0x09,
};

struct clock_control_it51xxx_data {
	const uint8_t *pll_configuration;
};

/* Driver config */
struct clock_control_it51xxx_config {
	mm_reg_t ecpm_base;
	int pll_freq;
};

/* Clock controller local functions */
static inline int clock_control_it51xxx_on(const struct device *dev,
					   clock_control_subsys_t sub_system)
{
	const struct clock_control_it51xxx_config *const config = dev->config;
	struct ite_clk_cfg *clk_cfg = (struct ite_clk_cfg *)(sub_system);

	/* Enable the clock of this module */
	sys_write8(sys_read8(config->ecpm_base + clk_cfg->ctrl) & ~(clk_cfg->bits),
		   config->ecpm_base + clk_cfg->ctrl);

	return 0;
}

static inline int clock_control_it51xxx_off(const struct device *dev,
					    clock_control_subsys_t sub_system)
{
	const struct clock_control_it51xxx_config *const config = dev->config;
	struct ite_clk_cfg *clk_cfg = (struct ite_clk_cfg *)(sub_system);
	uint8_t tmp_mask = 0;

	/* CGCTRL3R, bit 6, must always write a 1. */
	tmp_mask = (clk_cfg->ctrl == IT51XXX_ECPM_CGCTRL3R_OFF) ? 0x40 : 0x00;
	sys_write8(sys_read8(config->ecpm_base + clk_cfg->ctrl) | clk_cfg->bits | tmp_mask,
		   config->ecpm_base + clk_cfg->ctrl);

	return 0;
}

static int clock_control_it51xxx_get_rate(const struct device *dev,
					  clock_control_subsys_t sub_system, uint32_t *rate)
{
	const struct clock_control_it51xxx_config *const config = dev->config;
	int reg_val = sys_read8(config->ecpm_base + ECPM_PLLFREQR) & ECPM_PLLFREQ_MASK;

	switch (reg_val) {
	case 0x01:
		*rate = KHZ(18400);
		break;
	case 0x03:
		*rate = KHZ(32300);
		break;
	case 0x07:
		*rate = KHZ(64500);
		break;
	case 0x09:
		*rate = KHZ(48000);
		break;
	default:
		return -ERANGE;
	}

	return 0;
}

static void pll_change_isr(const void *unused)
{
	ARG_UNUSED(unused);

	/*
	 * We are here because we have completed changing PLL sequence,
	 * so disabled PLL frequency change event interrupt.
	 */
	irq_disable(IT51XXX_IRQ_PLL_CHANGE);
}

static void chip_configure_pll(const struct device *dev, uint8_t pll)
{
	const struct clock_control_it51xxx_config *config = dev->config;

	/* Set pll frequency change event */
	IRQ_CONNECT(IT51XXX_IRQ_PLL_CHANGE, 0, pll_change_isr, NULL, IRQ_TYPE_EDGE_RISING);
	/* Clear interrupt status of pll frequency change event */
	ite_intc_isr_clear(IT51XXX_IRQ_PLL_CHANGE);

	irq_enable(IT51XXX_IRQ_PLL_CHANGE);
	/*
	 * Configure PLL clock dividers.
	 * Writing data to these registers doesn't change the PLL frequency immediately until the
	 * status is changed into wakeup from the sleep mode.
	 * The following code is intended to make the system enter sleep mode, and wait PLL
	 * frequency change event to wakeup chip to complete PLL update.
	 */
	sys_write8(pll, config->ecpm_base + ECPM_PLLFREQR);

	/* Chip sleep after wait for interrupt (wfi) instruction */
	chip_pll_ctrl(CHIP_PLL_SLEEP);
	/* Chip sleep and wait timer wake it up */
	__asm__ volatile("wfi");
	/* Chip sleep and wait timer wake it up */
	chip_pll_ctrl(CHIP_PLL_DOZE);
}

static int clock_control_it51xxx_init(const struct device *dev)
{
	const struct clock_control_it51xxx_config *config = dev->config;
	struct clock_control_it51xxx_data *data = dev->data;
	int reg_val = sys_read8(config->ecpm_base + ECPM_PLLFREQR) & ECPM_PLLFREQ_MASK;
	uint8_t autocg;

	/* Disable auto gating and enable it by the respective module. */
	autocg = sys_read8(config->ecpm_base + ECPM_AUTOCG);
	sys_write8(autocg & ~(ECPM_AUART1CG | ECPM_AUART2CG | ECPM_ASSPICG | ECPM_ACIRCG),
		   config->ecpm_base + ECPM_AUTOCG);

	/* The following modules are gated in the initial state */
	sys_write8(ECPM_CIRCG | ECPM_SWUCCG, config->ecpm_base + ECPM_CGCTRL2R);
	sys_write8(sys_read8(config->ecpm_base + ECPM_CGCTRL3R) | ECPM_PECICG | ECPM_SSPICG,
		   config->ecpm_base + ECPM_CGCTRL3R);

	if (IS_ENABLED(CONFIG_ITE_IT51XXX_INTC)) {
		ite_intc_save_and_disable_interrupts();
	}

#ifdef CONFIG_ESPI
	/*
	 * We have to disable eSPI pad before changing PLL sequence
	 * or sequence will fail if CS# pin is low.
	 */
	espi_ite_ec_enable_pad_ctrl(ESPI_ITE_SOC_DEV, false);
#endif
	if (reg_val != data->pll_configuration[config->pll_freq]) {
		/* configure PLL clock */
		chip_configure_pll(dev, data->pll_configuration[config->pll_freq]);
	}
#ifdef CONFIG_ESPI
	/* Enable eSPI pad after changing PLL sequence */
	espi_ite_ec_enable_pad_ctrl(ESPI_ITE_SOC_DEV, true);
#endif
	if (IS_ENABLED(CONFIG_ITE_IT51XXX_INTC)) {
		ite_intc_restore_interrupts();
	}

	return 0;
}

/* Clock controller driver registration */
static DEVICE_API(clock_control, clock_control_it51xxx_api) = {
	.on = clock_control_it51xxx_on,
	.off = clock_control_it51xxx_off,
	.get_rate = clock_control_it51xxx_get_rate,
};

static struct clock_control_it51xxx_data clock_control_it51xxx_data = {
	.pll_configuration = pll_cfg,
};

static const struct clock_control_it51xxx_config clock_control_it51xxx_cfg = {
	.ecpm_base = DT_INST_REG_ADDR(0),
	.pll_freq = DT_INST_PROP(0, pll_frequency),
};

DEVICE_DT_INST_DEFINE(0, clock_control_it51xxx_init, NULL, &clock_control_it51xxx_data,
		      &clock_control_it51xxx_cfg, PRE_KERNEL_1,
		      CONFIG_IT51XXX_PLL_SEQUENCE_PRIORITY, &clock_control_it51xxx_api);
