/*
 * Copyright (C) 2021 Otto-von-Guericke Universität Magdeburg
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     cpu_rpx0xx
 * @ingroup     drivers_periph_gpio
 * @{
 *
 * @file
 * @brief       GPIO driver implementation for the RP2040
 *
 * @author      Marian Buschsieweke <marian.buschsieweke@ovgu.de>
 *
 * @}
 */

#include <errno.h>

#include "bitarithm.h"
#include "board.h"
#include "irq.h"
#include "periph/gpio.h"
#include "periph_conf.h"
#include "periph_cpu.h"
#include "io_reg.h"

#define ENABLE_DEBUG    0
#include "debug.h"

#define GPIO_PIN_NUMOF  30U

#ifdef MODULE_PERIPH_GPIO_IRQ
static gpio_cb_t _cbs[GPIO_PIN_NUMOF];
static void *_args[GPIO_PIN_NUMOF];
#endif /* MODULE_PERIPH_GPIO_IRQ */

int gpio_init(gpio_t pin, gpio_mode_t mode)
{
    assert(pin < GPIO_PIN_NUMOF);
    SIO->GPIO_OE_CLR.reg = 1LU << pin;
    SIO->GPIO_OUT_CLR.reg = 1LU << pin;

    switch (mode) {
    case GPIO_IN:
        {
            gpio_pad_ctrl_t pad_config = {
                .input_enable = 1,
                .schmitt_trig_enable = 1
            };
            gpio_io_ctrl_t io_config = {
                .function_select = FUNCTION_SELECT_SIO,
            };
            gpio_set_pad_config(pin, pad_config);
            gpio_set_io_config(pin, io_config);
        }
        break;
    case GPIO_IN_PD:
        {
            gpio_pad_ctrl_t pad_config = {
                .pull_down_enable = 1,
                .input_enable = 1,
                .schmitt_trig_enable = 1
            };
            gpio_io_ctrl_t io_config = {
                .function_select = FUNCTION_SELECT_SIO,
            };
            gpio_set_pad_config(pin, pad_config);
            gpio_set_io_config(pin, io_config);
        }
        break;
    case GPIO_IN_PU:
        {
            gpio_pad_ctrl_t pad_config = {
                .pull_up_enable = 1,
                .input_enable = 1,
                .schmitt_trig_enable = 1
            };
            gpio_io_ctrl_t io_config = {
                .function_select = FUNCTION_SELECT_SIO,
            };
            gpio_set_pad_config(pin, pad_config);
            gpio_set_io_config(pin, io_config);
        }
        break;
    case GPIO_OUT:
        {
            gpio_pad_ctrl_t pad_config = {
                .drive_strength = DRIVE_STRENGTH_12MA,
            };
            gpio_io_ctrl_t io_config = {
                .function_select = FUNCTION_SELECT_SIO,
            };
            gpio_set_pad_config(pin, pad_config);
            gpio_set_io_config(pin, io_config);
        }
        SIO->GPIO_OE_SET.reg = 1LU << pin;
        break;
    default:
        return -ENOTSUP;
    }
    return 0;
}

int gpio_read(gpio_t pin)
{
    if (SIO->GPIO_OE.reg & (1LU << pin)) {
        /* pin is output: */
        return SIO->GPIO_OUT.reg & (1LU << pin);
    }
    /* pin is input: */
    return SIO->GPIO_IN.reg & (1LU << pin);
}

void gpio_set(gpio_t pin)
{
    SIO->GPIO_OUT_SET.reg = 1LU << pin;
}

void gpio_clear(gpio_t pin)
{
    SIO->GPIO_OUT_CLR.reg = 1LU << pin;
}

void gpio_toggle(gpio_t pin)
{
    SIO->GPIO_OUT_XOR.reg = 1LU << pin;
}

void gpio_write(gpio_t pin, int value)
{
    if (value) {
        gpio_set(pin);
    }
    else {
        gpio_clear(pin);
    }
}

