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

#define DT_DRV_COMPAT ite_it8xxx2_pinctrl_func

#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>

#include <chip_chipregs.h>

LOG_MODULE_REGISTER(pinctrl_ite_it8xxx2, LOG_LEVEL_ERR);

#define GPIO_IT8XXX2_REG_BASE \
	((struct gpio_it8xxx2_regs *)DT_REG_ADDR(DT_NODELABEL(gpiogcr)))
#define GPIO_GROUP_MEMBERS  8

struct pinctrl_it8xxx2_gpio {
	/* gpio port control register (byte mapping to pin) */
	uint8_t *reg_gpcr;
	/* function 3 general control register */
	uintptr_t func3_gcr[GPIO_GROUP_MEMBERS];
	/* function 3 enable mask */
	uint8_t func3_en_mask[GPIO_GROUP_MEMBERS];
	/* function 4 general control register */
	uintptr_t func4_gcr[GPIO_GROUP_MEMBERS];
	/* function 4 enable mask */
	uint8_t func4_en_mask[GPIO_GROUP_MEMBERS];
	/* Input voltage selection */
	uintptr_t volt_sel[GPIO_GROUP_MEMBERS];
	/* Input voltage selection mask */
	uint8_t volt_sel_mask[GPIO_GROUP_MEMBERS];
};

struct pinctrl_it8xxx2_ksi_kso {
	/*
	 * KSI[7:0]/KSO[15:8]/KSO[7:0] port gpio control register
	 * (bit mapping to pin)
	 */
	uint8_t *reg_gctrl;
	/* KSI[7:0]/KSO[15:8]/KSO[7:0] port control register */
	uint8_t *reg_ctrl;
	/*
	 * KSO push-pull/open-drain bit of KSO[15:0] control register
	 * (this bit apply to all pins)
	 */
	int pp_od_mask;
	/*
	 * KSI/KSO pullup bit of KSI[7:0]/KSO[15:0] control register
	 * (this bit apply to all pins)
	 */
	int pullup_mask;
};

struct pinctrl_it8xxx2_config {
	bool gpio_group;
	union {
		struct pinctrl_it8xxx2_gpio gpio;
		struct pinctrl_it8xxx2_ksi_kso ksi_kso;
	};
};

static int pinctrl_it8xxx2_set(const pinctrl_soc_pin_t *pins)
{
	const struct pinctrl_it8xxx2_config *pinctrl_config = pins->pinctrls->config;
	const struct pinctrl_it8xxx2_gpio *gpio = &(pinctrl_config->gpio);
	uint32_t pincfg = pins->pincfg;
	uint8_t pin = pins->pin;
	volatile uint8_t *reg_gpcr = (uint8_t *)gpio->reg_gpcr + pin;
	volatile uint8_t *reg_volt_sel = (uint8_t *)(gpio->volt_sel[pin]);

	/* Setting pull-up or pull-down. */
	switch (IT8XXX2_DT_PINCFG_PUPDR(pincfg)) {
	case IT8XXX2_PULL_PIN_DEFAULT:
		/* No pull-up or pull-down */
		*reg_gpcr &= ~(GPCR_PORT_PIN_MODE_PULLUP |
			       GPCR_PORT_PIN_MODE_PULLDOWN);
		break;
	case IT8XXX2_PULL_UP:
		*reg_gpcr = (*reg_gpcr | GPCR_PORT_PIN_MODE_PULLUP) &
			     ~GPCR_PORT_PIN_MODE_PULLDOWN;
		break;
	case IT8XXX2_PULL_DOWN:
		*reg_gpcr = (*reg_gpcr | GPCR_PORT_PIN_MODE_PULLDOWN) &
			     ~GPCR_PORT_PIN_MODE_PULLUP;
		break;
	default:
		LOG_ERR("This pull level is not supported.");
		return -EINVAL;
	}

	/*
	 * Since not all GPIOs support voltage selection, configure voltage
	 * selection register only if it is present.
	 */
	if (reg_volt_sel != NULL) {
		/* Setting voltage 3.3V or 1.8V. */
		switch (IT8XXX2_DT_PINCFG_VOLTAGE(pincfg)) {
		case IT8XXX2_VOLTAGE_3V3:
			/* Input voltage selection 3.3V. */
			*reg_volt_sel &= ~gpio->volt_sel_mask[pin];
			break;
		case IT8XXX2_VOLTAGE_1V8:
			__ASSERT(!(IT8XXX2_DT_PINCFG_PUPDR(pincfg)
				   == IT8XXX2_PULL_UP),
			"Don't enable internal pullup if 1.8V voltage is used");
			/* Input voltage selection 1.8V. */
			*reg_volt_sel |= gpio->volt_sel_mask[pin];
			break;
		default:
			LOG_ERR("The voltage selection is not supported");
			return -EINVAL;
		}
	}

	/* Setting tri-state mode. */
	if (IT8XXX2_DT_PINCFG_IMPEDANCE(pincfg)) {
		*reg_gpcr |= (GPCR_PORT_PIN_MODE_PULLUP |
			      GPCR_PORT_PIN_MODE_PULLDOWN);
	}

	return 0;
}

