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

#include "analog.h"

#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/irq.h>
#include <zephyr/drivers/gpio/gpio_utils.h>


/* Driver dts compatibility: telink,b91_gpio */
#define DT_DRV_COMPAT telink_b91_gpio

/* Get GPIO instance */
#define GET_GPIO(dev)           ((volatile struct gpio_b91_t *)	\
				 ((const struct gpio_b91_config *)dev->config)->gpio_base)

/* Get GPIO IRQ number defined in dts */
#define GET_IRQ_NUM(dev)        (((const struct gpio_b91_config *)dev->config)->irq_num)

/* Get GPIO IRQ priority defined in dts */
#define GET_IRQ_PRIORITY(dev)   (((const struct gpio_b91_config *)dev->config)->irq_priority)

/* Get GPIO port number: port A - 0, port B - 1, ..., port F - 5  */
#define GET_PORT_NUM(gpio)      ((uint8_t)(((uint32_t)gpio - DT_REG_ADDR(DT_NODELABEL(gpioa))) / \
					   DT_REG_SIZE(DT_NODELABEL(gpioa))))

/* Check that gpio is port C */
#define IS_PORT_C(gpio)         ((uint32_t)gpio == DT_REG_ADDR(DT_NODELABEL(gpioc)))

/* Check that gpio is port D */
#define IS_PORT_D(gpio)         ((uint32_t)gpio == DT_REG_ADDR(DT_NODELABEL(gpiod)))

/* Check that 'inst' has only 1 interrupt selected in dts */
#define IS_INST_IRQ_EN(inst)    (DT_NUM_IRQS(DT_DRV_INST(inst)) == 1)

/* Max pin number per port (pin 0..7) */
#define PIN_NUM_MAX ((uint8_t)7u)

/* IRQ Enable registers */
#define reg_irq_risc0_en(i)      REG_ADDR8(0x140338 + i)
#define reg_irq_risc1_en(i)      REG_ADDR8(0x140340 + i)

/* Pull-up/down resistors */
#define GPIO_PIN_UP_DOWN_FLOAT   ((uint8_t)0u)
#define GPIO_PIN_PULLDOWN_100K   ((uint8_t)2u)
#define GPIO_PIN_PULLUP_10K      ((uint8_t)3u)

/* GPIO interrupt types */
#define INTR_RISING_EDGE         ((uint8_t)0u)
#define INTR_FALLING_EDGE        ((uint8_t)1u)
#define INTR_HIGH_LEVEL          ((uint8_t)2u)
#define INTR_LOW_LEVEL           ((uint8_t)3u)

/* Supported IRQ numbers */
#define IRQ_GPIO                 ((uint8_t)25u)
#define IRQ_GPIO2_RISC0          ((uint8_t)26u)
#define IRQ_GPIO2_RISC1          ((uint8_t)27u)


/* B91 GPIO registers structure */
struct gpio_b91_t {
	uint8_t input;                  /* Input: read GPI input */
	uint8_t ie;                     /* IE: input enable, high active. 1: enable, 0: disable */
	uint8_t oen;                    /* OEN: output enable, low active. 0: enable, 1: disable */
	uint8_t output;                 /* Output: configure GPIO output */
	uint8_t polarity;               /* Polarity: interrupt polarity: rising, falling */
	uint8_t ds;                     /* DS: drive strength. 1: maximum (default), 0: minimal */
	uint8_t actas_gpio;             /* Act as GPIO: enable (1) or disable (0) GPIO function */
	uint8_t irq_en;                 /* Act as GPIO: enable (1) or disable (0) GPIO function */
};

/* GPIO driver configuration structure */
struct gpio_b91_config {
	struct gpio_driver_config common;
	uint32_t gpio_base;
	uint32_t irq_num;
	uint8_t irq_priority;
	void (*pirq_connect)(void);
};

/* GPIO driver data structure */
struct gpio_b91_data {
	struct gpio_driver_data common; /* driver data */
	sys_slist_t callbacks;          /* list of callbacks */
};


