/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>
#include <errno.h>
#include <assert.h>
#include <os/mynewt.h>
#include <mcu/da1469x_pd.h>
#include <mcu/da1469x_hal.h>
#include <hal/hal_spi.h>
#include <mcu/mcu.h>

/* The maximum number of SPI interfaces we will allow */
#define DA1469X_HAL_SPI_MAX (2)

#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_1_MASTER)
#define SPI_MASTER_CODE 1
#else
#define SPI_MASTER_CODE 0
#endif
#if MYNEWT_VAL(SPI_0_SLAVE) || MYNEWT_VAL(SPI_1_SLAVE)
#defien SPI_SLAVE_CODE  1
#else
#define SPI_SLAVE_CODE  0
#endif

#if SPI_MASTER_CODE || SPI_SLAVE_CODE

struct da1469x_hal_spi_controller {
    SPI_Type *regs;
    uint8_t spi_num;
    uint8_t spi_clk_func;
    uint8_t spi_do_func;
    uint8_t spi_di_func;
    uint8_t spi_ss_func;
    uint8_t irq_num;
    void (* irq_handler)(void);
};

struct da1469x_hal_spi {
    uint8_t spi_type;
    struct hal_spi_settings spi_cfg; /* Slave and master */

    /* SPI controller data  */
    const struct da1469x_hal_spi_controller *hw;

    const uint8_t *txbuf;   /* Pointer to TX buffer */
    uint8_t *rxbuf;         /* Pointer to RX buffer */

    uint16_t len;           /* Length of buffer(s) */
    uint16_t txlen;         /* Number of bytes sent so far */
    uint16_t rxlen;         /* Number of bytes received so far */

    /* Callback and arguments */
    hal_spi_txrx_cb txrx_cb_func;
    void           *txrx_cb_arg;
};

void SPI_Handler(void);
void SPI2_Handler(void);

#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)
static const struct da1469x_hal_spi_controller hal_spi0_controller = {
    .regs = (SPI_Type *)SPI,
    .spi_num = 0,
    .spi_clk_func = MCU_GPIO_FUNC_SPI_CLK,
    .spi_do_func = MCU_GPIO_FUNC_SPI_DO,
    .spi_di_func = MCU_GPIO_FUNC_SPI_DI,
    .spi_ss_func = MCU_GPIO_FUNC_SPI_EN,
    .irq_num = SPI_IRQn,
    .irq_handler = SPI_Handler,
};
struct da1469x_hal_spi hal_spi0 = {
    .hw = &hal_spi0_controller,
};
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
static const struct da1469x_hal_spi_controller hal_spi1_controller = {
    .regs = (SPI_Type *)SPI2,
    .spi_num = 1,
    .spi_clk_func = MCU_GPIO_FUNC_SPI2_CLK,
    .spi_do_func = MCU_GPIO_FUNC_SPI2_DO,
    .spi_di_func = MCU_GPIO_FUNC_SPI2_DI,
    .spi_ss_func = MCU_GPIO_FUNC_SPI2_EN,
    .irq_num = SPI2_IRQn,
    .irq_handler = SPI2_Handler,
};
struct da1469x_hal_spi hal_spi1 = {
    .hw = &hal_spi1_controller,
};
#endif

static struct da1469x_hal_spi *da1469x_hal_spis[DA1469X_HAL_SPI_MAX] = {
#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)
    &hal_spi0,
#else
    NULL,
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
    &hal_spi1
#else
    NULL
#endif
};

static struct da1469x_hal_spi *
hal_spi_resolve(int spi_num)
{
    if (spi_num >= DA1469X_HAL_SPI_MAX) {
        return NULL;
    }

    return da1469x_hal_spis[spi_num];
}