static int pinctrl_gpio_it8xxx2_configure_pins(const pinctrl_soc_pin_t *pins)
{
	const struct pinctrl_it8xxx2_config *pinctrl_config = pins->pinctrls->config;
	const struct pinctrl_it8xxx2_gpio *gpio = &(pinctrl_config->gpio);
	uint8_t pin = pins->pin;
	volatile uint8_t *reg_gpcr = (uint8_t *)gpio->reg_gpcr + pin;
	volatile uint8_t *reg_func3_gcr = (uint8_t *)(gpio->func3_gcr[pin]);
	volatile uint8_t *reg_func4_gcr = (uint8_t *)(gpio->func4_gcr[pin]);

	/* Handle PIN configuration. */
	if (pinctrl_it8xxx2_set(pins)) {
		LOG_ERR("Pin configuration is invalid.");
		return -EINVAL;
	}

	/*
	 * If pincfg is input, we don't need to handle
	 * alternate function.
	 */
	if (IT8XXX2_DT_PINCFG_INPUT(pins->pincfg)) {
		*reg_gpcr = (*reg_gpcr | GPCR_PORT_PIN_MODE_INPUT) &
			     ~GPCR_PORT_PIN_MODE_OUTPUT;
		return 0;
	}

	/*
	 * Handle alternate function.
	 */
	/* Common settings for alternate function. */
	*reg_gpcr &= ~(GPCR_PORT_PIN_MODE_INPUT |
		       GPCR_PORT_PIN_MODE_OUTPUT);
	switch (pins->alt_func) {
	case IT8XXX2_ALT_FUNC_1:
		/* Func1: Alternate function has been set above. */
		break;
	case IT8XXX2_ALT_FUNC_2:
		/* Func2: WUI function: turn the pin into an input */
		*reg_gpcr |= GPCR_PORT_PIN_MODE_INPUT;
		break;
	case IT8XXX2_ALT_FUNC_3:
		/*
		 * Func3: In addition to the alternate setting above,
		 *        Func3 also need to set the general control.
		 */
		*reg_func3_gcr |= gpio->func3_en_mask[pin];
		break;
	case IT8XXX2_ALT_FUNC_4:
		/*
		 * Func4: In addition to the alternate setting above,
		 *        Func4 also need to set the general control.
		 */
		*reg_func4_gcr |= gpio->func4_en_mask[pin];
		break;
	case IT8XXX2_ALT_DEFAULT:
		*reg_gpcr = (*reg_gpcr | GPCR_PORT_PIN_MODE_INPUT) &
			     ~GPCR_PORT_PIN_MODE_OUTPUT;
		*reg_func3_gcr &= ~gpio->func3_en_mask[pin];
		*reg_func4_gcr &= ~gpio->func4_en_mask[pin];
		break;
	default:
		LOG_ERR("This function is not supported.");
		return -EINVAL;
	}

	return 0;
}