/* Set IRQ Enable bit based on IRQ number */
static inline void gpiob_b91_irq_en_set(const struct device *dev, gpio_pin_t pin)
{
	uint8_t irq = GET_IRQ_NUM(dev);

	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	if (irq == IRQ_GPIO) {
		BM_SET(gpio->irq_en, BIT(pin));
	} else if (irq == IRQ_GPIO2_RISC0) {
		BM_SET(reg_irq_risc0_en(GET_PORT_NUM(gpio)), BIT(pin));
	} else if (irq == IRQ_GPIO2_RISC1) {
		BM_SET(reg_irq_risc1_en(GET_PORT_NUM(gpio)), BIT(pin));
	} else {
		__ASSERT(false, "Not supported GPIO IRQ number.");
	}
}

/* Clear IRQ Enable bit based on IRQ number */
static inline void gpiob_b91_irq_en_clr(const struct device *dev, gpio_pin_t pin)
{
	uint8_t irq = GET_IRQ_NUM(dev);
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	if (irq == IRQ_GPIO) {
		BM_CLR(gpio->irq_en, BIT(pin));
	} else if (irq == IRQ_GPIO2_RISC0) {
		BM_CLR(reg_irq_risc0_en(GET_PORT_NUM(gpio)), BIT(pin));
	} else if (irq == IRQ_GPIO2_RISC1) {
		BM_CLR(reg_irq_risc1_en(GET_PORT_NUM(gpio)), BIT(pin));
	}
}

/* Get IRQ Enable register value */
static inline uint8_t gpio_b91_irq_en_get(const struct device *dev)
{
	uint8_t status = 0;
	uint8_t irq = GET_IRQ_NUM(dev);
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	if (irq == IRQ_GPIO) {
		status = gpio->irq_en;
	} else if (irq == IRQ_GPIO2_RISC0) {
		status = reg_irq_risc0_en(GET_PORT_NUM(gpio));
	} else if (irq == IRQ_GPIO2_RISC1) {
		status = reg_irq_risc1_en(GET_PORT_NUM(gpio));
	}

	return status;
}

/* Clear IRQ Status bit */
static inline void gpio_b91_irq_status_clr(uint8_t irq)
{
	gpio_irq_status_e status = 0;

	if (irq == IRQ_GPIO) {
		status = FLD_GPIO_IRQ_CLR;
	} else if (irq == IRQ_GPIO2_RISC0) {
		status = FLD_GPIO_IRQ_GPIO2RISC0_CLR;
	} else if (irq == IRQ_GPIO2_RISC1) {
		status = FLD_GPIO_IRQ_GPIO2RISC1_CLR;
	}

	reg_gpio_irq_clr = status;
}

/* Set pin's irq type */
void gpio_b91_irq_set(const struct device *dev, gpio_pin_t pin,
		      uint8_t trigger_type)
{
	uint8_t irq_lvl = 0;
	uint8_t irq_mask = 0;
	uint8_t irq_num = GET_IRQ_NUM(dev);
	uint8_t irq_prioriy = GET_IRQ_PRIORITY(dev);
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	/* Get level and mask based on IRQ number */
	if (irq_num == IRQ_GPIO) {
		irq_lvl = FLD_GPIO_IRQ_LVL_GPIO;
		irq_mask = FLD_GPIO_IRQ_MASK_GPIO;
	} else if (irq_num == IRQ_GPIO2_RISC0) {
		irq_lvl = FLD_GPIO_IRQ_LVL_GPIO2RISC0;
		irq_mask = FLD_GPIO_IRQ_MASK_GPIO2RISC0;
	} else if (irq_num == IRQ_GPIO2_RISC1) {
		irq_lvl = FLD_GPIO_IRQ_LVL_GPIO2RISC1;
		irq_mask = FLD_GPIO_IRQ_MASK_GPIO2RISC1;
	}

	/* Set polarity and level */
	switch (trigger_type) {
	case INTR_RISING_EDGE:
		BM_CLR(gpio->polarity, BIT(pin));
		BM_CLR(reg_gpio_irq_risc_mask, irq_lvl);
		break;

	case INTR_FALLING_EDGE:
		BM_SET(gpio->polarity, BIT(pin));
		BM_CLR(reg_gpio_irq_risc_mask, irq_lvl);
		break;

	case INTR_HIGH_LEVEL:
		BM_CLR(gpio->polarity, BIT(pin));
		BM_SET(reg_gpio_irq_risc_mask, irq_lvl);
		break;

	case INTR_LOW_LEVEL:
		BM_SET(gpio->polarity, BIT(pin));
		BM_SET(reg_gpio_irq_risc_mask, irq_lvl);
		break;
	}

	if (irq_num == IRQ_GPIO) {
		reg_gpio_irq_ctrl |= FLD_GPIO_CORE_INTERRUPT_EN;
	}
	gpio_b91_irq_status_clr(irq_num);
	BM_SET(reg_gpio_irq_risc_mask, irq_mask);

	/* Enable peripheral interrupt */
	gpiob_b91_irq_en_set(dev, pin);

	/* Enable PLIC interrupt */
	riscv_plic_irq_enable(irq_num);
	riscv_plic_set_priority(irq_num, irq_prioriy);
}

