/*
 * Copyright (c) 2023 Frontgrade Gaisler AB
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * Driver for GRLIB GRGPIO revision 2.
 * - iflag determine pending interrupt.
 * - interrupt map decides interrupt number if implemented.
 * - logic or/and/xor registers used when possible
 */

#define DT_DRV_COMPAT gaisler_grgpio

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include "gpio_grgpio.h"

#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(gpio_grgpio2);

struct cfg {
	struct gpio_driver_config common;
	volatile struct grgpio_regs *regs;
	int interrupt;
};

struct data {
	struct gpio_driver_data common;
	struct k_spinlock lock;
	sys_slist_t cb;
	uint32_t imask;
	uint32_t connected;
	int irqgen;
};

static void grgpio_isr(const struct device *dev);

static int pin_configure(const struct device *dev,
				gpio_pin_t pin, gpio_flags_t flags)
{
	const struct cfg *cfg = dev->config;
	struct data *data = dev->data;
	volatile struct grgpio_regs *regs = cfg->regs;
	uint32_t mask = 1 << pin;

	if (flags & GPIO_SINGLE_ENDED) {
		return -ENOTSUP;
	}

	if (flags == GPIO_DISCONNECTED) {
		return -ENOTSUP;
	}

	if ((flags & GPIO_DIR_MASK) == (GPIO_INPUT | GPIO_OUTPUT)) {
		return -ENOTSUP;
	}

	if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) {
		return -ENOTSUP;
	}

	if (flags & GPIO_OUTPUT) {
		k_spinlock_key_t key;

		/*
		 * Register operations are atomic, but do the sequence under
		 * lock so it serializes.
		 */
		key = k_spin_lock(&data->lock);
		if (flags & GPIO_OUTPUT_INIT_HIGH) {
			regs->output_or = mask;
		} else if (flags & GPIO_OUTPUT_INIT_LOW) {
			regs->output_and = ~mask;
		}
		regs->dir_or = mask;
		k_spin_unlock(&data->lock, key);
	} else {
		regs->dir_and = ~mask;
	}

	return 0;
}

static int port_get_raw(const struct device *dev, gpio_port_value_t *value)
{
	const struct cfg *cfg = dev->config;

	*value = cfg->regs->data;
	return 0;
}

static int port_set_masked_raw(const struct device *dev,
					  gpio_port_pins_t mask,
					  gpio_port_value_t value)
{
	const struct cfg *cfg = dev->config;
	struct data *data = dev->data;
	volatile struct grgpio_regs *regs = cfg->regs;
	uint32_t port_val;
	k_spinlock_key_t key;

	value &= mask;
	key = k_spin_lock(&data->lock);
	port_val = (regs->output & ~mask) | value;
	regs->output = port_val;
	k_spin_unlock(&data->lock, key);

	return 0;
}

static int port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins)
{
	const struct cfg *cfg = dev->config;
	volatile struct grgpio_regs *regs = cfg->regs;

	regs->output_or = pins;
	return 0;
}

static int port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins)
{
	const struct cfg *cfg = dev->config;
	volatile struct grgpio_regs *regs = cfg->regs;

	regs->output_and = ~pins;
	return 0;
}

static int port_toggle_bits(const struct device *dev, gpio_port_pins_t pins)
{
	const struct cfg *cfg = dev->config;
	volatile struct grgpio_regs *regs = cfg->regs;

	regs->output_xor = pins;
	return 0;
}

static uint32_t get_pending_int(const struct device *dev)
{
	const struct cfg *cfg = dev->config;
	volatile struct grgpio_regs *regs = cfg->regs;

	return regs->iflag;
}

static int pin_interrupt_configure(const struct device *dev,
					      gpio_pin_t pin,
					      enum gpio_int_mode mode,
					      enum gpio_int_trig trig)
{
	const struct cfg *cfg = dev->config;
	struct data *data = dev->data;
	volatile struct grgpio_regs *regs = cfg->regs;
	int ret = 0;
	const uint32_t mask = 1 << pin;
	uint32_t polmask;
	k_spinlock_key_t key;

