/*
 * 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 <stdbool.h>
#include "os/mynewt.h"
#include "hal/hal_spi.h"
#include "mcu/hal_apollo2.h"
#include "mcu/cmsis_nvic.h"

#include "am_mcu_apollo.h"

/* Prevent CMSIS from breaking apollo2 macros. */
#undef GPIO
#undef IOSLAVE
#undef CLKGEN

#define SPI_0_ENABLED (MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE))
#define SPI_1_ENABLED (MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE))
#define SPI_2_ENABLED (MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE))
#define SPI_3_ENABLED (MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_3_SLAVE))
#define SPI_4_ENABLED (MYNEWT_VAL(SPI_4_MASTER) || MYNEWT_VAL(SPI_4_SLAVE))
#define SPI_5_ENABLED (MYNEWT_VAL(SPI_5_MASTER) || MYNEWT_VAL(SPI_5_SLAVE))

#define SPI_ANY_ENABLED (       \
    SPI_0_ENABLED ||            \
    SPI_1_ENABLED ||            \
    SPI_2_ENABLED ||            \
    SPI_3_ENABLED ||            \
    SPI_4_ENABLED ||            \
    SPI_5_ENABLED)

#if SPI_ANY_ENABLED

#define SPI_N_MASTER (          \
    MYNEWT_VAL(SPI_0_MASTER) || \
    MYNEWT_VAL(SPI_1_MASTER) || \
    MYNEWT_VAL(SPI_2_MASTER) || \
    MYNEWT_VAL(SPI_3_MASTER) || \
    MYNEWT_VAL(SPI_4_MASTER) || \
    MYNEWT_VAL(SPI_5_MASTER))

#define SPI_N_SLAVE (           \
    MYNEWT_VAL(SPI_0_SLAVE) ||  \
    MYNEWT_VAL(SPI_1_SLAVE) ||  \
    MYNEWT_VAL(SPI_2_SLAVE) ||  \
    MYNEWT_VAL(SPI_3_SLAVE) ||  \
    MYNEWT_VAL(SPI_4_SLAVE) ||  \
    MYNEWT_VAL(SPI_5_SLAVE))

#define APOLLO2_SPI_MAX_CHUNK_SZ    64
#define APOLLO2_SPI_MAX_CHUNK_WORDS (APOLLO2_SPI_MAX_CHUNK_SZ / 4)
#define APOLLO2_SPI_MAX_TXR_SZ      4095

#define APOLLO2_SPI_OP_NONE         0
#define APOLLO2_SPI_OP_BLOCKING     1
#define APOLLO2_SPI_OP_NONBLOCKING  2

/* IRQ handler type */
typedef void apollo2_spi_irq_handler(void);

struct apollo2_spi {
    volatile uint8_t op;

    const uint8_t *txbuf;
    uint8_t *rxbuf;
    int buf_num_bytes;
    int buf_off;
    uint32_t interrupts;
    uint16_t buf_len;
    uint8_t prev_num_bytes;
    uint8_t spi_num;
    uint8_t spi_type;

    uint8_t enabled:1;

    hal_spi_txrx_cb txrx_cb_func;
    void *txrx_cb_arg;
};

static void apollo2_spi_service_master(struct apollo2_spi *spi,
                                       uint32_t status);

static uint32_t apollo2_spi_fifo[APOLLO2_SPI_MAX_CHUNK_WORDS];

#if SPI_0_ENABLED
static struct apollo2_spi apollo2_spi0;
#endif
#if SPI_1_ENABLED
static struct apollo2_spi apollo2_spi1;
#endif
#if SPI_2_ENABLED
static struct apollo2_spi apollo2_spi2;
#endif
#if SPI_3_ENABLED
static struct apollo2_spi apollo2_spi3;
#endif
#if SPI_4_ENABLED
static struct apollo2_spi apollo2_spi4;
#endif
#if SPI_5_ENABLED
static struct apollo2_spi apollo2_spi5;
#endif

static uint32_t
apollo2_spi_data_mode_to_ios(int spi_mode)
{
    switch (spi_mode) {
        case HAL_SPI_MODE0:     return AM_HAL_IOS_SPIMODE_0;
        case HAL_SPI_MODE1:     return AM_HAL_IOS_SPIMODE_1;
        case HAL_SPI_MODE2:     return AM_HAL_IOS_SPIMODE_2;
        case HAL_SPI_MODE3:     return AM_HAL_IOS_SPIMODE_3;
        default:                return -1;
    }
}