/* Set pin's pull-up/down resistor */
static void gpio_b91_up_down_res_set(volatile struct gpio_b91_t *gpio,
				     gpio_pin_t pin,
				     uint8_t up_down_res)
{
	uint8_t val;
	uint8_t mask;
	uint8_t analog_reg;

	pin = BIT(pin);
	val = up_down_res & 0x03;
	analog_reg = 0x0e + (GET_PORT_NUM(gpio) << 1) + ((pin & 0xf0) ? 1 : 0);

	if (pin & 0x11) {
		val = val << 0;
		mask = 0xfc;
	} else if (pin & 0x22) {
		val = val << 2;
		mask = 0xf3;
	} else if (pin & 0x44) {
		val = val << 4;
		mask = 0xcf;
	} else if (pin & 0x88) {
		val = val << 6;
		mask = 0x3f;
	} else {
		return;
	}

	analog_write_reg8(analog_reg, (analog_read_reg8(analog_reg) & mask) | val);
}

/* Config Pin pull-up / pull-down resistors */
static void gpio_b91_config_up_down_res(volatile struct gpio_b91_t *gpio,
					gpio_pin_t pin,
					gpio_flags_t flags)
{
	if ((flags & GPIO_PULL_UP) != 0) {
		gpio_b91_up_down_res_set(gpio, pin, GPIO_PIN_PULLUP_10K);
	} else if ((flags & GPIO_PULL_DOWN) != 0) {
		gpio_b91_up_down_res_set(gpio, pin, GPIO_PIN_PULLDOWN_100K);
	} else {
		gpio_b91_up_down_res_set(gpio, pin, GPIO_PIN_UP_DOWN_FLOAT);
	}
}

/* Config Pin In/Out direction */
static void gpio_b91_config_in_out(volatile struct gpio_b91_t *gpio,
				   gpio_pin_t pin,
				   gpio_flags_t flags)
{
	uint8_t ie_addr = 0;

	/* Port C and D Input Enable registers are located in another place: analog */
	if (IS_PORT_C(gpio)) {
		ie_addr = areg_gpio_pc_ie;
	} else if (IS_PORT_D(gpio)) {
		ie_addr = areg_gpio_pd_ie;
	}

	/* Enable/disable output */
	WRITE_BIT(gpio->oen, pin, ~flags & GPIO_OUTPUT);

	/* Enable/disable input */
	if (ie_addr != 0) {
		/* Port C and D are located in analog space */
		if (flags & GPIO_INPUT) {
			analog_write_reg8(ie_addr, analog_read_reg8(ie_addr) | BIT(pin));
		} else {
			analog_write_reg8(ie_addr, analog_read_reg8(ie_addr) & (~BIT(pin)));
		}
	} else {
		/* Input Enable registers of all other ports are located in common GPIO space */
		WRITE_BIT(gpio->ie, pin, flags & GPIO_INPUT);
	}
}

/* GPIO driver initialization */
static int gpio_b91_init(const struct device *dev)
{
	const struct gpio_b91_config *cfg = dev->config;

	cfg->pirq_connect();

	return 0;
}

