/*
 * (C) Copyright 2020 Rockchip Electronics Co., Ltd
 *
 * SPDX-License-Identifier:     GPL-2.0+
 */

#include <dm.h>
#include <malloc.h>
#include "irq-internal.h"

typedef enum GPIOIntType {
	GPIOLevelLow = 0,
	GPIOLevelHigh,
	GPIOEdgelFalling,
	GPIOEdgelRising
} eGPIOIntType_t;

typedef enum eGPIOPinLevel {
	GPIO_LOW = 0,
	GPIO_HIGH
} eGPIOPinLevel_t;

typedef enum eGPIOPinDirection {
	GPIO_IN = 0,
	GPIO_OUT
} eGPIOPinDirection_t;

#define GPIO_SWPORT_DR		0x00
#define GPIO_SWPORT_DDR		0x08
#define GPIO_INTEN		0x10
#define GPIO_INTEN_L		0x10
#define GPIO_INTEN_H		0x14
#define GPIO_INTMASK		0x18
#define GPIO_INTTYPE_LEVEL	0x20
#define GPIO_INTTYPE_LEVEL_L	0x20
#define GPIO_INTTYPE_LEVEL_H	0x24
#define GPIO_INT_POLARITY	0x28
#define GPIO_DEBOUNCE		0x38
#define GPIO_INT_STATUS		0x50
#define GPIO_INT_RAWSTATUS	0x58
#define GPIO_PORTS_EOI		0x60
#define GPIO_EXT_PORT		0x70

#define WMSK_SETBIT(n)		(n << 16 | n)
#define WMSK_CLRBIT(n)		(n << 16)
#define REG_PLUS4(off, n)	(off + (n >= BIT(16) ? 4 : 0))
#define BIT_SUB16(n)		(n >= BIT(16) ? (n >> 16) : n)

static inline unsigned offset_to_bit(unsigned offset)
{
	return (1 << offset);
}

static void gpio_bit_op(void __iomem *regbase, unsigned int offset,
			u32 bit, unsigned char flag)
{
	u32 val;

	offset = REG_PLUS4(offset, bit);
	bit = BIT_SUB16(bit);

	val = flag ? WMSK_SETBIT(bit) : WMSK_CLRBIT(bit);
	writel(val, regbase + offset);
}

static int gpio_bit_rd(void __iomem *regbase, unsigned int offset, u32 bit)
{
	offset = REG_PLUS4(offset, bit);
	bit = BIT_SUB16(bit);

	return readl(regbase + offset) & bit ? 1 : 0;
}

static void gpio_irq_unmask(void __iomem *regbase, unsigned int bit)
{
	gpio_bit_op(regbase, GPIO_INTEN, bit, 1);
}

static void gpio_irq_mask(void __iomem *regbase, unsigned int bit)
{
	gpio_bit_op(regbase, GPIO_INTEN, bit, 0);
}

static void gpio_irq_ack(void __iomem *regbase, unsigned int bit)
{
	gpio_bit_op(regbase, GPIO_PORTS_EOI, bit, 1);
}

static void generic_gpio_handle_irq(int irq, void *data __always_unused)
{
	struct gpio_bank *bank = gpio_id_to_bank(irq - IRQ_GPIO0);
	unsigned gpio_irq, pin, h_pin, unmasked = 0;
	u32 isr, ilr_l, ilr_h;

	isr = readl(bank->regbase + GPIO_INT_STATUS);
	ilr_l = readl(bank->regbase + GPIO_INTTYPE_LEVEL_L);
	ilr_h = readl(bank->regbase + GPIO_INTTYPE_LEVEL_H);
	gpio_irq = bank->irq_base;

	while (isr) {
		pin = fls(isr) - 1;

		/* first mask and ack irq */
		gpio_irq_mask(bank->regbase, offset_to_bit(pin));
		gpio_irq_ack(bank->regbase, offset_to_bit(pin));

		/*
		 * If gpio is edge triggered, clear condition before executing
		 * the handler, so that we don't miss next edges trigger.
		 */
		if (pin < 16) {
			if (ilr_l & (1 << pin)) {
				unmasked = 1;
				gpio_irq_unmask(bank->regbase, offset_to_bit(pin));
			}
		} else {
			h_pin = pin - 16;
			if (ilr_h & (1 << h_pin)) {
				unmasked = 1;
				gpio_irq_unmask(bank->regbase, offset_to_bit(pin));
			}
		}
		__generic_gpio_handle_irq(gpio_irq + pin);

		isr &= ~(1 << pin);

		if (!unmasked)
			gpio_irq_unmask(bank->regbase, offset_to_bit(pin));
	}
}

static void gpio_set_intr_type(void __iomem *regbase,
			       unsigned int bit,
			       eGPIOIntType_t type)
{
	switch (type) {
	case GPIOLevelLow:
		gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 0);
		gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 0);
		break;
	case GPIOLevelHigh:
		gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 0);
		gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 1);
		break;
	case GPIOEdgelFalling:
		gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 1);
		gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 0);
		break;
	case GPIOEdgelRising:
		gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 1);
		gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 1);
		break;
	}
}

static int gpio_get_intr_type(void __iomem *regbase,
			      unsigned int bit)
{
	u32 polarity, level, magic = 0;
	int type;

	polarity = gpio_bit_rd(regbase, GPIO_INT_POLARITY, bit);
	level = gpio_bit_rd(regbase, GPIO_INTTYPE_LEVEL, bit);
	magic = (polarity << 1) | (level << 0);

	switch (magic) {
	case 0x00:
		type = GPIOLevelLow;
		break;
	case 0x02:
		type = GPIOLevelHigh;
		break;
	case 0x01:
		type = GPIOEdgelFalling;
		break;
	case 0x03:
		type = GPIOEdgelRising;
		break;
	default:
		type = -EINVAL;
	}

	return type;
}