static struct apollo2_spi *
apollo2_spi_resolve(int spi_num)
{
    switch (spi_num) {
#if SPI_0_ENABLED
    case 0:
        return &apollo2_spi0;
#endif
#if SPI_1_ENABLED
    case 1:
        return &apollo2_spi1;
#endif
#if SPI_2_ENABLED
    case 2:
        return &apollo2_spi2;
#endif
#if SPI_3_ENABLED
    case 3:
        return &apollo2_spi3;
#endif
#if SPI_4_ENABLED
    case 4:
        return &apollo2_spi4;
#endif
#if SPI_5_ENABLED
    case 5:
        return &apollo2_spi5;
#endif
    default:
        return NULL;
    }
}

static int
apollo2_spi_fifo_count(int spi_num)
{
    return AM_BFRn(IOMSTR, spi_num, FIFOPTR, FIFOSIZ);
}

static int
apollo2_spi_fifo_space(int spi_num)
{
    return APOLLO2_SPI_MAX_CHUNK_SZ - apollo2_spi_fifo_count(spi_num);
}

static void
apollo2_spi_block_until_idle(const struct apollo2_spi *spi)
{
    while (spi->op != APOLLO2_SPI_OP_NONE) { }
}

static void
apollo2_spi_clear_ints(int spi_num)
{
    AM_REGn(IOMSTR, spi_num, INTCLR) = 0xffffffff;
}

static void
apollo2_spi_disable_ints(struct apollo2_spi *spi)
{
    /* Remember currently-enabled interrupts. */
    assert(spi->interrupts == 0);
    spi->interrupts = AM_REGn(IOMSTR, spi->spi_num, INTEN);

    /* Disable interrupts. */
    AM_REGn(IOMSTR, spi->spi_num, INTEN) = 0;
}

static void
apollo2_spi_reenable_ints(struct apollo2_spi *spi)
{
    AM_REGn(IOMSTR, spi->spi_num, INTEN) = spi->interrupts;
    spi->interrupts = 0;
}

static uint32_t
apollo2_spi_status(int spi_num)
{
    uint32_t status;

    status = AM_REGn(IOMSTR, spi_num, INTSTAT);
    apollo2_spi_clear_ints(spi_num);

    return status;
}

static void
apollo2_spi_irqh_x(int spi_num)
{
    struct apollo2_spi *spi;
    uint32_t status;

    status = apollo2_spi_status(spi_num);

    spi = apollo2_spi_resolve(spi_num);
    assert(spi != NULL);

    switch (spi->spi_type) {
    case HAL_SPI_TYPE_MASTER:
        apollo2_spi_service_master(spi, status);
        break;

    case HAL_SPI_TYPE_SLAVE:
        /* XXX: Slave unimplemented. */
        break;

    default:
        assert(0);
        break;
    }
}

#if SPI_0_ENABLED
static void apollo2_spi_irqh_0(void) { apollo2_spi_irqh_x(0); }
#endif
#if SPI_1_ENABLED
static void apollo2_spi_irqh_1(void) { apollo2_spi_irqh_x(1); }
#endif
#if SPI_2_ENABLED
static void apollo2_spi_irqh_2(void) { apollo2_spi_irqh_x(2); }
#endif
#if SPI_3_ENABLED
static void apollo2_spi_irqh_3(void) { apollo2_spi_irqh_x(3); }
#endif
#if SPI_4_ENABLED
static void apollo2_spi_irqh_4(void) { apollo2_spi_irqh_x(4); }
#endif
#if SPI_5_ENABLED
static void apollo2_spi_irqh_5(void) { apollo2_spi_irqh_x(5); }
#endif