/* API implementation: pin_configure */
static int gpio_b91_pin_configure(const struct device *dev,
				  gpio_pin_t pin,
				  gpio_flags_t flags)
{
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	/* Check input parameters: pin number */
	if (pin > PIN_NUM_MAX) {
		return -ENOTSUP;
	}

	/* Check input parameters: open-source and open-drain */
	if ((flags & GPIO_SINGLE_ENDED) != 0) {
		return -ENOTSUP;
	}

	/* Check input parameters: simultaneous in/out mode */
	if ((flags & GPIO_OUTPUT) && (flags & GPIO_INPUT)) {
		return -ENOTSUP;
	}

	/* Set GPIO init state if defined to avoid glitches */
	if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) {
		gpio->output |= BIT(pin);
	} else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) {
		gpio->output &= ~BIT(pin);
	}

	/* GPIO function enable */
	WRITE_BIT(gpio->actas_gpio, BIT(pin), 1);

	/* Set GPIO pull-up / pull-down resistors */
	gpio_b91_config_up_down_res(gpio, pin, flags);

	/* Enable/disable input/output */
	gpio_b91_config_in_out(gpio, pin, flags);

	return 0;
}

/* API implementation: port_get_raw */
static int gpio_b91_port_get_raw(const struct device *dev,
				 gpio_port_value_t *value)
{
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	*value = gpio->input;

	return 0;
}

/* API implementation: port_set_masked_raw */
static int gpio_b91_port_set_masked_raw(const struct device *dev,
					gpio_port_pins_t mask,
					gpio_port_value_t value)
{
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	gpio->output = (gpio->output & ~mask) | (value & mask);

	return 0;
}

/* API implementation: port_set_bits_raw */
static int gpio_b91_port_set_bits_raw(const struct device *dev,
				      gpio_port_pins_t mask)
{
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	gpio->output |= mask;

	return 0;
}

/* API implementation: port_clear_bits_raw */
static int gpio_b91_port_clear_bits_raw(const struct device *dev,
					gpio_port_pins_t mask)
{
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	gpio->output &= ~mask;

	return 0;
}

/* API implementation: port_toggle_bits */
static int gpio_b91_port_toggle_bits(const struct device *dev,
				     gpio_port_pins_t mask)
{
	volatile struct gpio_b91_t *gpio = GET_GPIO(dev);

	gpio->output ^= mask;

	return 0;
}

/* API implementation: interrupts handler */
#if IS_INST_IRQ_EN(0) || IS_INST_IRQ_EN(1) || IS_INST_IRQ_EN(2) || \
	IS_INST_IRQ_EN(3) || IS_INST_IRQ_EN(4)
static void gpio_b91_irq_handler(const struct device *dev)
{
	struct gpio_b91_data *data = dev->data;
	uint8_t irq = GET_IRQ_NUM(dev);
	uint8_t status = gpio_b91_irq_en_get(dev);

	gpio_b91_irq_status_clr(irq);
	gpio_fire_callbacks(&data->callbacks, dev, status);
}
#endif

/* API implementation: pin_interrupt_configure */
static int gpio_b91_pin_interrupt_configure(const struct device *dev,
					    gpio_pin_t pin,
					    enum gpio_int_mode mode,
					    enum gpio_int_trig trig)
{
	int ret_status = 0;

	switch (mode) {
	case GPIO_INT_MODE_DISABLED:                /* GPIO interrupt disable */
		gpiob_b91_irq_en_clr(dev, pin);
		break;

	case GPIO_INT_MODE_LEVEL:
		if (trig == GPIO_INT_TRIG_HIGH) {       /* GPIO interrupt High level */
			gpio_b91_irq_set(dev, pin, INTR_HIGH_LEVEL);
		} else if (trig == GPIO_INT_TRIG_LOW) { /* GPIO interrupt Low level */
			gpio_b91_irq_set(dev, pin, INTR_LOW_LEVEL);
		} else {
			ret_status = -ENOTSUP;
		}
		break;

	case GPIO_INT_MODE_EDGE:
		if (trig == GPIO_INT_TRIG_HIGH) {       /* GPIO interrupt Rising edge */
			gpio_b91_irq_set(dev, pin, INTR_RISING_EDGE);
		} else if (trig == GPIO_INT_TRIG_LOW) { /* GPIO interrupt Falling edge */
			gpio_b91_irq_set(dev, pin, INTR_FALLING_EDGE);
		} else {
			ret_status = -ENOTSUP;
		}
		break;

	default:
		ret_status = -ENOTSUP;
		break;
	}

