/*
 * Copyright (c) 2018 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>

#include <board.h>
#include <soc.h>
#include <zephyr/drivers/gpio.h>

#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>

/**
 * @file
 *
 * @brief Example of using GPIOs on UP Squared board
 *
 * This example outputs the value of a counter via 4 GPIO lines
 * as a 4-bit value (bin 0, 1, 2, 3 -> HAT Pin 35, 37, 38, 40).
 * The counter increments for each change from 0 to 1 on HAT Pin 16.
 *
 * Note:
 * Need to change the BIOS settings:
 * () Advanced -> HAT Configurations:
 *    - HD-Audio / I2S6 Selec -> Disabled
 *    - GPIO / PWM3 Selection -> GPIO
 *    - GPIO / I2S2 Selection -> GPIO
 *
 *    - GPIO 19 (Pin16) Confi -> Input
 *
 *    - GPIO 14 (Pin35) Confi -> Output
 *    - GPIO 15 (Pin37) Confi -> Output
 *    - GPIO 27 (Pin38) Confi -> Output
 *    - GPIO 28 (Pin40) Confi -> Output
 */

struct _pin {
	uint32_t		hat_num;
	uint32_t		pin;
	const struct device *gpio_dev;
};

struct _pin counter_pins[] = {
	{
		.hat_num = 35,
		.pin = UP2_HAT_PIN_35,
		.gpio_dev = DEVICE_DT_GET(UP2_HAT_PIN_35_DEV),
	},
	{
		.hat_num = 37,
		.pin = UP2_HAT_PIN_37,
		.gpio_dev = DEVICE_DT_GET(UP2_HAT_PIN_37_DEV),
	},
	{
		.hat_num = 38,
		.pin = UP2_HAT_PIN_38,
		.gpio_dev = DEVICE_DT_GET(UP2_HAT_PIN_38_DEV),
	},
	{
		.hat_num = 40,
		.pin = UP2_HAT_PIN_40,
		.gpio_dev = DEVICE_DT_GET(UP2_HAT_PIN_40_DEV),
	},
};

struct _pin intr_pin = {
	.hat_num = 16,
	.pin = UP2_HAT_PIN_16,
	.gpio_dev = DEVICE_DT_GET(UP2_HAT_PIN_16_DEV),
};

static struct gpio_callback gpio_cb;

static volatile uint32_t counter;

K_SEM_DEFINE(counter_sem, 0, 1);

#define NUM_PINS	ARRAY_SIZE(counter_pins)
#define MASK		(BIT(NUM_PINS) - 1)

void button_cb(const struct device *gpiodev, struct gpio_callback *cb,
	       uint32_t pin)
{
	counter++;
	k_sem_give(&counter_sem);
}

int get_gpio_dev(struct _pin *pin)
{
	if (!device_is_ready(pin->gpio_dev)) {
		printk("ERROR: GPIO device is not ready for %s\n", pin->gpio_dev->name);
		return -1;
	}

	return 0;
}

int main(void)
{
	uint32_t val;
	int i, ret;

	for (i = 0; i < NUM_PINS; i++) {
		if (get_gpio_dev(&counter_pins[i]) != 0) {
			return 0;
		}
	}

	if (get_gpio_dev(&intr_pin) != 0) {
		return 0;
	}

	/* Set pins to output */
	for (i = 0; i < NUM_PINS; i++) {
		ret = gpio_pin_configure(counter_pins[i].gpio_dev,
					 counter_pins[i].pin,
					 GPIO_OUTPUT_LOW);
		if (ret) {
			printk("ERROR: cannot set HAT pin %d to OUT (%d)\n",
			       counter_pins[i].hat_num, ret);
			return 0;
		}
	}

	/* Setup input pin */
	ret = gpio_pin_configure(intr_pin.gpio_dev, intr_pin.pin,
				 GPIO_INPUT);
	if (ret) {
		printk("ERROR: cannot set HAT pin %d to IN (%d)\n",
			       intr_pin.hat_num, ret);
			return 0;
	}


	/* Callback uses pin_mask, so need bit shifting */
	gpio_init_callback(&gpio_cb, button_cb, (1 << intr_pin.pin));
	gpio_add_callback(intr_pin.gpio_dev, &gpio_cb);

	/* Setup input pin for interrupt */
	ret = gpio_pin_interrupt_configure(intr_pin.gpio_dev, intr_pin.pin,
					   GPIO_INT_EDGE_RISING);
	if (ret) {
		printk("ERROR: cannot config interrupt on HAT pin %d (%d)\n",
			       intr_pin.hat_num, ret);
			return 0;
	}

	/* main loop */
	val = 0U;
	while (1) {
		printk("counter: 0x%x\n", val);

		for (i = 0; i < NUM_PINS; i++) {
			ret = gpio_pin_set(counter_pins[i].gpio_dev,
					   counter_pins[i].pin,
					   (val & BIT(i)));
			if (ret) {
				printk("ERROR: cannot set HAT pin %d value (%d)\n",
				       counter_pins[i].hat_num, ret);
				return 0;
			}
		}

		k_sem_take(&counter_sem, K_FOREVER);
		val = counter & MASK;
	}
	return 0;
}