static int
apollo2_spi_irq_info(int spi_num, int *out_irq_num,
                     apollo2_spi_irq_handler **out_irqh)
{
    switch (spi_num) {
#if SPI_0_ENABLED
    case 0:
        *out_irq_num = IOMSTR0_IRQn;
        *out_irqh = apollo2_spi_irqh_0;
        return 0;
#endif
#if SPI_1_ENABLED
    case 1:
        *out_irq_num = IOMSTR1_IRQn;
        *out_irqh = apollo2_spi_irqh_1;
        return 0;
#endif
#if SPI_2_ENABLED
    case 2:
        *out_irq_num = IOMSTR2_IRQn;
        *out_irqh = apollo2_spi_irqh_2;
        return 0;
#endif
#if SPI_3_ENABLED
    case 3:
        *out_irq_num = IOMSTR3_IRQn;
        *out_irqh = apollo2_spi_irqh_3;
        return 0;
#endif
#if SPI_4_ENABLED
    case 4:
        *out_irq_num = IOMSTR4_IRQn;
        *out_irqh = apollo2_spi_irqh_4;
        return 0;
#endif
#if SPI_5_ENABLED
    case 5:
        *out_irq_num = IOMSTR5_IRQn;
        *out_irqh = apollo2_spi_irqh_5;
        return 0;
#endif
    default:
        return SYS_EINVAL;
    }
}

static int
hal_spi_config_master(int spi_num, const struct hal_spi_settings *settings)
{
    am_hal_iom_config_t sdk_config;
    int cpol;
    int cpha;
    int rc;

    if (spi_num < 0 || spi_num >= AM_REG_IOMSTR_NUM_MODULES) {
        return SYS_EINVAL;
    }

    rc = hal_spi_data_mode_breakout(settings->data_mode, &cpol, &cpha);
    if (rc != 0) {
        return SYS_EINVAL;
    }

    am_hal_iom_pwrctrl_enable(spi_num);

    sdk_config.ui32InterfaceMode =
        AM_HAL_IOM_SPIMODE | AM_REG_IOMSTR_CFG_FULLDUP_FULLDUP;
    sdk_config.ui32ClockFrequency = settings->baudrate;
    sdk_config.bSPHA = cpha;
    sdk_config.bSPOL = cpol;
    sdk_config.ui8WriteThreshold = 4;
    sdk_config.ui8ReadThreshold = 60;
    am_hal_iom_config(spi_num, &sdk_config);

    return 0;
}

static int
hal_spi_config_slave(int spi_num, const struct hal_spi_settings *settings)
{
    uint32_t ios_data_mode;
    uint32_t cfg;

    cfg = AM_REG_IOSLAVE_FIFOCFG_ROBASE(0x78 >> 3);
    cfg |= AM_REG_IOSLAVE_FIFOCFG_FIFOBASE(0x80 >> 3);
    cfg |= AM_REG_IOSLAVE_FIFOCFG_FIFOMAX(0x100 >> 3);

    ios_data_mode = apollo2_spi_data_mode_to_ios(settings->data_mode);

    AM_REG(IOSLAVE, CFG) = ios_data_mode;
    AM_REG(IOSLAVE, FIFOCFG) = cfg;
    return 0;
}

/*  | spi:cfg   | sck   | miso  | mosi  |
 *  |-----------+-------+-------+-------|
 *  | 0:1       | 5     | 6     | 7     |
 *  | 1:1       | 8     | 9     | 10    |
 *  | 2:5       | 0     | 2     | 1     |
 *  | 2:5       | 27    | 28    | 25    |
 *  | 3:5       | 42    | 43    | 38    |
 *  | 4:5       | 39    | 40    | 44    |
 *  | 5:5       | 48    | 49    | 47    |
 */
static int
hal_spi_pin_config_master(int spi_num, const struct apollo2_spi_cfg *pins)
{
    const uint8_t miso = pins->miso_pin;
    const uint8_t mosi = pins->mosi_pin;
    const uint8_t sck = pins->sck_pin;

    switch (spi_num) {
#if SPI_0_ENABLED
    case 0:
        if (sck == 5 && miso == 6 && mosi == 7) {
            return 1;
        } else {
            return -1;
        }
#endif
#if SPI_1_ENABLED
    case 1:
        if (sck == 8 && miso == 9 && mosi == 10) {
            return 1;
        } else {
            return -1;
        }
#endif
#if SPI_2_ENABLED
    case 2:
        if (sck == 0 && miso == 2 && mosi == 1) {
            return 5;
        } else if (sck == 27 && miso == 28 && mosi == 25) {
            return 5;
        } else {
            return -1;
        }
#endif
#if SPI_3_ENABLED
    case 3:
        if (sck == 42 && miso == 43 && mosi == 38) {
            return 5;
        } else {
            return -1;
        }
#endif
#if SPI_4_ENABLED
    case 4:
        if (sck == 39 && miso == 40 && mosi == 44) {
            return 5;
        } else {
            return -1;
        }
#endif
#if SPI_5_ENABLED
    case 5:
        if (sck == 48 && miso == 49 && mosi == 47) {
            return 5;
        } else {
            return -1;
        }
#endif
    default:
        return -1;
    }
}

