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

#include <zephyr.h>
#include <init.h>
#include <drivers/gpio.h>
#include <logging/log.h>

LOG_MODULE_REGISTER(board_control, CONFIG_BOARD_NRF9160DK_LOG_LEVEL);

/* The following pins on the nRF52840 control the routing of certain
 * components/lines on the nRF9160 DK. They are specified as follows:
 *
 * COMPONENT_SWITCH : ROUTING PIN
 *
 * NOTE: UART1_VCOM_U7 is on pin 12 of both P0 and P1.
 * Both P0.12 -and- P1.12 need to be toggled to route UART1 to VCOM2.
 */

/* GPIO pins on Port 0 */

#define INTERFACE0_U5 13 /* MCU interface pins 0 - 2 */
#define INTERFACE1_U6 24 /* MCU interface pins 3 - 5 */
#define UART1_VCOM_U7 12 /* Route nRF9160 UART1 to VCOM2 */
#define BUTTON1_U12 6
#define BUTTON2_U12 26
#define SWITCH2_U9 8

/* GPIO pins on Port 1 */

#define INTERFACE2_U21 10 /* COEX interface pins 6 - 8 */
#define UART0_VCOM_U14 14 /* Route nRF9160 UART0 to VCOM0 */
#define UART1_VCOM_U7 12  /* Route nRF9160 UART1 to VCOM2 */
#define LED1_U8 5
#define LED2_U8 7
#define LED3_U11 1
#define LED4_U11 3
#define SWITCH1_U9 9

/* MCU interface pins
 * These pins can be used for inter-SoC communication.
 *
 * | nRF9160 |				 | nRF52840 | nRF9160 DK |
 * | P0.17   | -- MCU Interface Pin 0 -- | P0.17    | Arduino 4  |
 * | P0.18   | -- MCU Interface Pin 1 -- | P0.20    | Arduino 5  |
 * | P0.19   | -- MCU Interface Pin 2 -- | P0.15    | Arduino 6  |
 * | P0.21   | -- MCU Interface Pin 3 -- | P0.22    | TRACECLK   |
 * | P0.22   | -- MCU Interface Pin 4 -- | P1.04    | TRACEDATA0 |
 * | P0.23   | -- MCU Interface Pin 5 -- | P1.02    | TRACEDATA1 |
 * | COEX0   | -- MCU Interface Pin 6 -- | P1.13    | COEX0_PH   |
 * | COEX1   | -- MCU Interface Pin 7 -- | P1.11    | COEX1_PH   |
 * | COEX2   | -- MCU Interface Pin 8 -- | P1.15    | COEX2_PH   |
 */

__packed struct pin_config {
	uint8_t pin;
	uint8_t val;
};

/* The following tables specify the configuration of each pin based on the
 * Kconfig options that drive it.
 * The switches have active-low logic, so when writing to the port we will
 * need to invert the value to match the IS_ENABLED() logic.
 */

static const struct pin_config pins_on_p0[] = {
	{ INTERFACE0_U5, IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE0_ARDUINO) },
	{ INTERFACE1_U6, IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE1_TRACE) },
	{ UART1_VCOM_U7, IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART1_ARDUINO) },
	{ BUTTON1_U12,   IS_ENABLED(CONFIG_BOARD_NRF9160DK_BUTTON0_PHY) },
	{ BUTTON2_U12,   IS_ENABLED(CONFIG_BOARD_NRF9160DK_BUTTON1_PHY) },
	{ SWITCH2_U9,    IS_ENABLED(CONFIG_BOARD_NRF9160DK_SWITCH1_PHY) },
};

static const struct pin_config pins_on_p1[] = {
	{ INTERFACE2_U21, IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE2_COEX) },
	{ UART0_VCOM_U14, IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART0_VCOM) },
	{ UART1_VCOM_U7,  IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART1_ARDUINO) },
	{ LED1_U8,        IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED0_PHY) },
	{ LED2_U8,        IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED1_PHY) },
	{ LED3_U11,       IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED2_PHY) },
	{ LED4_U11,       IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED3_PHY) },
	{ SWITCH1_U9,     IS_ENABLED(CONFIG_BOARD_NRF9160DK_SWITCH0_PHY) },
};

static void config_print(void)
{
	/* Interface pins 0-2 */
	LOG_INF("Routing interface pins 0-2 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE0_MCU) ?
			"nRF52840" :
			"Arduino headers",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE0_MCU));

	/* Interface pins 3-5 */
	LOG_INF("Routing interface pins 3-5 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE1_MCU) ?
			"nRF52840" :
			"TRACE header",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE1_MCU));

	/* Interface pins 6-8 */
	LOG_INF("Routing interface pins 6-8 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE2_MCU) ?
			"nRF52840" :
			"COEX header",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_INTERFACE2_MCU));

	LOG_INF("Routing nRF9160 UART0 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART0_ARDUINO) ?
			"Arduino pin headers" :
			"VCOM0",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART0_ARDUINO));

	LOG_INF("Routing nRF9160 UART1 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART1_ARDUINO) ?
			"Arduino pin headers" :
			"VCOM2",
		/* defaults to arduino pins */
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_UART1_VCOM));

	LOG_INF("Routing nRF9160 LED 1 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED0_ARDUINO) ?
			"Arduino pin headers" :
			"physical LED",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED0_ARDUINO));

	LOG_INF("Routing nRF9160 LED 2 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED1_ARDUINO) ?
			"Arduino pin headers" :
			"physical LED",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED1_ARDUINO));

	LOG_INF("Routing nRF9160 LED 3 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED2_ARDUINO) ?
			"Arduino pin headers" :
			"physical LED",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED2_ARDUINO));

	LOG_INF("Routing nRF9160 LED 4 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED3_ARDUINO) ?
			"Arduino pin headers" :
			"physical LED",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_LED3_ARDUINO));

	LOG_INF("Routing nRF9160 button 1 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_BUTTON0_ARDUINO) ?
			"Arduino pin headers" :
			"physical button",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_BUTTON0_ARDUINO));

	LOG_INF("Routing nRF9160 button 2 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_BUTTON1_ARDUINO) ?
			"Arduino pin headers" :
			"physical button",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_BUTTON1_ARDUINO));

	LOG_INF("Routing nRF9160 switch 1 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_SWITCH0_ARDUINO) ?
			"Arduino pin headers" :
			"physical switch",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_SWITCH0_ARDUINO));

	LOG_INF("Routing nRF9160 switch 2 to %s (pin -> %d)",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_SWITCH1_ARDUINO) ?
			"Arduino pin headers" :
			"physical switch",
		IS_ENABLED(CONFIG_BOARD_NRF9160DK_SWITCH1_ARDUINO));
}