#ifdef MODULE_PERIPH_GPIO_IRQ
static void _irq_enable(gpio_t pin, unsigned flank)
{
    volatile uint32_t *irq_enable_regs = &IO_BANK0->PROC0_INTE0.reg;
    volatile uint32_t *irq_ack_regs = &IO_BANK0->INTR0.reg;
    /* There are 4 bits to control IRQs per pin, hence the configuration is split across multiple
     * I/O registers. The following calculates the position the four bits matching the given pin,
     * where idx refers to the I/O register and shift_amount to the position in the I/O register.
     */
    unsigned shift_amount = (pin & 0x7) << 2;
    unsigned idx = pin >> 3;
    /* make access atomic by disabling IRQs */
    unsigned irq_state = irq_disable();
    uint32_t value = irq_enable_regs[idx];
    /* clear any stray IRQs */
    io_reg_atomic_clear(&irq_ack_regs[idx], 0xFLU << shift_amount);
    /* first, clear previous setting */
    value &= ~(0xFLU << shift_amount);
    /* then, apply new setting */
    value |= flank << shift_amount;
    irq_enable_regs[idx] = value;
    irq_restore(irq_state);
    NVIC_EnableIRQ(IO_IRQ_BANK0_IRQn);
}

void gpio_irq_enable(gpio_t pin)
{
    io_reg_atomic_clear(gpio_io_register(pin), IO_BANK0_GPIO1_CTRL_IRQOVER_Msk);
}

void gpio_irq_disable(gpio_t pin)
{
    /* Beware: The two-bit IRQOVER value needs to be set to 0b10 == IRQ_OVERRIDE_LOW. This
     * implementation will set IRQOVER only to either 0b00 == IRQ_OVERRIDE_NORMAL or
     * 0b10 == IRQ_OVERRIDE_LOW. If we just set the most significant bit, this will result in
     * IRQOVER set to IRQ_OVERRIDE_LOW.
     *
     * IRQOVER must not be set by user code for this to work, though.
     */
    io_reg_atomic_set(gpio_io_register(pin), IRQ_OVERRIDE_LOW << IO_BANK0_GPIO1_CTRL_IRQOVER_Pos);
}

int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, gpio_cb_t cb, void *arg)
{
    assert(pin < GPIO_PIN_NUMOF);
    int retval = gpio_init(pin, mode);
    if (retval) {
        return retval;
    }

    _cbs[pin] = cb;
    _args[pin] = arg;
    _irq_enable(pin, flank);

    return 0;
}

void isr_io_bank0(void)
{
    unsigned offset = 0;
    volatile uint32_t *irq_status_regs = &IO_BANK0->PROC0_INTS0.reg;
    volatile uint32_t *irq_ack_regs = &IO_BANK0->INTR0.reg;

    DEBUG("[rp0x00] GPIO IRQ mask:      %08x, %08x, %08x, %08x\n",
          (unsigned)IO_BANK0->PROC0_INTE0.reg, (unsigned)IO_BANK0->PROC0_INTE1.reg,
          (unsigned)IO_BANK0->PROC0_INTE2.reg, (unsigned)IO_BANK0->PROC0_INTE3.reg);
    DEBUG("[rp0x00] GPIO IRQ status:    %08x, %08x, %08x, %08x\n",
          (unsigned)IO_BANK0->PROC0_INTS0.reg, (unsigned)IO_BANK0->PROC0_INTS1.reg,
          (unsigned)IO_BANK0->PROC0_INTS2.reg, (unsigned)IO_BANK0->PROC0_INTS3.reg);

    /* There are four IRQ status bits per pin, so there is info for 8 pins per I/O register.
     * We will iterate over all IRQ status I/O registers in the outer loop, and over all 8 pins
     * per register in the inner loop */
    for (unsigned i = 0; i < (GPIO_PIN_NUMOF + 7) / 8; i++, offset += 8) {
        unsigned status = irq_status_regs[i];
        irq_ack_regs[i] = status;
        for (unsigned pin = 0; pin < 8; pin++) {
            /* Note: Not use bitarithm_test_and_clear() here, since two bits for a single pin could
             * be set. This happens if both rising and falling flank (1 bit used to track each) have
             * occurred until the ISR started to handle the first IRQ.
             */
            if (status & (0xFLU << (pin << 2))) {
                if (_cbs[pin + offset]) {
                    _cbs[pin + offset](_args[pin + offset]);
                }
            }
        }
    }
    cortexm_isr_end();
}

#endif /* MODULE_PERIPH_GPIO_IRQ */