static int
hal_spi_pin_config(int spi_num, int master, const struct apollo2_spi_cfg *pins)
{
    if (master) {
        return hal_spi_pin_config_master(spi_num, pins);
    } else {
        return -1;
    }
}

static int
hal_spi_init_master(int spi_num, const struct apollo2_spi_cfg *cfg)
{
    apollo2_spi_irq_handler *irqh;
    struct apollo2_spi *spi;
    int pin_cfg;
    int irq_num;
    int rc;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    pin_cfg = hal_spi_pin_config(spi_num, 1, cfg);
    if (pin_cfg == -1) {
        return SYS_EINVAL;
    }

    am_hal_gpio_pin_config(
        cfg->sck_pin, AM_HAL_GPIO_FUNC(pin_cfg) | AM_HAL_PIN_DIR_INPUT);
    am_hal_gpio_pin_config(
        cfg->miso_pin, AM_HAL_GPIO_FUNC(pin_cfg) | AM_HAL_PIN_DIR_INPUT);
    am_hal_gpio_pin_config(
        cfg->mosi_pin, AM_HAL_GPIO_FUNC(pin_cfg));

    memset(spi, 0, sizeof *spi);
    spi->spi_num = spi_num;
    spi->spi_type = HAL_SPI_TYPE_MASTER;

    rc = apollo2_spi_irq_info(spi_num, &irq_num, &irqh);
    if (rc != 0) {
        return rc;
    }

    NVIC_SetVector(irq_num, (uint32_t)irqh);
    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(int spi_num, struct apollo2_spi_cfg *cfg)
{
    return SYS_ERANGE;
}

/**
 * Initialize the SPI, given by spi_num.
 *
 * @param spi_num The number of the SPI to initialize
 * @param cfg HW/MCU specific configuration,
 *            passed to the underlying implementation, providing extra
 *            configuration.
 * @param spi_type SPI type (master or slave)
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_init(int spi_num, void *cfg, uint8_t spi_type)
{
    int rc;

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

    switch (spi_type) {
    case HAL_SPI_TYPE_MASTER:
        rc = hal_spi_init_master(spi_num, cfg);
        if (rc != 0) {
            return rc;
        }
        break;

    case HAL_SPI_TYPE_SLAVE:
        rc = hal_spi_init_slave(spi_num, cfg);
        if (rc != 0) {
            return rc;
        }
        break;

    default:
        return SYS_EINVAL;
    }

    return 0;
}

/**
 * Configure the spi. Must be called after the spi is initialized (after
 * hal_spi_init is called) and when the spi is disabled (user must call
 * hal_spi_disable if the spi has been enabled through hal_spi_enable prior
 * to calling this function). Can also be used to reconfigure an initialized
 * SPI (assuming it is disabled as described previously).
 *
 * @param spi_num The number of the SPI to configure.
 * @param psettings The settings to configure this SPI with
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_config(int spi_num, struct hal_spi_settings *settings)
{
    const struct apollo2_spi *spi;
    int rc;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
        rc = hal_spi_config_master(spi_num, settings);
    } else {
        rc = hal_spi_config_slave(spi_num, settings);
    }

    return rc;
}

/**
 * Enables the SPI. This does not start a transmit or receive operation;
 * it is used for power mgmt. Cannot be called when a SPI transfer is in
 * progress.
 *
 * @param spi_num
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_enable(int spi_num)
{
    struct apollo2_spi *spi;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    if (spi->enabled) {
        return SYS_EBUSY;
    }

    switch (spi->spi_type) {
    case HAL_SPI_TYPE_MASTER:
        AM_REGn(IOMSTR, spi_num, CFG) |= AM_REG_IOMSTR_CFG_IFCEN(1);
        AM_REGn(IOMSTR, spi_num, INTEN) = 0xffffffff;

        if (spi_num == 0) {
            AM_REGn(GPIO, 0, PADKEY) = AM_REG_GPIO_PADKEY_KEYVAL;
            AM_BFW(GPIO, PADREGB, PAD5INPEN, 1);
            AM_BFW(GPIO, PADREGB, PAD6INPEN, 1);
            AM_REGn(GPIO, 0, PADKEY) = 0;
        } else {
            AM_REGn(GPIO, 0, PADKEY) = AM_REG_GPIO_PADKEY_KEYVAL;
            AM_BFW(GPIO, PADREGC, PAD8INPEN, 1);
            AM_BFW(GPIO, PADREGC, PAD9INPEN, 1);
            AM_REGn(GPIO, 0, PADKEY) = 0;
        }
        break;

    case HAL_SPI_TYPE_SLAVE:
        AM_REGn(IOSLAVE, spi_num, CFG) |= AM_REG_IOSLAVE_CFG_IFCEN(1);
        break;

    default:
        return SYS_EINVAL;
    }

    spi->enabled = 1;
    return 0;
}

/**
 * Disables the SPI. Used for power mgmt. It will halt any current SPI transfers
 * in progress.
 *
 * @param spi_num
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_disable(int spi_num)
{
    struct apollo2_spi *spi;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    switch (spi->spi_type) {
    case HAL_SPI_TYPE_MASTER:
        apollo2_spi_block_until_idle(spi);
        AM_REGn(IOMSTR, spi_num, CFG) &= ~AM_REG_IOMSTR_CFG_IFCEN(1);
        spi->enabled = 0;
        return 0;

    case HAL_SPI_TYPE_SLAVE:
        AM_REGn(IOSLAVE, spi_num, CFG) &= ~AM_REG_IOSLAVE_CFG_IFCEN(1);
        return 0;

    default:
        return SYS_EINVAL;
    }
}

static void
apollo2_spi_fifo_read(struct apollo2_spi *spi, void *rx_data, int num_bytes)
{
    int num_words;
    int i;

    num_words = (num_bytes + 3) / 4;
    for (i = 0; i < num_words; i++) {
        apollo2_spi_fifo[i] = AM_REGn(IOMSTR, spi->spi_num, FIFO);
    }

    if (rx_data != NULL) {
        memcpy(rx_data, apollo2_spi_fifo, num_bytes);
    }
}

static void
apollo2_spi_fifo_write(struct apollo2_spi *spi,
                       const void *tx_data, int num_bytes)
{
    uint32_t word;
    int num_words;
    int i;

    assert(num_bytes != 0);

    memcpy(apollo2_spi_fifo, tx_data, num_bytes);

    num_words = (num_bytes + 3) / 4;
    for (i = 0; i < num_words; i++) {
        if (tx_data == NULL) {
            word = 0;
        } else {
            word = apollo2_spi_fifo[i];
        }
        AM_REGn(IOMSTR, spi->spi_num, FIFO) = word;
    }
}

static int
apollo2_spi_next_chunk_sz(int buf_sz, int off, int fifo_space)
{
    int bytes_left;

    bytes_left = buf_sz - off;
    if (bytes_left > fifo_space) {
        return fifo_space;
    } else {
        return bytes_left;
    }
}

static int
apollo2_spi_tx_next_chunk(struct apollo2_spi *spi)
{
    int fifo_space;
    int chunk_sz;

    fifo_space = apollo2_spi_fifo_space(spi->spi_num);
    chunk_sz = apollo2_spi_next_chunk_sz(spi->buf_num_bytes, spi->buf_off,
                                         fifo_space);
    if (chunk_sz <= 0) {
        return 0;
    }

    apollo2_spi_clear_ints(spi->spi_num);

    apollo2_spi_fifo_write(spi, spi->txbuf + spi->buf_off, chunk_sz);
    spi->prev_num_bytes = chunk_sz;

    return SYS_EAGAIN;
}

static uint32_t
apollo2_spi_cmd_build(uint16_t num_bytes, uint8_t channel)
{
    return 0x40000000  /* Raw write. */     |
           (num_bytes & 0xF00) << 15        |
           (num_bytes & 0xFF)               |
           channel << 16;
}