static int pins_configure(struct device *port, const struct pin_config cfg[],
			  size_t pins)
{
	int err;

	for (size_t i = 0; i < pins; i++) {
		/* A given pin controlling the switch needs to be driven
		 * to the low state to activate the routing indicated by
		 * the corresponding IS_ENABLED() macro in the table,
		 * so configure the pin as output with the proper initial
		 * state.
		 */
		uint32_t flag = (cfg[i].val ? GPIO_OUTPUT_LOW
					 : GPIO_OUTPUT_HIGH);
		err = gpio_pin_configure(port, cfg[i].pin, flag);
		if (err) {
			return cfg[i].pin;
		}

		LOG_DBG("port %p, pin %u -> %u",
			port, cfg[i].pin, !cfg[i].val);
	}

	return 0;
}

static void chip_reset(struct device *gpio,
		       struct gpio_callback *cb, uint32_t pins)
{
	const uint32_t stamp = k_cycle_get_32();

	printk("GPIO reset line asserted, device reset.\n");
	printk("Bye @ cycle32 %u\n", stamp);

	NVIC_SystemReset();
}

static void reset_pin_wait_low(struct device *port, uint32_t pin)
{
	int val;

	/* Wait until the pin is pulled low */
	do {
		val = gpio_pin_get_raw(port, pin);
	} while (val > 0);
}

static int reset_pin_configure(struct device *p0, struct device *p1)
{
	int err;
	uint32_t pin = 0;
	struct device *port = NULL;

	static struct gpio_callback gpio_ctx;

	/* MCU interface pins 0-2 */
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET_P0_17)) {
		port = p0;
		pin = 17;
	}
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET_P0_20)) {
		port = p0;
		pin = 20;
	}
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET_P0_15)) {
		port = p0;
		pin = 15;
	}
	/* MCU interface pins 3-6 */
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET_P0_22)) {
		port = p0;
		pin = 22;
	}
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET_P1_04)) {
		port = p1;
		pin = 4;
	}
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET_P1_02)) {
		port = p1;
		pin = 2;
	}

	__ASSERT_NO_MSG(port != NULL);

	err = gpio_pin_configure(port, pin, GPIO_INPUT | GPIO_PULL_DOWN);
	if (err) {
		return err;
	}

	gpio_init_callback(&gpio_ctx, chip_reset, BIT(pin));

	err = gpio_add_callback(port, &gpio_ctx);
	if (err) {
		return err;
	}

	err = gpio_pin_interrupt_configure(port, pin, GPIO_INT_EDGE_RISING);
	if (err) {
		return err;
	}

	/* Wait until the pin is pulled low before continuing.
	 * This lets the other side ensure that they are ready.
	 */
	LOG_INF("GPIO reset line enabled on pin %s.%02u, holding..",
		port == p0 ? "P0" : "P1", pin);

	reset_pin_wait_low(port, pin);

	return 0;
}

static int init(struct device *dev)
{
	int rc;
	struct device *p0;
	struct device *p1;

	p0 = device_get_binding(DT_LABEL(DT_NODELABEL(gpio0)));
	if (!p0) {
		LOG_ERR("GPIO device " DT_LABEL(DT_NODELABEL(gpio0))
			" not found!");
		return -EIO;
	}

	p1 = device_get_binding(DT_LABEL(DT_NODELABEL(gpio1)));
	if (!p1) {
		LOG_ERR("GPIO device " DT_LABEL(DT_NODELABEL(gpio1))
			" not found!");
		return -EIO;
	}

	/* Configure pins on each port */
	rc = pins_configure(p0, pins_on_p0, ARRAY_SIZE(pins_on_p0));
	if (rc) {
		LOG_ERR("Error while configuring pin P0.%02d", rc);
		return -EIO;
	}
	rc = pins_configure(p1, pins_on_p1, ARRAY_SIZE(pins_on_p1));
	if (rc) {
		LOG_ERR("Error while configuring pin P1.%02d", rc);
		return -EIO;
	}

	config_print();

	/* Make sure to configure the switches before initializing
	 * the GPIO reset pin, so that we are connected to
	 * the nRF9160 before enabling our interrupt.
	 */
	if (IS_ENABLED(CONFIG_BOARD_NRF9160DK_NRF52840_RESET)) {
		rc = reset_pin_configure(p0, p1);
		if (rc) {
			LOG_ERR("Unable to configure reset pin, err %d", rc);
			return -EIO;
		}
	}

	LOG_INF("Board configured.");

	return 0;
}

SYS_INIT(init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
