/*
 * Copyright (c) 2022 Espressif Systems (Shanghai) Co., Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/* Include esp-idf headers first to avoid redefining BIT() macro */
#include <hal/gpio_ll.h>
#include <hal/rtc_io_hal.h>

#include <soc.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pinctrl/pinctrl_esp32_common.h>

#ifdef CONFIG_SOC_SERIES_ESP32C3
/* gpio structs in esp32c3 series are different from xtensa ones */
#define out out.data
#define in in.data
#define out_w1ts out_w1ts.val
#define out_w1tc out_w1tc.val
#endif

#ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT
#define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0
#endif

#define ESP32_INVALID_PORT_ADDR          0UL

#define ESP32_GPIO_PORT_ADDR(nodelabel)				\
	COND_CODE_1(DT_NODE_EXISTS(DT_NODELABEL(nodelabel)),	\
		(DT_REG_ADDR(DT_NODELABEL(nodelabel)),),	\
		(ESP32_INVALID_PORT_ADDR))

/**
 * @brief Array containing each GPIO port address.
 *
 * Entries will be an invalid address if the port is not enabled.
 */
static const uint32_t esp32_gpio_ports_addrs[] = {
	ESP32_GPIO_PORT_ADDR(gpio0)
	ESP32_GPIO_PORT_ADDR(gpio1)
};

/** Number of GPIO ports. */
static const size_t esp32_gpio_ports_cnt = ARRAY_SIZE(esp32_gpio_ports_addrs);

static inline bool rtc_gpio_is_valid_gpio(uint32_t gpio_num)
{
#if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
	return (gpio_num < SOC_GPIO_PIN_COUNT && rtc_io_num_map[gpio_num] >= 0);
#else
	return false;
#endif
}

static inline bool esp32_pin_is_valid(uint32_t pin)
{
	return ((BIT(pin) & SOC_GPIO_VALID_GPIO_MASK) != 0);
}

static inline bool esp32_pin_is_output_capable(uint32_t pin)
{
	return ((BIT(pin) & SOC_GPIO_VALID_OUTPUT_GPIO_MASK) != 0);
}

static int esp32_pin_apply_config(uint32_t pin, uint32_t flags)
{
	gpio_dev_t *const gpio_base = (gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio0));
	uint32_t io_pin = (uint32_t) pin + ((ESP32_PORT_IDX(pin) == 1 && pin < 32) ? 32 : 0);
	int ret = 0;

	if (!esp32_pin_is_valid(io_pin)) {
		return -EINVAL;
	}

#if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
	if (rtc_gpio_is_valid_gpio(io_pin)) {
		rtcio_hal_function_select(rtc_io_num_map[io_pin], RTCIO_FUNC_DIGITAL);
	}
#endif

	if (io_pin >= GPIO_NUM_MAX) {
		ret = -EINVAL;
		goto end;
	}

	/* Set pin function as GPIO */
	gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[io_pin], PIN_FUNC_GPIO);

	if (flags & ESP32_PULL_UP_FLAG) {
		if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) {
			gpio_ll_pulldown_dis(&GPIO, io_pin);
			gpio_ll_pullup_en(&GPIO, io_pin);
		} else {
#if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
			int rtcio_num = rtc_io_num_map[io_pin];

			rtcio_hal_pulldown_disable(rtc_io_num_map[io_pin]);

			if (rtc_io_desc[rtcio_num].pullup) {
				rtcio_hal_pullup_enable(rtc_io_num_map[io_pin]);
			} else {
				ret = -ENOTSUP;
				goto end;
			}
#endif
		}
	} else if (flags & ESP32_PULL_DOWN_FLAG) {
		if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) {
			gpio_ll_pullup_dis(&GPIO, io_pin);
			gpio_ll_pulldown_en(&GPIO, io_pin);
		} else {
#if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
			int rtcio_num = rtc_io_num_map[io_pin];

			rtcio_hal_pulldown_enable(rtc_io_num_map[io_pin]);

			if (rtc_io_desc[rtcio_num].pullup) {
				rtcio_hal_pullup_disable(rtc_io_num_map[io_pin]);
			} else {
				ret = -ENOTSUP;
				goto end;
			}
#endif
		}
	}

	if (flags & ESP32_DIR_OUT_FLAG) {
		if (!esp32_pin_is_output_capable(pin)) {
			ret = -EINVAL;
			goto end;
		}

		if (flags & ESP32_OPEN_DRAIN_FLAG) {
			gpio_ll_od_enable(gpio_base, io_pin);
		} else {
			gpio_ll_od_disable(gpio_base, io_pin);
		}

		/* Set output pin initial value */
		if (flags & ESP32_PIN_OUT_HIGH_FLAG) {
			gpio_ll_set_level(gpio_base, io_pin, 1);
		} else if (flags & ESP32_PIN_OUT_LOW_FLAG) {
			gpio_ll_set_level(gpio_base, io_pin, 0);
		}

		gpio_ll_output_enable(&GPIO, io_pin);
		esp_rom_gpio_matrix_out(io_pin, SIG_GPIO_OUT_IDX, false, false);
	} else {
		if (!(flags & ESP32_PIN_OUT_EN_FLAG)) {
			gpio_ll_output_disable(&GPIO, io_pin);
		}
	}

	if (flags & ESP32_DIR_INP_FLAG) {
		gpio_ll_input_enable(&GPIO, io_pin);
	} else {
		if (!(flags & ESP32_PIN_IN_EN_FLAG)) {
			gpio_ll_input_disable(&GPIO, io_pin);
		}
	}