static void
apollo2_spi_tx_first_chunk(struct apollo2_spi *spi)
{
    uint32_t cmd;

    apollo2_spi_tx_next_chunk(spi);

    cmd = apollo2_spi_cmd_build(spi->buf_num_bytes, 0);
    apollo2_spi_disable_ints(spi);
    AM_REGn(IOMSTR, spi->spi_num, CMD) = cmd;
    apollo2_spi_reenable_ints(spi);
}

static void
apollo2_spi_service_master(struct apollo2_spi *spi, uint32_t status)
{
    uint8_t prev_op;
    int rc;

    if (spi->op == APOLLO2_SPI_OP_NONE) {
        /* Spurious interrupt or programming error. */
        return;
    }

    /* Copy received data. */
    apollo2_spi_fifo_read(spi, spi->rxbuf + spi->buf_off, spi->prev_num_bytes);
    spi->buf_off += spi->prev_num_bytes;

    assert(spi->buf_off <= spi->buf_num_bytes);

    if (!(status & AM_HAL_IOM_INT_THR)) {
        /* Error or command complete. */

        prev_op = spi->op;
        spi->op = APOLLO2_SPI_OP_NONE;

        if (prev_op == APOLLO2_SPI_OP_NONBLOCKING) {
            spi->txrx_cb_func(spi->txrx_cb_arg, spi->buf_off);
        }

        return;
    }

    /* Transmit next chunk. */
    rc = apollo2_spi_tx_next_chunk(spi);
    assert(rc == 0);
}