static int pinctrl_kscan_it8xxx2_set(const pinctrl_soc_pin_t *pins)
{
	const struct pinctrl_it8xxx2_config *pinctrl_config = pins->pinctrls->config;
	const struct pinctrl_it8xxx2_ksi_kso *ksi_kso = &(pinctrl_config->ksi_kso);
	volatile uint8_t *reg_ctrl = ksi_kso->reg_ctrl;
	uint8_t pullup_mask = ksi_kso->pullup_mask;
	uint8_t pp_od_mask = ksi_kso->pp_od_mask;
	uint32_t pincfg = pins->pincfg;

	/*
	 * Enable or disable internal pull-up (this bit apply to all pins):
	 * If KSI[7:0]/KSO[15:0] is in KBS mode , setting 1 enables the internal
	 * pull-up (KSO[17:16] setting internal pull-up by GPIO port GPCR register).
	 * If KSI[7:0]/KSO[15:0] is in GPIO mode, then this bit is always disabled.
	 */
	switch (IT8XXX2_DT_PINCFG_PULLUP(pincfg)) {
	case IT8XXX2_PULL_PIN_DEFAULT:
		/* Disable internal pulll-up */
		*reg_ctrl &= ~pullup_mask;
		break;
	case IT8XXX2_PULL_UP:
		*reg_ctrl |= pullup_mask;
		break;
	default:
		LOG_ERR("This pull level is not supported.");
		return -EINVAL;
	}

	/*
	 * Set push-pull or open-drain mode (this bit apply to all pins):
	 * KSI[7:0] doesn't support push-pull and open-drain settings in kbs mode.
	 * If KSO[17:0] is in KBS mode, setting 1 selects open-drain mode,
	 * setting 0 selects push-pull mode.
	 * If KSO[15:0] is in GPIO mode, then this bit is always disabled.
	 */
	if (pp_od_mask != NO_FUNC) {
		switch (IT8XXX2_DT_PINCFG_PP_OD(pincfg)) {
		case IT8XXX2_PUSH_PULL:
			*reg_ctrl &= ~pp_od_mask;
			break;
		case IT8XXX2_OPEN_DRAIN:
			*reg_ctrl |= pp_od_mask;
			break;
		default:
			LOG_ERR("This pull mode is not supported.");
			return -EINVAL;
		}
	}

	return 0;
}

static int pinctrl_kscan_it8xxx2_configure_pins(const pinctrl_soc_pin_t *pins)
{
	const struct pinctrl_it8xxx2_config *pinctrl_config = pins->pinctrls->config;
	const struct pinctrl_it8xxx2_ksi_kso *ksi_kso = &(pinctrl_config->ksi_kso);

	/* Set a pin of KSI[7:0]/KSO[15:0] to pullup, push-pull/open-drain */
	if (pinctrl_kscan_it8xxx2_set(pins)) {
		return -EINVAL;
	}

#ifdef CONFIG_SOC_IT8XXX2_REG_SET_V1
	uint8_t pin_mask = BIT(pins->pin);
	volatile uint8_t *reg_gctrl = ksi_kso->reg_gctrl;

	switch (pins->alt_func) {
	case IT8XXX2_ALT_FUNC_1:
		/* Set a pin of KSI[7:0]/KSO[15:0] to kbs mode */
		*reg_gctrl &= ~pin_mask;
		break;
	case IT8XXX2_ALT_DEFAULT:
		/* Set a pin of KSI[7:0]/KSO[15:0] to gpio mode */
		*reg_gctrl |= pin_mask;
		break;
#elif CONFIG_SOC_IT8XXX2_REG_SET_V2
	uint8_t pin = pins->pin;
	volatile uint8_t *reg_gctrl = ksi_kso->reg_gctrl + pin;

	switch (pins->alt_func) {
	case IT8XXX2_ALT_FUNC_1:
		/* Set a pin of KSI[7:0]/KSO[15:0] to kbs mode */
		*reg_gctrl &= ~(GPCR_PORT_PIN_MODE_INPUT |
				GPCR_PORT_PIN_MODE_OUTPUT);
		break;
	case IT8XXX2_ALT_DEFAULT:
		/* Set a pin of KSI[7:0]/KSO[15:0] to gpio mode */
		*reg_gctrl = (*reg_gctrl | GPCR_PORT_PIN_MODE_INPUT) &
			      ~GPCR_PORT_PIN_MODE_OUTPUT;
		break;
#endif
	default:
		LOG_ERR("Alternate function not supported");
		return -ENOTSUP;
	}

	return 0;
}

int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
			   uintptr_t reg)
{
	ARG_UNUSED(reg);
	const struct pinctrl_it8xxx2_config *pinctrl_config;
	int status;

	for (uint8_t i = 0U; i < pin_cnt; i++) {
		pinctrl_config = pins[i].pinctrls->config;

		if (pinctrl_config->gpio_group) {
			status = pinctrl_gpio_it8xxx2_configure_pins(&pins[i]);
		} else {
			status = pinctrl_kscan_it8xxx2_configure_pins(&pins[i]);
		}

		if (status < 0) {
			LOG_ERR("%s pin%d configuration is invalid.",
				pins[i].pinctrls->name, pins[i].pin);
			return status;
		}
	}

	return 0;
}