end:
	return ret;
}

static int esp32_pin_configure(const uint32_t pin_mux, const uint32_t pin_cfg)
{
	uint32_t port_addr;
	uint32_t pin_num = ESP32_PIN_NUM(pin_mux);
	uint32_t sig_in = ESP32_PIN_SIGI(pin_mux);
	uint32_t sig_out = ESP32_PIN_SIGO(pin_mux);
	uint32_t flags = 0;

	if (ESP32_PORT_IDX(pin_num) >= esp32_gpio_ports_cnt) {
		return -EINVAL;
	}

	port_addr = esp32_gpio_ports_addrs[ESP32_PORT_IDX(pin_num)];

	if (port_addr == ESP32_INVALID_PORT_ADDR) {
		return -EINVAL;
	}

	switch (ESP32_PIN_BIAS(pin_cfg)) {
	case ESP32_PULL_UP:
		flags |= ESP32_PULL_UP_FLAG;
		break;
	case ESP32_PULL_DOWN:
		flags |= ESP32_PULL_DOWN_FLAG;
		break;
	default:
		break;
	}

	switch (ESP32_PIN_DRV(pin_cfg)) {
	case ESP32_PUSH_PULL:
		flags |= ESP32_PUSH_PULL_FLAG;
		break;
	case ESP32_OPEN_DRAIN:
		flags |= ESP32_OPEN_DRAIN_FLAG;
		break;
	default:
		break;
	}

	if (sig_in == ESP_SIG_INVAL && sig_out == ESP_SIG_INVAL) {
		return -ENOTSUP;
	}

	if (sig_in != ESP_SIG_INVAL) {
		flags |= ESP32_DIR_INP_FLAG;
	}

	if (sig_out != ESP_SIG_INVAL) {
		flags |= ESP32_DIR_OUT_FLAG;
	}

	switch (ESP32_PIN_MODE_OUT(pin_cfg)) {
	case ESP32_PIN_OUT_HIGH:
		flags |= ESP32_PIN_OUT_HIGH_FLAG;
		break;
	case ESP32_PIN_OUT_LOW:
		flags |= ESP32_PIN_OUT_LOW_FLAG;
		break;
	default:
		break;
	}

	switch (ESP32_PIN_EN_DIR(pin_cfg)) {
	case ESP32_PIN_OUT_EN:
		flags |= ESP32_PIN_OUT_EN_FLAG;
		break;
	case ESP32_PIN_IN_EN:
		flags |= ESP32_PIN_IN_EN_FLAG;
		break;
	default:
		break;
	}

	if (flags & ESP32_PIN_OUT_HIGH_FLAG) {
		if (ESP32_PORT_IDX(pin_num) == 0) {
			gpio_dev_t *const gpio_dev =
				(gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio0));
			gpio_dev->out_w1ts = pin_num;
#if DT_NODE_HAS_STATUS(DT_NODELABEL(gpio1), okay)
		} else {
			gpio_dev_t *const gpio_dev =
				(gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio1));
			gpio_dev->out1_w1ts.data = pin_num;
#endif
		}
	}

	if (flags & ESP32_PIN_OUT_LOW_FLAG) {
		if (ESP32_PORT_IDX(pin_num) == 0) {
			gpio_dev_t *const gpio_dev =
				(gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio0));
			gpio_dev->out_w1tc = pin_num;
#if DT_NODE_HAS_STATUS(DT_NODELABEL(gpio1), okay)
		} else {
			gpio_dev_t *const gpio_dev =
				(gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio1));
			gpio_dev->out1_w1tc.data = pin_num;
#endif
		}
	}

	esp32_pin_apply_config(pin_num, flags);

	if (flags & ESP32_DIR_OUT_FLAG) {
		esp_rom_gpio_matrix_out(pin_num, sig_out, 0, 0);
	}

	if (flags & ESP32_DIR_INP_FLAG) {
		esp_rom_gpio_matrix_in(pin_num, sig_in, 0);
	}

	return 0;
}

int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
			   uintptr_t reg)
{
	uint32_t pin_mux, pin_cfg;
	int ret = 0;

	ARG_UNUSED(reg);

	for (uint8_t i = 0U; i < pin_cnt; i++) {
		pin_mux = pins[i].pinmux;
		pin_cfg = pins[i].pincfg;

		ret = esp32_pin_configure(pin_mux, pin_cfg);
		if (ret < 0) {
			return ret;
		}
	}

	return 0;
}