	if ((mask & data->imask) == 0) {
		/* This pin can not generate interrupt */
		return -ENOTSUP;
	}
	if (mode != GPIO_INT_MODE_DISABLED) {
		if (trig == GPIO_INT_TRIG_LOW) {
			polmask = 0;
		} else if (trig == GPIO_INT_TRIG_HIGH) {
			polmask = mask;
		} else {
			return -ENOTSUP;
		}
	}
	key = k_spin_lock(&data->lock);
	if (mode == GPIO_INT_MODE_DISABLED) {
		regs->imask_and = ~mask;
	} else if (mode == GPIO_INT_MODE_LEVEL) {
		regs->imask_and = ~mask;
		regs->iedge &= ~mask;
		regs->ipol = (regs->ipol & ~mask) | polmask;
		regs->imask_or = mask;
	} else if (mode == GPIO_INT_MODE_EDGE) {
		regs->imask_and = ~mask;
		regs->iedge |= mask;
		regs->ipol = (regs->ipol & ~mask) | polmask;
		regs->imask_or = mask;
	} else {
		ret = -ENOTSUP;
	}
	k_spin_unlock(&data->lock, key);

	/* Remove old interrupt history for this pin. */
	regs->iflag = mask;

	int interrupt = cfg->interrupt;
	const int irqgen = data->irqgen;

	if (irqgen == 0) {
		interrupt += pin;
	} else if (irqgen == 1) {
		;
	} else if (irqgen < 32) {
		/* look up interrupt number in GRGPIO interrupt map */
		uint32_t val = regs->irqmap[pin/4];

		val >>= (3 - pin % 4) * 8;
		interrupt += (val & 0x1f);
	}

	if (interrupt && ((1 << interrupt) & data->connected) == 0) {
		irq_connect_dynamic(
			interrupt,
			0,
			(void (*)(const void *)) grgpio_isr,
			dev,
			0
		);
		irq_enable(interrupt);
		data->connected |= 1 << interrupt;
	}

	return ret;
}

static int manage_callback(const struct device *dev,
			   struct gpio_callback *callback,
			   bool set)
{
	struct data *data = dev->data;

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

static void grgpio_isr(const struct device *dev)
{
	const struct cfg *cfg = dev->config;
	struct data *data = dev->data;
	volatile struct grgpio_regs *regs = cfg->regs;
	uint32_t pins;

	/* no locking needed when iflag is implemented */
	pins = regs->iflag;
	if (pins == 0) {
		return;
	}
	regs->iflag = pins;
	gpio_fire_callbacks(&data->cb, dev, pins);
}

static int grgpio_init(const struct device *dev)
{
	const struct cfg *cfg = dev->config;
	struct data *data = dev->data;
	volatile struct grgpio_regs *regs = cfg->regs;

	data->irqgen = (regs->cap & GRGPIO_CAP_IRQGEN) >> GRGPIO_CAP_IRQGEN_BIT;
	regs->dir = 0;
	/* Mask all Interrupts */
	regs->imask = 0;
	/* Make IRQ Rising edge triggered default */
	regs->ipol = 0xffffffff;
	regs->iedge = 0xffffffff;
	regs->iflag = 0xffffffff;
	/* Read what I/O lines have IRQ support */
	data->imask = regs->ipol;

	return 0;
}

static DEVICE_API(gpio, driver_api) = {
	.pin_configure                  = pin_configure,
	.port_get_raw                   = port_get_raw,
	.port_set_masked_raw            = port_set_masked_raw,
	.port_set_bits_raw              = port_set_bits_raw,
	.port_clear_bits_raw            = port_clear_bits_raw,
	.port_toggle_bits               = port_toggle_bits,
	.pin_interrupt_configure        = pin_interrupt_configure,
	.manage_callback                = manage_callback,
	.get_pending_int                = get_pending_int,
};

#define GRGPIO_INIT(n)							\
	static const struct cfg cfg_##n = {				\
		.common = {						\
			.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),\
		},							\
		.regs = (void *) DT_INST_REG_ADDR(n),			\
		.interrupt = DT_INST_IRQN(n),				\
	};								\
	static struct data data_##n;					\
									\
	DEVICE_DT_INST_DEFINE(n,					\
			    grgpio_init,				\
			    NULL,					\
			    &data_##n,					\
			    &cfg_##n,					\
			    POST_KERNEL,				\
			    CONFIG_GPIO_INIT_PRIORITY,			\
			    &driver_api					\
			   );

DT_INST_FOREACH_STATUS_OKAY(GRGPIO_INIT)
