/** 
 * @file    gpio.c
 * @brief   GPIO module
 * @author  Kenny Tran
 * @date    Mar 01 2024
 * @version 0.0.1
 *
 * @section LICENSES
 * Copyright (C) Morgan Advanced Programmable Systems, Inc.
 */


/*--------------------------------------------------------------------------------------------------
 * INCLUDE FILES
 *------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>

#include <cmsis_core.h>
#include <zephyr/drivers/gpio.h>

#include "../lib/errno.h"

#include "gpio.h"


/*--------------------------------------------------------------------------------------------------
 * MACROS & CONSTANTS
 *------------------------------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------------------------------
 * TYPE DEFINITIONS & ENUMERATIONS
 *------------------------------------------------------------------------------------------------*/
typedef struct _gpio
{
    struct gpio_dt_spec  spec;
    struct gpio_callback cb;
    bool                 cb_registered;
    bool                 cfg_output;
    bool                 high;  /* NOTE: gpio_pin_get_dt() can't read status if configured
                                         for output.  Temporarily using flag to store state. */
} _gpio_s;



/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION PROTOTYPES
 *------------------------------------------------------------------------------------------------*/
static void left_button_isr (void);
static void center_button_isr (void);
static void right_button_isr (void);
static void pwr_5v_isr (void);
static void led_ring_pwr_isr (void);
static void led_ring_level_shift_enable_isr (void);
static void led_ring_din_isr (void);
static void battery_check_enable_isr (void);
static void imu_int_isr (void);
static void charger_status_isr (void);


/*--------------------------------------------------------------------------------------------------
 * PRIVATE CONSTANTS & VARIABLES
 *------------------------------------------------------------------------------------------------*/
static _gpio_s _gpio_arr[GPIO_PIN_COUNT] = {
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(left_button), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) left_button_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(center_button), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) center_button_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(right_button), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) right_button_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(pwr_5v_enable), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) pwr_5v_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(led_ring_pwr), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) led_ring_pwr_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(led_ring_level_shift_enable), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) led_ring_level_shift_enable_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(led_ring_din), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) led_ring_din_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(battery_check_enable), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) battery_check_enable_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(imu_int), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) imu_int_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
    (_gpio_s) {
        .spec          = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(charger_status), gpios, {0}),
        .cb.handler    = (gpio_callback_handler_t) charger_status_isr,
        .cb_registered = false,
        .cfg_output    = false,
        .high          = false
    },
};
static bool _gpio_init = false;