static bool
da1469x_hal_spi_do_transfer(struct da1469x_hal_spi *spi)
{
    SPI_Type *regs = spi->hw->regs;
    uint32_t ctrl_reg;

    while (spi->rxlen < spi->len || spi->txlen < spi->len) {
        ctrl_reg = regs->SPI_CTRL_REG;
        if (0 == (ctrl_reg & SPI_SPI_CTRL_REG_SPI_RX_FIFO_EMPTY_Msk)) {
            if (spi->rxlen < spi->len) {
                spi->rxbuf[spi->rxlen++] = (uint8_t)regs->SPI_RX_TX_REG;
            }
        } else if (0 != (ctrl_reg & SPI_SPI_CTRL_REG_SPI_TXH_Msk)) {
            if (spi->txlen < spi->len) {
                regs->SPI_RX_TX_REG = spi->txbuf[spi->txlen++];
            }
        } else {
            /* Transfer not finished yet, but there is nothing more to send
             * or TX FIFO is full for now */
            return false;
        }
    }
    return true;
}

static void
da1469x_hal_spi_irq_handler(struct da1469x_hal_spi *spi)
{
    SPI_Type *regs = spi->hw->regs;

    if (!da1469x_hal_spi_do_transfer(spi)) {
        if (spi->txlen >= spi->len) {
            /* No need for interrupt from TX fifo */
            regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_TX_FIFO_NOTFULL_MASK_Msk;
        }
        regs->SPI_CLEAR_INT_REG = 1;
    } else {
        regs->SPI_CTRL_REG &= ~(SPI_SPI_CTRL_REG_SPI_TX_FIFO_NOTFULL_MASK_Msk |
                                SPI_SPI_CTRL_REG_SPI_MINT_Msk);
        if (spi->txrx_cb_func) {
            spi->txrx_cb_func(spi->txrx_cb_arg, spi->len);
        }
    }
}

#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)
void
SPI_Handler(void)
{
    da1469x_hal_spi_irq_handler(&hal_spi0);
}
#endif

#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
void
SPI2_Handler(void)
{
    da1469x_hal_spi_irq_handler(&hal_spi1);
}
#endif

static int
hal_spi_init_master(const struct da1469x_hal_spi *spi,
                    const struct da1469x_hal_spi_cfg *cfg)
{
    int irq_num = spi->hw->irq_num;

    /* Configure pins */
    mcu_gpio_set_pin_function(cfg->pin_sck, MCU_GPIO_MODE_OUTPUT,
                              spi->hw->spi_clk_func);
    if (cfg->pin_do >= 0) {
        mcu_gpio_set_pin_function(cfg->pin_do, MCU_GPIO_MODE_OUTPUT,
                                  spi->hw->spi_do_func);
    }
    if (cfg->pin_di >= 0) {
        mcu_gpio_set_pin_function(cfg->pin_di, MCU_GPIO_MODE_INPUT,
                                  spi->hw->spi_di_func);
    }

    spi->hw->regs->SPI_CLEAR_INT_REG = 0;
    spi->hw->regs->SPI_CTRL_REG = 0;

    if (spi->hw->spi_num == 0 && MYNEWT_VAL(SPI_0_MASTER)) {
        CRG_COM->RESET_CLK_COM_REG = CRG_COM_RESET_CLK_COM_REG_SPI_CLK_SEL_Msk;
        CRG_COM->SET_CLK_COM_REG = CRG_COM_RESET_CLK_COM_REG_SPI_ENABLE_Msk;
    } else if (spi->hw->spi_num == 1 && MYNEWT_VAL(SPI_1_MASTER)) {
        CRG_COM->RESET_CLK_COM_REG = CRG_COM_RESET_CLK_COM_REG_SPI2_CLK_SEL_Msk;
        CRG_COM->SET_CLK_COM_REG = CRG_COM_RESET_CLK_COM_REG_SPI2_ENABLE_Msk;
    }

    NVIC_SetVector(irq_num, (uint32_t)spi->hw->irq_handler);
    NVIC_SetPriority(irq_num, (1 << __NVIC_PRIO_BITS) - 1);
    NVIC_ClearPendingIRQ(irq_num);
    NVIC_EnableIRQ(irq_num);

    return 0;
}

static int
hal_spi_init_slave(const struct da1469x_hal_spi *spi,
                   const struct da1469x_hal_spi_cfg *cfg)
{
    return -1;
}