static int pinctrl_it8xxx2_init(const struct device *dev)
{
	struct gpio_it8xxx2_regs *const gpio_base = GPIO_IT8XXX2_REG_BASE;

	/*
	 * The default value of LPCRSTEN is bit2:1 = 10b(GPD2) in GCR.
	 * If LPC reset is enabled on GPB7, we have to clear bit2:1
	 * to 00b.
	 */
	gpio_base->GPIO_GCR &= ~IT8XXX2_GPIO_LPCRSTEN;

	/*
	 * TODO: If UART2 swaps from bit2:1 to bit6:5 in H group, we
	 * have to set UART1PSEL = 1 in UART1PMR register.
	 */

#ifdef CONFIG_SOC_IT8XXX2_REG_SET_V2
	/*
	 * Swap the default I2C2 SMCLK2/SMDAT2 pins from GPC7/GPD0 to GPF6/GPF7,
	 * and I2C3 SMCLK3/SMDAT3 pins from GPB2/GPB5 to GPH1/GPH2,
	 * and I2C5 SMCLK5/SMDAT5 pins from GPE1/GPE2 to GPA4/GPA5,
	 */
	gpio_base->GPIO_GCR7 &= ~(IT8XXX2_GPIO_SMB2PS |
				  IT8XXX2_GPIO_SMB3PS |
				  IT8XXX2_GPIO_SMB5PS);
#endif
	return 0;
}

#define INIT_UNION_CONFIG(inst)                                                    \
	COND_CODE_1(DT_INST_PROP(inst, gpio_group),                                \
		(.gpio = {                                                         \
			 .reg_gpcr = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 0),  \
			 .func3_gcr = DT_INST_PROP(inst, func3_gcr),               \
			 .func3_en_mask = DT_INST_PROP(inst, func3_en_mask),       \
			 .func4_gcr = DT_INST_PROP(inst, func4_gcr),               \
			 .func4_en_mask = DT_INST_PROP(inst, func4_en_mask),       \
			 .volt_sel = DT_INST_PROP(inst, volt_sel),                 \
			 .volt_sel_mask = DT_INST_PROP(inst, volt_sel_mask),       \
		}),                                                                \
		(.ksi_kso = {                                                      \
			 .reg_gctrl = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 0), \
			 .reg_ctrl = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 1),  \
			 .pp_od_mask = (uint8_t)DT_INST_PROP(inst, pp_od_mask),    \
			 .pullup_mask = (uint8_t)DT_INST_PROP(inst, pullup_mask),  \
		})                                                                 \
	)

#define PINCTRL_ITE_INIT(inst)                                                     \
	static const struct pinctrl_it8xxx2_config pinctrl_it8xxx2_cfg_##inst = {  \
		.gpio_group = DT_INST_PROP(inst, gpio_group),                      \
		{                                                                  \
			INIT_UNION_CONFIG(inst)                                    \
		}                                                                  \
	};                                                                         \
										   \
	DEVICE_DT_INST_DEFINE(inst, &pinctrl_it8xxx2_init,                         \
			      NULL,                                                \
			      NULL,                                                \
			      &pinctrl_it8xxx2_cfg_##inst,                         \
			      PRE_KERNEL_1,                                        \
			      CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,                 \
			      NULL);
DT_INST_FOREACH_STATUS_OKAY(PINCTRL_ITE_INIT)