static int
apollo2_spi_txrx_begin(struct apollo2_spi *spi, uint8_t op,
                       const void *tx_data, void *rx_data, int num_bytes)
{
    if (spi->op != APOLLO2_SPI_OP_NONE) {
        return SYS_EBUSY;
    }

    if (num_bytes <= 0 || num_bytes > APOLLO2_SPI_MAX_TXR_SZ) {
        return SYS_EINVAL;
    }

    spi->op = op;
    spi->txbuf = tx_data;
    spi->rxbuf = rx_data;
    spi->buf_num_bytes = num_bytes;
    spi->buf_off = 0;

    apollo2_spi_tx_first_chunk(spi);
    return 0;
}

static int
apollo2_spi_txrx_blocking(struct apollo2_spi *spi,
                          const void *tx_data, void *rx_data, int num_bytes)
{
    int rc;

    rc = apollo2_spi_txrx_begin(spi, APOLLO2_SPI_OP_BLOCKING,
                                tx_data, rx_data, num_bytes);
    if (rc != 0) {
        return rc;
    }

    apollo2_spi_block_until_idle(spi);

    return 0;
}

/**
 * Blocking call to send a value on the SPI. Returns the value received from
 * the SPI slave.
 *
 * MASTER: Sends the value and returns the received value from the slave.
 * SLAVE: Invalid API. Returns 0xFFFF
 *
 * @param spi_num   Spi interface to use
 * @param val       Value to send
 *
 * @return uint16_t Value received on SPI interface from slave. Returns 0xFFFF
 * if called when the SPI is configured to be a slave
 */
uint16_t
hal_spi_tx_val(int spi_num, uint16_t val)
{
    struct apollo2_spi *spi;
    uint8_t tx_data;
    uint8_t rx_data;
    int rc;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return 0xffff;
    }

    switch (spi->spi_type) {
    case HAL_SPI_TYPE_MASTER:
        tx_data = val;
        rc = apollo2_spi_txrx_blocking(spi, &tx_data, &rx_data, 1);
        if (rc == 0) {
            return rx_data;
        } else {
            return 0xffff;
        }

    case HAL_SPI_TYPE_SLAVE:
        return 0xffff;

    default:
        return 0xffff;
    }
}