int
hal_spi_init(int spi_num, void *cfg, uint8_t spi_type)
{
    int rc;
    struct da1469x_hal_spi *spi;

    if (cfg == NULL) {
        return SYS_EINVAL;
    }

    spi = hal_spi_resolve(spi_num);
    if (!spi) {
        return SYS_EINVAL;
    }

    if ((spi_type != HAL_SPI_TYPE_MASTER) && (spi_type != HAL_SPI_TYPE_SLAVE)) {
        return SYS_EINVAL;
    }

    da1469x_pd_acquire(MCU_PD_DOMAIN_COM);

    spi->spi_type  = spi_type;

    rc = hal_spi_disable(spi_num);
    if (rc) {
        return rc;
    }

    if (spi_type == HAL_SPI_TYPE_MASTER && SPI_MASTER_CODE) {
        rc = hal_spi_init_master(spi, (struct da1469x_hal_spi_cfg *)cfg);
    } else if (SPI_SLAVE_CODE) {
        rc = hal_spi_init_slave(spi, (struct da1469x_hal_spi_cfg *)cfg);
    } else {
        rc = SYS_EINVAL;
    }

    return rc;
}

int
hal_spi_config(int spi_num, struct hal_spi_settings *settings)
{
    SPI_Type *regs;
    uint32_t ctrl_reg;
    struct da1469x_hal_spi *spi;

    if (settings == NULL) {
        return SYS_EINVAL;
    }

    spi = hal_spi_resolve(spi_num);
    if (!spi) {
        return SYS_EINVAL;
    }
    regs = spi->hw->regs;

    regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_ON_Msk;
    regs->SPI_CTRL_REG |= SPI_SPI_CTRL_REG_SPI_RST_Msk;

    /* Preserve some register fields only */
    ctrl_reg = regs->SPI_CTRL_REG & (SPI_SPI_CTRL_REG_SPI_TX_FIFO_NOTFULL_MASK_Msk |
                                     SPI_SPI_CTRL_REG_SPI_DMA_TXREQ_MODE_Msk |
                                     SPI_SPI_CTRL_REG_SPI_PRIORITY_Msk |
                                     SPI_SPI_CTRL_REG_SPI_EN_CTRL_Msk |
                                     SPI_SPI_CTRL_REG_SPI_SMN_Msk |
                                     SPI_SPI_CTRL_REG_SPI_DO_Msk |
                                     SPI_SPI_CTRL_REG_SPI_RST_Msk);

    assert(settings->data_order == HAL_SPI_MSB_FIRST);

    if (settings->baudrate >= 16000) {
        /* Set 16 MHz */
        ctrl_reg |= (2U << SPI_SPI_CTRL_REG_SPI_CLK_Pos);
    } else if (settings->baudrate >= 8000) {
        /* Set 8 MHz */
        ctrl_reg |= (1U << SPI_SPI_CTRL_REG_SPI_CLK_Pos);
    } else if (settings->baudrate > 4000) {
        /* Set 4 MHz */
        ctrl_reg |= (0U << SPI_SPI_CTRL_REG_SPI_CLK_Pos);
    } else {
        /* Slowest possibly clock, divider 14, 2.28MHz */
        ctrl_reg |= (3U << SPI_SPI_CTRL_REG_SPI_CLK_Pos);
    }

    switch (settings->data_mode) {
    case HAL_SPI_MODE0:
        /* Bits already zeroed */
        break;
    case HAL_SPI_MODE1:
        ctrl_reg |= (1U << SPI_SPI_CTRL_REG_SPI_PHA_Pos);
        break;
    case HAL_SPI_MODE2:
        ctrl_reg |= (1U << SPI_SPI_CTRL_REG_SPI_POL_Pos);
        break;
    case HAL_SPI_MODE3:
        ctrl_reg |= (1U << SPI_SPI_CTRL_REG_SPI_PHA_Pos) |
                    (1U << SPI_SPI_CTRL_REG_SPI_POL_Pos);
        break;
    default:
        assert(0);
        break;
    }
    if (settings->word_size == HAL_SPI_WORD_SIZE_9BIT) {
        ctrl_reg |= (1U << SPI_SPI_CTRL_REG_SPI_WORD_Pos);
    }
    regs->SPI_CTRL_REG = ctrl_reg;
    /* At this point interrupt is cleared, FIFO is enabled, controller is disabled */

    return 0;
}