static int gpio_irq_set_type(int gpio_irq, unsigned int type)
{
	int gpio = irq_to_gpio(gpio_irq);
	struct gpio_bank *bank = gpio_to_bank(gpio);
	eGPIOIntType_t int_type = 0;

	if (!bank)
		return -EINVAL;

	gpio &= GPIO_PIN_MASK;
	if (gpio >= bank->ngpio)
		return -EINVAL;

	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		int_type = GPIOEdgelRising;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		int_type = GPIOEdgelFalling;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		int_type = GPIOLevelHigh;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		int_type = GPIOLevelLow;
		break;
	default:
		return -EINVAL;
	}

	/* Before set interrupt type, gpio must set input */
	gpio_bit_op(bank->regbase, GPIO_SWPORT_DDR,
		    offset_to_bit(gpio), GPIO_IN);
	gpio_set_intr_type(bank->regbase, offset_to_bit(gpio), int_type);

	return 0;
}

static int gpio_irq_revert_type(int gpio_irq)
{
	int gpio = irq_to_gpio(gpio_irq);
	struct gpio_bank *bank = gpio_to_bank(gpio);
	eGPIOIntType_t int_type = 0;
	int type;

	if (!bank)
		return -EINVAL;

	gpio &= GPIO_PIN_MASK;
	if (gpio >= bank->ngpio)
		return -EINVAL;

	type = gpio_get_intr_type(bank->regbase, offset_to_bit(gpio));
	switch (type) {
	case GPIOEdgelFalling:
		int_type = GPIOEdgelRising;
		break;
	case GPIOEdgelRising:
		int_type = GPIOEdgelFalling;
		break;
	case GPIOLevelHigh:
		int_type = GPIOLevelLow;
		break;
	case GPIOLevelLow:
		int_type = GPIOLevelHigh;
		break;
	default:
		return -EINVAL;
	}

	gpio_set_intr_type(bank->regbase, offset_to_bit(gpio), int_type);

	return 0;
}

static int gpio_irq_get_gpio_level(int gpio_irq)
{
	int gpio = irq_to_gpio(gpio_irq);
	struct gpio_bank *bank = gpio_to_bank(gpio);

	if (!bank)
		return -EINVAL;

	gpio &= GPIO_PIN_MASK;
	if (gpio >= bank->ngpio)
		return -EINVAL;

	/* NOTE: GPIO_EXT_PORT doesn't have _H/_L registers */
	return readl(bank->regbase + GPIO_EXT_PORT) & offset_to_bit(gpio) ? 1 : 0;
}

static int gpio_irq_enable(int gpio_irq)
{
	int gpio = irq_to_gpio(gpio_irq);
	struct gpio_bank *bank = gpio_to_bank(gpio);

	if (!bank)
		return -EINVAL;

	gpio &= GPIO_PIN_MASK;
	if (gpio >= bank->ngpio)
		return -EINVAL;

	gpio_irq_unmask(bank->regbase, offset_to_bit(gpio));

	if (bank->use_count == 0)
		irq_handler_enable(IRQ_GPIO0 + bank->id);
	bank->use_count++;

	return 0;
}

static int gpio_irq_disable(int irq)
{
	int gpio = irq_to_gpio(irq);
	struct gpio_bank *bank = gpio_to_bank(gpio);

	if (!bank)
		return -EINVAL;

	if (bank->use_count <= 0)
		return 0;

	gpio &= GPIO_PIN_MASK;
	if (gpio >= bank->ngpio)
		return -EINVAL;

	gpio_irq_mask(bank->regbase, offset_to_bit(gpio));

	if (bank->use_count == 1)
		irq_handler_disable(IRQ_GPIO0 + bank->id);
	bank->use_count--;

	return 0;
}

static int gpio_irq_init(void)
{
	struct gpio_bank *bank = NULL;
	int i = 0;

	for (i = 0; i < GPIO_BANK_NUM; i++) {
		struct udevice *dev;

		dev = malloc(sizeof(*dev));
		if (!dev)
			return -ENOMEM;

		bank = gpio_id_to_bank(i);
		if (bank) {
			dev->name = bank->name;

			/* disable gpio pin interrupt */
			writel(0xffff0000, bank->regbase + GPIO_INTEN_L);
			writel(0xffff0000, bank->regbase + GPIO_INTEN_H);

			/* register gpio group irq handler */
			irq_install_handler(IRQ_GPIO0 + bank->id,
			(interrupt_handler_t *)generic_gpio_handle_irq, dev);

			/* default disable all gpio group interrupt */
			irq_handler_disable(IRQ_GPIO0 + bank->id);
		}
	}

	return 0;
}

static struct irq_chip gpio_irq_chip = {
	.name		= "gpio-irq-chip",
	.irq_init	= gpio_irq_init,
	.irq_enable	= gpio_irq_enable,
	.irq_disable	= gpio_irq_disable,
	.irq_set_type	= gpio_irq_set_type,
	.irq_revert_type = gpio_irq_revert_type,
	.irq_get_gpio_level = gpio_irq_get_gpio_level,
};

struct irq_chip *arch_gpio_get_irqchip(void)
{
	return &gpio_irq_chip;
}