/**
 * Sets the txrx callback (executed at interrupt context) when the
 * buffer is transferred by the master or the slave using the non-blocking API.
 * Cannot be called when the spi is enabled. This callback will also be called
 * when chip select is de-asserted on the slave.
 *
 * NOTE: This callback is only used for the non-blocking interface and must
 * be called prior to using the non-blocking API.
 *
 * @param spi_num   SPI interface on which to set callback
 * @param txrx      Callback function
 * @param arg       Argument to be passed to callback function
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_set_txrx_cb(int spi_num, hal_spi_txrx_cb txrx_cb, void *arg)
{
    struct apollo2_spi *spi;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    if (spi->enabled) {
        return SYS_EBUSY;
    }

    spi->txrx_cb_func = txrx_cb;
    spi->txrx_cb_arg = arg;

    return 0;
}

/**
 * Blocking interface to send a buffer and store the received values from the
 * slave. The transmit and receive buffers are either arrays of 8-bit (uint8_t)
 * values or 16-bit values depending on whether the spi is configured for 8 bit
 * data or more than 8 bits per value. The 'cnt' parameter is the number of
 * 8-bit or 16-bit values. Thus, if 'cnt' is 10, txbuf/rxbuf would point to an
 * array of size 10 (in bytes) if the SPI is using 8-bit data; otherwise
 * txbuf/rxbuf would point to an array of size 20 bytes (ten, uint16_t values).
 *
 * NOTE: these buffers are in the native endian-ness of the platform.
 *
 *     MASTER: master sends all the values in the buffer and stores the
 *             stores the values in the receive buffer if rxbuf is not NULL.
 *             The txbuf parameter cannot be NULL.
 *     SLAVE: cannot be called for a slave; returns -1
 *
 * @param spi_num   SPI interface to use
 * @param txbuf     Pointer to buffer where values to transmit are stored.
 * @param rxbuf     Pointer to buffer to store values received from peer.
 * @param cnt       Number of 8-bit or 16-bit values to be transferred.
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_txrx(int spi_num, void *txbuf, void *rxbuf, int num_bytes)
{
    struct apollo2_spi *spi;
    int rc;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    rc = apollo2_spi_txrx_blocking(spi, txbuf, rxbuf, num_bytes);
    return rc;
}

/**
 * Non-blocking interface to send a buffer and store received values. Can be
 * used for both master and slave SPI types. The user must configure the
 * callback (using hal_spi_set_txrx_cb); the txrx callback is executed at
 * interrupt context when the buffer is sent.
 *
 * The transmit and receive buffers are either arrays of 8-bit (uint8_t)
 * values or 16-bit values depending on whether the spi is configured for 8 bit
 * data or more than 8 bits per value. The 'cnt' parameter is the number of
 * 8-bit or 16-bit values. Thus, if 'cnt' is 10, txbuf/rxbuf would point to an
 * array of size 10 (in bytes) if the SPI is using 8-bit data; otherwise
 * txbuf/rxbuf would point to an array of size 20 bytes (ten, uint16_t values).
 *
 * NOTE: these buffers are in the native endian-ness of the platform.
 *
 *     MASTER: master sends all the values in the buffer and stores the
 *             stores the values in the receive buffer if rxbuf is not NULL.
 *             The txbuf parameter cannot be NULL
 *     SLAVE: Slave "preloads" the data to be sent to the master (values
 *            stored in txbuf) and places received data from master in rxbuf
 *            (if not NULL). The txrx callback occurs when len values are
 *            transferred or master de-asserts chip select. If txbuf is NULL,
 *            the slave transfers its default byte. Both rxbuf and txbuf cannot
 *            be NULL.
 *
 * @param spi_num   SPI interface to use
 * @param txbuf     Pointer to buffer where values to transmit are stored.
 * @param rxbuf     Pointer to buffer to store values received from peer.
 * @param num_bytes Number of 8-bit values to be transferred.
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_txrx_noblock(int spi_num, void *txbuf, void *rxbuf, int num_bytes)
{
    struct apollo2_spi *spi;
    int rc;

    spi = apollo2_spi_resolve(spi_num);
    if (spi == NULL) {
        return SYS_EINVAL;
    }

    if (spi->txrx_cb_func == NULL) {
        return SYS_ENOENT;
    }

    if (spi->op != APOLLO2_SPI_OP_NONE) {
        return SYS_EBUSY;
    }

    switch (spi->spi_type) {
    case HAL_SPI_TYPE_MASTER:
        rc = apollo2_spi_txrx_begin(spi, APOLLO2_SPI_OP_NONBLOCKING,
                                    txbuf, rxbuf, num_bytes);
        return rc;

    case HAL_SPI_TYPE_SLAVE:
        spi->txbuf = txbuf;
        spi->rxbuf = rxbuf;
        spi->op = APOLLO2_SPI_OP_NONBLOCKING;
        return 0;

    default:
        return SYS_EINVAL;
    }
}

/**
 * Sets the default value transferred by the slave. Not valid for master
 *
 * @param spi_num SPI interface to use
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_slave_set_def_tx_val(int spi_num, uint16_t val)
{
    return SYS_ERANGE;
}

/**
 * This aborts the current transfer but keeps the spi enabled.
 *
 * @param spi_num   SPI interface on which transfer should be aborted.
 *
 * @return int 0 on success, non-zero error code on failure.
 *
 * NOTE: does not return an error if no transfer was in progress.
 */
int
hal_spi_abort(int spi_num)
{
    return SYS_ERANGE;
}

#endif /* SPI_ANY_ENABLED */