int
hal_spi_enable(int spi_num)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;

    spi = hal_spi_resolve(spi_num);
    if (!spi) {
        return SYS_EINVAL;
    }
    regs = spi->hw->regs;

    if (regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_BUSY_Msk) {
        return SYS_EBUSY;
    }

    regs->SPI_CTRL_REG |= SPI_SPI_CTRL_REG_SPI_ON_Msk;
    regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_RST_Msk;

    return 0;
}

int
hal_spi_disable(int spi_num)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;

    spi = hal_spi_resolve(spi_num);
    if (!spi) {
        return SYS_EINVAL;
    }
    regs = spi->hw->regs;

    while (regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_BUSY_Msk) {
    }

    regs->SPI_CTRL_REG &= ~(SPI_SPI_CTRL_REG_SPI_ON_Msk |
                            SPI_SPI_CTRL_REG_SPI_INT_BIT_Msk);
    regs->SPI_CTRL_REG |= SPI_SPI_CTRL_REG_SPI_RST_Msk;

    return 0;
}

uint16_t
hal_spi_tx_val(int spi_num, uint16_t val)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;
    uint16_t dummy;
    uint32_t ctrl_reg;
    bool nine_bits = false;

    spi = hal_spi_resolve(spi_num);
    if (!spi || spi->spi_type == HAL_SPI_TYPE_SLAVE) {
        return 0xFFFF;
    }
    regs = spi->hw->regs;

    /* Get rid of old data if any */
    while (!(regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_TX_FIFO_EMPTY_Msk)) {
    }

    while (!(regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_RX_FIFO_EMPTY_Msk)) {
        dummy = regs->SPI_RX_TX_REG;
        (void)dummy;
    }

    ctrl_reg = regs->SPI_CTRL_REG;
    /* 9-bit word */
    nine_bits = (ctrl_reg & SPI_SPI_CTRL_REG_SPI_WORD_Msk) ==
        (3U << SPI_SPI_CTRL_REG_SPI_WORD_Pos);
    if (nine_bits) {
        ctrl_reg &= ~SPI_SPI_CTRL_REG_SPI_9BIT_VAL_Msk;
        ctrl_reg |= (val << (SPI_SPI_CTRL_REG_SPI_9BIT_VAL_Pos - 8) ) &
            SPI_SPI_CTRL_REG_SPI_9BIT_VAL_Msk;
    }
    regs->SPI_RX_TX_REG = (uint8_t)val;
    while (regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_RX_FIFO_EMPTY_Msk) {
    }

    ctrl_reg = regs->SPI_CTRL_REG;
    val = (uint8_t)regs->SPI_RX_TX_REG;
    if (nine_bits) {
        val |= (ctrl_reg & SPI_SPI_CTRL_REG_SPI_9BIT_VAL_Msk) >>
                (SPI_SPI_CTRL_REG_SPI_9BIT_VAL_Pos - 8);
    }

    return val;
}

int
hal_spi_set_txrx_cb(int spi_num, hal_spi_txrx_cb txrx_cb, void *arg)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;
    int rc = 0;

    spi = hal_spi_resolve(spi_num);
    if (!spi) {
        rc = SYS_EINVAL;
        goto err;
    }
    regs = spi->hw->regs;


    if ((regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_ON_Msk) != 0) {
        rc = SYS_EINVAL;
    } else {
        spi->txrx_cb_func = txrx_cb;
        spi->txrx_cb_arg = arg;
    }

err:
    return rc;
}

