/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/drivers/pinctrl.h>

#include <hal/nrf_gpio.h>

BUILD_ASSERT(((NRF_PULL_NONE == NRF_GPIO_PIN_NOPULL) &&
	      (NRF_PULL_DOWN == NRF_GPIO_PIN_PULLDOWN) &&
	      (NRF_PULL_UP == NRF_GPIO_PIN_PULLUP)),
	      "nRF pinctrl pull settings do not match HAL values");

BUILD_ASSERT(((NRF_DRIVE_S0S1 == NRF_GPIO_PIN_S0S1) &&
	      (NRF_DRIVE_H0S1 == NRF_GPIO_PIN_H0S1) &&
	      (NRF_DRIVE_S0H1 == NRF_GPIO_PIN_S0H1) &&
	      (NRF_DRIVE_H0H1 == NRF_GPIO_PIN_H0H1) &&
	      (NRF_DRIVE_D0S1 == NRF_GPIO_PIN_D0S1) &&
	      (NRF_DRIVE_D0H1 == NRF_GPIO_PIN_D0H1) &&
	      (NRF_DRIVE_S0D1 == NRF_GPIO_PIN_S0D1) &&
	      (NRF_DRIVE_H0D1 == NRF_GPIO_PIN_H0D1) &&
#if defined(GPIO_PIN_CNF_DRIVE_E0E1)
	      (NRF_DRIVE_E0E1 == NRF_GPIO_PIN_E0E1) &&
#endif /* defined(GPIO_PIN_CNF_DRIVE_E0E1) */
	      (1U)),
	     "nRF pinctrl drive settings do not match HAL values");

/* value to indicate pin level doesn't need initialization */
#define NO_WRITE UINT32_MAX

#define PSEL_DISCONNECTED 0xFFFFFFFFUL

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_uart)
#define NRF_PSEL_UART(reg, line) ((NRF_UART_Type *)reg)->PSEL##line
#elif DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_uarte)
#define NRF_PSEL_UART(reg, line) ((NRF_UARTE_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_spi)
#define NRF_PSEL_SPIM(reg, line) ((NRF_SPI_Type *)reg)->PSEL##line
#elif DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_spim)
#define NRF_PSEL_SPIM(reg, line) ((NRF_SPIM_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_spis)
#if defined(NRF51)
#define NRF_PSEL_SPIS(reg, line) ((NRF_SPIS_Type *)reg)->PSEL##line
#else
#define NRF_PSEL_SPIS(reg, line) ((NRF_SPIS_Type *)reg)->PSEL.line
#endif
#endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_spis) */

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_twi)
#if !defined(TWI_PSEL_SCL_CONNECT_Pos)
#define NRF_PSEL_TWIM(reg, line) ((NRF_TWI_Type *)reg)->PSEL##line
#else
#define NRF_PSEL_TWIM(reg, line) ((NRF_TWI_Type *)reg)->PSEL.line
#endif
#elif DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_twim)
#define NRF_PSEL_TWIM(reg, line) ((NRF_TWIM_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_i2s)
#define NRF_PSEL_I2S(reg, line) ((NRF_I2S_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_pdm)
#define NRF_PSEL_PDM(reg, line) ((NRF_PDM_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_pwm)
#define NRF_PSEL_PWM(reg, line) ((NRF_PWM_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_qdec)
#define NRF_PSEL_QDEC(reg, line) ((NRF_QDEC_Type *)reg)->PSEL.line
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_qspi)
#define NRF_PSEL_QSPI(reg, line) ((NRF_QSPI_Type *)reg)->PSEL.line
#endif