/*--------------------------------------------------------------------------------------------------
 * PUBLIC FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
ERRNO_e GPIO_init (void)
{
    uint32_t i;

    if (_gpio_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    for (i = 0; i < GPIO_PIN_COUNT; i++)
    {
        if (!gpio_is_ready_dt (&_gpio_arr[i].spec))
        {
            return ERRNO_NOT_READY;
		}
	}

    _gpio_init = true;

    return ERRNO_SUCCESS;
}

ERRNO_e GPIO_cfg (GPIO_pin_e pin, GPIO_direction_e dir, GPIO_pull_e pull, GPIO_init_e init, GPIO_int_edge_e edge, GPIO_cb_t cb)
{
    uint32_t flags = 0;

    if (!_gpio_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    if (pin >= GPIO_PIN_COUNT)
    {
        return ERRNO_INVALID_PARAM;
    }

    if (pull == GPIO_PULL_DOWN)
    {
        flags = GPIO_PULL_DOWN;
    }
    else if (pull == GPIO_PULL_UP)
    {
        flags = GPIO_PULL_UP;
    }

    /* Disable interrupts. */
    gpio_pin_interrupt_configure_dt (&_gpio_arr[pin].spec, GPIO_INT_DISABLE);

    /* Clear any pending interrupts. */
    NVIC_ClearPendingIRQ (GPIOTE_IRQn);

    switch (dir)
    {
        case GPIO_DIRECTION_IN:
            flags |= GPIO_INPUT;

            if (gpio_pin_configure_dt (&_gpio_arr[pin].spec, flags))
            {
                return ERRNO_GPIO_FAILURE;
            }

            switch (edge)
            {
                case GPIO_INT_RISING_EDGE:
                    /* GPIO interrupt to be triggered on pin rising edge. */
                    flags = GPIO_INT_EDGE_RISING;
                    break;
                case GPIO_INT_FALLING_EDGE:
                    /* GPIO interrupt to be triggered on pin falling edge. */
                    flags = GPIO_INT_EDGE_FALLING;
                    break;
                case GPIO_INT_BOTH_EDGES:
                    /* GPIO interrupt to be triggered on pin rising or falling edge. */
                    flags = GPIO_INT_EDGE_BOTH;
                    break;
                case GPIO_INT_PL_LOW:
                    /* GPIO interrupt to be triggered on pin physical level low. */
                    flags = GPIO_INT_LEVEL_LOW;
                    break;
                case GPIO_INT_PL_HIGH:
                    /* GPIO interrupt to be triggered on pin physical level high. */
                    flags = GPIO_INT_LEVEL_HIGH;
                    break;
                case GPIO_INT_STATE_CHANGE_LL_LOW:
                    /* GPIO interrupt to be triggered on pin state change to logical level 0. */
                    flags = GPIO_INT_EDGE_TO_INACTIVE;
                    break;
                case GPIO_INT_STATE_CHANGE_LL_HIGH:
                    /* GPIO interrupt to be triggered on pin state change to logical level 1. */
                    flags = GPIO_INT_EDGE_TO_ACTIVE;
                    break;
                case GPIO_INT_LL_LOW:
                    /* GPIO interrupt to be triggered on pin logical level 0. */
                    flags = GPIO_INT_LEVEL_INACTIVE;
                    break;
                case GPIO_INT_LL_HIGH:
                    /* GPIO interrupt to be triggered on pin logical level 1. */
                    flags = GPIO_INT_LEVEL_ACTIVE;
                    break;
            }

            if (gpio_pin_interrupt_configure_dt (&_gpio_arr[pin].spec, flags))
            {
                return ERRNO_GPIO_FAILURE;
            }

            if (_gpio_arr[pin].cb_registered)
            {
                /* Before adding the new callback, remove any existing. */
                gpio_remove_callback (_gpio_arr[pin].spec.port, &_gpio_arr[pin].cb);
            }

            if (cb == NULL)
            {
                gpio_init_callback (&_gpio_arr[pin].cb, (gpio_callback_handler_t) _gpio_arr[pin].cb.handler, BIT(_gpio_arr[pin].spec.pin));
            }
            else
            {
                gpio_init_callback (&_gpio_arr[pin].cb, (gpio_callback_handler_t) cb, BIT(_gpio_arr[pin].spec.pin));
            }
            
            if (gpio_add_callback (_gpio_arr[pin].spec.port, &_gpio_arr[pin].cb))
            {
                return ERRNO_GPIO_FAILURE;
            }

            _gpio_arr[pin].cb_registered = true;
            _gpio_arr[pin].cfg_output    = false;

            break;

        case GPIO_DIRECTION_OUT:
            if (init == GPIO_INIT_LOW)
            {
                flags |= GPIO_OUTPUT_INACTIVE;
                _gpio_arr[pin].high = false;
            }
            else
            {
                flags |= GPIO_OUTPUT_ACTIVE;
                _gpio_arr[pin].high = true;
            }

            if (gpio_pin_configure_dt (&_gpio_arr[pin].spec, flags))
            {
                return ERRNO_GPIO_FAILURE;
            }

            _gpio_arr[pin].cfg_output = true;

            break;
    }
    
    return ERRNO_SUCCESS;
}

ERRNO_e GPIO_level_set (GPIO_pin_e pin, bool high)
{
    if (_gpio_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    if (pin >= GPIO_PIN_COUNT)
    {
        return ERRNO_INVALID_PARAM;
    }

    /* Check that pin is not configured for input. */
    if (!_gpio_arr[pin].cfg_output)
    {
        return ERRNO_GPIO_FAILURE;
    }

    if (gpio_pin_set_dt (&_gpio_arr[pin].spec, high))
    {
        return ERRNO_GPIO_FAILURE;
    }

    _gpio_arr[pin].high = high;

    return ERRNO_SUCCESS;
}

bool GPIO_level_get (GPIO_pin_e pin)
{
    if (pin >= GPIO_PIN_COUNT)
    {
        /* TODO: Add asserts. */
        while (1)
            ;
    }

    /* FIXME: When configured as output, can't read back GPIO state.  Using flag to store and readback. */
    if (_gpio_arr[pin].cfg_output)
    {
        return _gpio_arr[pin].high;
    }

    return (bool) gpio_pin_get_dt (&_gpio_arr[pin].spec);
}

ERRNO_e GPIO_interrupt_disable (GPIO_pin_e pin)
{
    if (_gpio_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    if (pin >= GPIO_PIN_COUNT)
    {
        return ERRNO_INVALID_PARAM;
    }

    /* Disable interrupts. */
    if (gpio_pin_interrupt_configure_dt (&_gpio_arr[pin].spec, GPIO_INT_DISABLE))
    {
        return ERRNO_GPIO_FAILURE;
    }

    return ERRNO_SUCCESS;
}

ERRNO_e GPIO_pending_interrupts_clear (void)
{
    if (_gpio_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    /* Clear any pending interrupts. */
    NVIC_ClearPendingIRQ (GPIOTE_IRQn);

    return ERRNO_SUCCESS;
}


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
static void left_button_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */

}

static void center_button_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */

}

static void right_button_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */

}

static void pwr_5v_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */

}

static void led_ring_pwr_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */

}

static void led_ring_level_shift_enable_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */

}

static void led_ring_din_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */
}

static void battery_check_enable_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */
}

static void imu_int_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */
}

static void charger_status_isr (void)
{
    /* Cannot have a NULL callback.  Used for empty callback. */
}