	return ret_status;
}

/* API implementation: manage_callback */
static int gpio_b91_manage_callback(const struct device *dev,
				    struct gpio_callback *callback,
				    bool set)
{
	struct gpio_b91_data *data = dev->data;

	return gpio_manage_callback(&data->callbacks, callback, set);
}

/* GPIO driver APIs structure */
static const struct gpio_driver_api gpio_b91_driver_api = {
	.pin_configure = gpio_b91_pin_configure,
	.port_get_raw = gpio_b91_port_get_raw,
	.port_set_masked_raw = gpio_b91_port_set_masked_raw,
	.port_set_bits_raw = gpio_b91_port_set_bits_raw,
	.port_clear_bits_raw = gpio_b91_port_clear_bits_raw,
	.port_toggle_bits = gpio_b91_port_toggle_bits,
	.pin_interrupt_configure = gpio_b91_pin_interrupt_configure,
	.manage_callback = gpio_b91_manage_callback
};

/* If instance 0 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0
static void gpio_b91_irq_connect_0(void)
{
	#if IS_INST_IRQ_EN(0)
	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
		    gpio_b91_irq_handler,
		    DEVICE_DT_INST_GET(0), 0);
	#endif
}
#endif

/* If instance 1 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 1
static void gpio_b91_irq_connect_1(void)
{
	#if IS_INST_IRQ_EN(1)
	IRQ_CONNECT(DT_INST_IRQN(1), DT_INST_IRQ(1, priority),
		    gpio_b91_irq_handler,
		    DEVICE_DT_INST_GET(1), 0);
	#endif
}
#endif

/* If instance 2 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 2
static void gpio_b91_irq_connect_2(void)
{
	#if IS_INST_IRQ_EN(2)
	IRQ_CONNECT(DT_INST_IRQN(2), DT_INST_IRQ(2, priority),
		    gpio_b91_irq_handler,
		    DEVICE_DT_INST_GET(2), 0);
	#endif
}
#endif

/* If instance 3 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 3
static void gpio_b91_irq_connect_3(void)
{
	#if IS_INST_IRQ_EN(3)
	IRQ_CONNECT(DT_INST_IRQN(3), DT_INST_IRQ(3, priority),
		    gpio_b91_irq_handler,
		    DEVICE_DT_INST_GET(3), 0);
	#endif
}
#endif

/* If instance 4 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 4
static void gpio_b91_irq_connect_4(void)
{
	#if IS_INST_IRQ_EN(4)
	IRQ_CONNECT(DT_INST_IRQN(4), DT_INST_IRQ(4, priority),
		    gpio_b91_irq_handler,
		    DEVICE_DT_INST_GET(4), 0);
	#endif
}
#endif

/* GPIO driver registration */
#define GPIO_B91_INIT(n)						    \
	static const struct gpio_b91_config gpio_b91_config_##n = {	    \
		.common = {						    \
			.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n) \
		},							    \
		.gpio_base = DT_INST_REG_ADDR(n),			    \
		.irq_num = DT_INST_IRQN(n),				    \
		.irq_priority = DT_INST_IRQ(n, priority),		    \
		.pirq_connect = gpio_b91_irq_connect_##n		    \
	};								    \
	static struct gpio_b91_data gpio_b91_data_##n;			    \
									    \
	DEVICE_DT_INST_DEFINE(n, gpio_b91_init,				    \
			      NULL,					    \
			      &gpio_b91_data_##n,			    \
			      &gpio_b91_config_##n,			    \
			      PRE_KERNEL_1,				    \
			      CONFIG_GPIO_INIT_PRIORITY,		    \
			      &gpio_b91_driver_api);

DT_INST_FOREACH_STATUS_OKAY(GPIO_B91_INIT)