int
hal_spi_txrx(int spi_num, void *txbuf, void *rxbuf, int len)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;
    int rc = 0;
    const uint8_t *tx;
    uint8_t *rx;
    int rxlen = 0;
    uint8_t val;

    spi = hal_spi_resolve(spi_num);
    if (spi == NULL || txbuf == NULL || spi->spi_type != HAL_SPI_TYPE_MASTER) {
        rc = SYS_EINVAL;
        goto err;
    }
    regs = spi->hw->regs;
    tx = txbuf;
    rx = rxbuf;

    /* Flush RX FIFO */
    while (!(regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_RX_FIFO_EMPTY_Msk)) {
        val = (uint8_t)regs->SPI_RX_TX_REG;
    }

    rxlen = len;

    while (len) {
        if (0 == (regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_TXH_Msk)) {
            regs->SPI_RX_TX_REG = *tx++;
            len--;
        }
        if (0 == (regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_RX_FIFO_EMPTY_Msk)) {
            val = (uint8_t)regs->SPI_RX_TX_REG;
            if (rx) {
                *rx++ = val;
            }
            rxlen--;
        }
    }
    while (rxlen) {
        if (0 == (regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_RX_FIFO_EMPTY_Msk)) {
            val = (uint8_t)regs->SPI_RX_TX_REG;
            if (rx) {
                *rx++ = val;
            }
            rxlen--;
        }
    }
err:
    return rc;
}

int
hal_spi_txrx_noblock(int spi_num, void *txbuf, void *rxbuf, int len)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;
    uint32_t ctrl_reg;
    int rc = 0;

    spi = hal_spi_resolve(spi_num);
    if (spi == NULL) {
        rc = SYS_EINVAL;
        goto err;
    }
    if (SPI_MASTER_CODE && txbuf == NULL && spi->spi_type != HAL_SPI_TYPE_MASTER) {
        rc = SYS_EINVAL;
        goto err;
    } else if (SPI_SLAVE_CODE && txbuf == NULL && rxbuf == NULL &&
               spi->spi_type == HAL_SPI_TYPE_SLAVE) {
        rc = SYS_EINVAL;
        goto err;
    }
    regs = spi->hw->regs;
    spi->txbuf = txbuf;
    spi->rxbuf = rxbuf;
    spi->len = (uint16_t)len;
    spi->txlen = 0;
    spi->rxlen = 0;

    if (da1469x_hal_spi_do_transfer(spi)) {
        if (spi->txrx_cb_func) {
            spi->txrx_cb_func(spi->txrx_cb_arg, spi->len);
        }
    } else {
        ctrl_reg = regs->SPI_CTRL_REG | SPI_SPI_CTRL_REG_SPI_MINT_Msk;
        if (spi->txlen < spi->len) {
            ctrl_reg |= SPI_SPI_CTRL_REG_SPI_TX_FIFO_NOTFULL_MASK_Msk;
        }
        regs->SPI_CTRL_REG = ctrl_reg;
    }
err:
    return rc;
}

int
hal_spi_slave_set_def_tx_val(int spi_num, uint16_t val)
{
    return 0;
}

int
hal_spi_abort(int spi_num)
{
    SPI_Type *regs;
    struct da1469x_hal_spi *spi;
    int rc = 0;

    spi = hal_spi_resolve(spi_num);
    if (spi == NULL) {
        rc = SYS_EINVAL;
        goto err;
    }
    regs = spi->hw->regs;
    regs->SPI_CTRL_REG &= ~(SPI_SPI_CTRL_REG_SPI_MINT_Msk |
        SPI_SPI_CTRL_REG_SPI_TX_FIFO_NOTFULL_MASK_Msk);

    spi->len = 0;
    spi->txlen = 0;
    spi->rxlen = 0;

err:
    return rc;
}

int
hal_spi_init_hw(uint8_t spi_num, uint8_t spi_type,
                const struct hal_spi_hw_settings *cfg)
{
    struct da1469x_hal_spi_cfg hal_cfg;

    hal_cfg.pin_sck = (uint8_t)cfg->pin_sck;
    if (spi_type == HAL_SPI_TYPE_MASTER) {
        hal_cfg.pin_do = (uint8_t)cfg->pin_mosi;
        hal_cfg.pin_di = (uint8_t)cfg->pin_miso;
    } else {
        hal_cfg.pin_di = (uint8_t)cfg->pin_mosi;
        hal_cfg.pin_do = (uint8_t)cfg->pin_miso;
    }
    hal_cfg.pin_ss = (uint8_t)cfg->pin_ss;

    return hal_spi_init(spi_num, &hal_cfg, spi_type);
}

#endif