int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
			   uintptr_t reg)
{
	for (uint8_t i = 0U; i < pin_cnt; i++) {
		nrf_gpio_pin_drive_t drive = NRF_GET_DRIVE(pins[i]);
		uint32_t psel = NRF_GET_PIN(pins[i]);
		uint32_t write = NO_WRITE;
		nrf_gpio_pin_dir_t dir;
		nrf_gpio_pin_input_t input;

		if (psel == NRF_PIN_DISCONNECTED) {
			psel = PSEL_DISCONNECTED;
		}

		switch (NRF_GET_FUN(pins[i])) {
#if defined(NRF_PSEL_UART)
		case NRF_FUN_UART_TX:
			NRF_PSEL_UART(reg, TXD) = psel;
			write = 1U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_UART_RX:
			NRF_PSEL_UART(reg, RXD) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_UART_RTS:
			NRF_PSEL_UART(reg, RTS) = psel;
			write = 1U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_UART_CTS:
			NRF_PSEL_UART(reg, CTS) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
#endif /* defined(NRF_PSEL_UART) */
#if defined(NRF_PSEL_SPIM)
		case NRF_FUN_SPIM_SCK:
			NRF_PSEL_SPIM(reg, SCK) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_SPIM_MOSI:
			NRF_PSEL_SPIM(reg, MOSI) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_SPIM_MISO:
			NRF_PSEL_SPIM(reg, MISO) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
#endif /* defined(NRF_PSEL_SPIM) */
#if defined(NRF_PSEL_SPIS)
		case NRF_FUN_SPIS_SCK:
			NRF_PSEL_SPIS(reg, SCK) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_SPIS_MOSI:
			NRF_PSEL_SPIS(reg, MOSI) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_SPIS_MISO:
			NRF_PSEL_SPIS(reg, MISO) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_SPIS_CSN:
			NRF_PSEL_SPIS(reg, CSN) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
#endif /* defined(NRF_PSEL_SPIS) */
#if defined(NRF_PSEL_TWIM)
		case NRF_FUN_TWIM_SCL:
			NRF_PSEL_TWIM(reg, SCL) = psel;
			if (drive == NRF_DRIVE_S0S1) {
				/* Override the default drive setting with one
				 * suitable for TWI/TWIM peripherals (S0D1).
				 * This drive cannot be used always so that
				 * users are able to select e.g. H0D1 or E0E1
				 * in devicetree.
				 */
				drive = NRF_DRIVE_S0D1;
			}
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_TWIM_SDA:
			NRF_PSEL_TWIM(reg, SDA) = psel;
			if (drive == NRF_DRIVE_S0S1) {
				drive = NRF_DRIVE_S0D1;
			}
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
#endif /* defined(NRF_PSEL_TWIM) */
#if defined(NRF_PSEL_I2S)
		case NRF_FUN_I2S_SCK_M:
			NRF_PSEL_I2S(reg, SCK) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_I2S_SCK_S:
			NRF_PSEL_I2S(reg, SCK) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_I2S_LRCK_M:
			NRF_PSEL_I2S(reg, LRCK) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_I2S_LRCK_S:
			NRF_PSEL_I2S(reg, LRCK) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_I2S_SDIN:
			NRF_PSEL_I2S(reg, SDIN) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_I2S_SDOUT:
			NRF_PSEL_I2S(reg, SDOUT) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_I2S_MCK:
			NRF_PSEL_I2S(reg, MCK) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
#endif /* defined(NRF_PSEL_I2S) */
#if defined(NRF_PSEL_PDM)
		case NRF_FUN_PDM_CLK:
			NRF_PSEL_PDM(reg, CLK) = psel;
			write = 0U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_PDM_DIN:
			NRF_PSEL_PDM(reg, DIN) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
#endif /* defined(NRF_PSEL_PDM) */
#if defined(NRF_PSEL_PWM)
		case NRF_FUN_PWM_OUT0:
			NRF_PSEL_PWM(reg, OUT[0]) = psel;
			write = NRF_GET_INVERT(pins[i]);
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_PWM_OUT1:
			NRF_PSEL_PWM(reg, OUT[1]) = psel;
			write = NRF_GET_INVERT(pins[i]);
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_PWM_OUT2:
			NRF_PSEL_PWM(reg, OUT[2]) = psel;
			write = NRF_GET_INVERT(pins[i]);
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_PWM_OUT3:
			NRF_PSEL_PWM(reg, OUT[3]) = psel;
			write = NRF_GET_INVERT(pins[i]);
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
#endif /* defined(NRF_PSEL_PWM) */
#if defined(NRF_PSEL_QDEC)
		case NRF_FUN_QDEC_A:
			NRF_PSEL_QDEC(reg, A) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_QDEC_B:
			NRF_PSEL_QDEC(reg, B) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
		case NRF_FUN_QDEC_LED:
			NRF_PSEL_QDEC(reg, LED) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_CONNECT;
			break;
#endif /* defined(NRF_PSEL_QDEC) */
#if defined(NRF_PSEL_QSPI)
		case NRF_FUN_QSPI_SCK:
			NRF_PSEL_QSPI(reg, SCK) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_QSPI_CSN:
			NRF_PSEL_QSPI(reg, CSN) = psel;
			write = 1U;
			dir = NRF_GPIO_PIN_DIR_OUTPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_QSPI_IO0:
			NRF_PSEL_QSPI(reg, IO0) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_QSPI_IO1:
			NRF_PSEL_QSPI(reg, IO1) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_QSPI_IO2:
			NRF_PSEL_QSPI(reg, IO2) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
		case NRF_FUN_QSPI_IO3:
			NRF_PSEL_QSPI(reg, IO3) = psel;
			dir = NRF_GPIO_PIN_DIR_INPUT;
			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			break;
#endif /* defined(NRF_PSEL_QSPI) */
		default:
			return -ENOTSUP;
		}

		/* configure GPIO properties */
		if (psel != PSEL_DISCONNECTED) {
			uint32_t pin = psel;

			if (write != NO_WRITE) {
				nrf_gpio_pin_write(pin, write);
			}

			/* force input and disconnected buffer for low power */
			if (NRF_GET_LP(pins[i]) == NRF_LP_ENABLE) {
				dir = NRF_GPIO_PIN_DIR_INPUT;
				input = NRF_GPIO_PIN_INPUT_DISCONNECT;
			}

			nrf_gpio_cfg(pin, dir, input, NRF_GET_PULL(pins[i]),
				     drive, NRF_GPIO_PIN_NOSENSE);
		}
	}

	return 0;
}
