/* Copyright 2019 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* MAX32660 GPIO module for Chrome EC */

#include "clock.h"
#include "console.h"
#include "common.h"
#include "gpio.h"
#include "hooks.h"
#include "switch.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "registers.h"
#include "gpio_regs.h"

#define CPRINTF(format, args...) cprintf(CC_GPIO, format, ##args)
#define CPRINTS(format, args...) cprints(CC_GPIO, format, ##args)

/* 0-terminated list of GPIO base addresses */
static mxc_gpio_regs_t *gpio_bases[] = {MXC_GPIO0, 0};

void gpio_set_alternate_function(uint32_t port, uint32_t mask,
			enum gpio_alternate_func func)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(port);

	switch (func) {
	case GPIO_ALT_FUNC_1:
		gpio->en_clr = mask;
		gpio->en1_clr = mask;
		break;
	case GPIO_ALT_FUNC_2:
		gpio->en_clr = mask;
		gpio->en1_set = mask;
		break;
	case GPIO_ALT_FUNC_3:
		gpio->en_set = mask;
		gpio->en1_set = mask;
		break;
	default:
	    /* Default as input */
		gpio->out_en_clr = mask;
		gpio->en_set = mask;
		gpio->en1_clr = mask;
		break;
	}
}

test_mockable int gpio_get_level(enum gpio_signal signal)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(gpio_list[signal].port);

	return (gpio->in & gpio_list[signal].mask);
}

void gpio_set_level(enum gpio_signal signal, int value)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(gpio_list[signal].port);

	if (value) {
		gpio->out_set = gpio_list[signal].mask;
	} else {
		gpio->out_clr = gpio_list[signal].mask;
	}
}

void gpio_set_flags_by_mask(uint32_t port, uint32_t mask, uint32_t flags)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(port);

	/* Setup for either an output or input. */
	if (flags & GPIO_OUTPUT) {
		gpio->out_en_set = mask;
		gpio->en_set = mask;
		gpio->en1_clr = mask;
	} else {
		gpio->out_en_clr = mask;
		gpio->en_set = mask;
		gpio->en1_clr = mask;
	}

	/* Handle pull up, pull down or neither. */
	if (flags & GPIO_PULL_UP) {
		gpio->pad_cfg1 |= mask;
		gpio->pad_cfg2 &= ~mask;
		gpio->ps |= mask;
	} else if (flags & GPIO_PULL_DOWN) {
		gpio->pad_cfg1 &= ~mask;
		gpio->pad_cfg2 |= mask;
		gpio->ps &= ~mask;
	} else {
		/* No pull up/down */
		gpio->pad_cfg1 &= ~mask;
		gpio->pad_cfg2 &= ~mask;
		gpio->ps &= ~mask;
	}

	/* Handle setting up level interrupts. */
	if (flags & GPIO_INT_F_HIGH) {
		/* Level triggered mode. */
		gpio->int_mod &= ~mask;
		/* Level high generates interrupt. */
		gpio->int_pol |= mask;
	} else if (flags & GPIO_INT_F_LOW) {
		/* Level triggered mode. */
		gpio->int_mod &= ~mask;
		/* Level low generates interrupt. */
		gpio->int_pol &= ~mask;
	}

	/* Handle setting up edge interrupts. */
	if (flags & GPIO_INT_F_RISING) {
		/* Edge triggered mode. */
		gpio->int_mod |= mask;
		/* Rising edge generates interrupt. */
		gpio->int_pol |= mask;
	} else if (flags & GPIO_INT_F_FALLING) {
		/* Edge triggered mode. */
		gpio->int_mod |= mask;
		/* Falling edge generates interrupt. */
		gpio->int_pol &= ~mask;
	}

	/* Handle interrupting on both edges. */
	if ((flags & GPIO_INT_F_RISING) && (flags & GPIO_INT_F_FALLING)) {
		/* Dual edge triggered mode. */
		gpio->int_dual_edge |= mask;
	} else {
		/* Not dual edge triggered mode. */
		gpio->int_dual_edge &= ~mask;
	}

	/* Set the gpio pin high or low. */
	if (flags & GPIO_HIGH) {
		gpio->out_set = mask;
	} else if (flags & GPIO_LOW) {
		gpio->out_clr = mask;
	}
}

int gpio_enable_interrupt(enum gpio_signal signal)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(gpio_list[signal].port);

	gpio->int_en_set = gpio_list[signal].mask;
	return EC_SUCCESS;
}

int gpio_disable_interrupt(enum gpio_signal signal)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(gpio_list[signal].port);

	gpio->int_en_clr = gpio_list[signal].mask;
	return EC_SUCCESS;
}

int gpio_clear_pending_interrupt(enum gpio_signal signal)
{
	mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(gpio_list[signal].port);

	gpio->int_clr = gpio_list[signal].mask;
	return EC_SUCCESS;
}

void gpio_pre_init(void)
{
	const struct gpio_info *g = gpio_list;
	int i;

	/* Mask all GPIO interrupts */
	for (i = 0; gpio_bases[i]; i++)
		gpio_bases[i]->int_en = 0;

	/* Set all GPIOs to defaults */
	for (i = 0; i < GPIO_COUNT; i++, g++) {
		int flags = g->flags;

		if (flags & GPIO_DEFAULT)
			continue;

		/* Use as GPIO, not alternate function */
		gpio_set_alternate_function(g->port, g->mask,
					GPIO_ALT_FUNC_NONE);

		/* Set up GPIO based on flags */
		gpio_set_flags_by_mask(g->port, g->mask, flags);
	}
}

static void gpio_init(void)
{
	/*
	 * Enable global GPIO0 Port interrupt. Note that interrupts still need to be
	 * enabled at the per pin level.
	 */
	task_enable_irq(EC_GPIO0_IRQn);
}
DECLARE_HOOK(HOOK_INIT, gpio_init, HOOK_PRIO_DEFAULT);

/*****************************************************************************/
/* Interrupt handlers */

/**
 * Handle a GPIO interrupt.
 *
 * port		GPIO port
 * mis		Masked interrupt status value for that port
 */
static void gpio_interrupt(int port, uint32_t mis)
{
	int i = 0;
	const struct gpio_info *g = gpio_list;

	for (i = 0; i < GPIO_IH_COUNT && mis; i++, g++) {
		if (port == g->port && (mis & g->mask)) {
			gpio_irq_handlers[i](i);
			mis &= ~g->mask;
		}
	}
}

/**
 * Handlers for each GPIO port. Read the interrupt status, call the common GPIO
 * interrupt handler and clear the GPIO hardware interrupt status.
 */
#define GPIO_IRQ_FUNC(irqfunc, gpiobase)                                       \
	void irqfunc(void)                                                     \
	{                                                                      \
		mxc_gpio_regs_t *gpio = MXC_GPIO_GET_GPIO(gpiobase);           \
		uint32_t mis = gpio->int_stat;                                 \
		gpio_interrupt(gpiobase, mis);                                 \
		gpio->int_clr = mis;                                           \
	}

GPIO_IRQ_FUNC(__gpio_0_interrupt, PORT_0);
#undef GPIO_IRQ_FUNC
DECLARE_IRQ(EC_GPIO0_IRQn, __gpio_0_interrupt, 1);
