/*
 * 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 <hal/hal_spi.h>
#include <hal/hal_spi.h>
#include <mcu/cmsis_nvic.h>
#include "mcu/nrf51_hal.h"
#include "nrf51_bitfields.h"

/* XXX:
 * 1) what about stats?
 * 2) Dealing with errors (OVERFLOW, OVERREAD)
 * 3) Dont think I need dummy_rx as I can set master RX maxcnt to zero.
 */
/* IRQ handler type */
typedef void (*nrf51_spi_irq_handler_t)(void);

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

/* Used to disable all interrupts */
#define NRF_SPI_IRQ_DISABLE_ALL 0xFFFFFFFF

/*
 *  Slave states
 *
 *  IDLE: Slave not ready to be used. If master attempts to access
 *        slave it will receive the default character
 *  ACQ_SEM: Slave is attempting to acquire semaphore.
 *  READY: Slave is ready for master to send it data
 *
 */
#define HAL_SPI_SLAVE_STATE_IDLE        (0)
#define HAL_SPI_SLAVE_STATE_ACQ_SEM     (1)
#define HAL_SPI_SLAVE_STATE_READY       (2)

struct nrf51_hal_spi {
    uint8_t spi_type;
    uint8_t spi_xfr_flag;   /* Master only */
    uint8_t slave_state;    /* Slave only */
    uint16_t nhs_buflen;
    uint16_t nhs_rxd_bytes;
    uint16_t nhs_txd_bytes;
    struct hal_spi_settings spi_cfg; /* Slave and master */
    union {
        NRF_SPI_Type  *spim;
        NRF_SPIS_Type *spis;
    } nhs_spi;

    /* IRQ number */
    IRQn_Type irq_num;

    /* Pointers to tx/rx buffers */
    uint8_t *nhs_txbuf;
    uint8_t *nhs_rxbuf;

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

#if MYNEWT_VAL(SPI_0_MASTER)
struct nrf51_hal_spi nrf51_hal_spi0;
#endif
#if MYNEWT_VAL(SPI_1_MASTER)  || MYNEWT_VAL(SPI_1_SLAVE)
struct nrf51_hal_spi nrf51_hal_spi1;
#endif

static const struct nrf51_hal_spi *nrf51_hal_spis[NRF51_HAL_SPI_MAX] = {
#if MYNEWT_VAL(SPI_0_MASTER)
    &nrf51_hal_spi0,
#else
    NULL,
#endif
#if MYNEWT_VAL(SPI_1_MASTER)  || MYNEWT_VAL(SPI_1_SLAVE)
    &nrf51_hal_spi1
#else
    NULL
#endif
};

#define NRF51_HAL_SPI_RESOLVE(__n, __v)                     \
    if ((__n) >= NRF51_HAL_SPI_MAX) {                       \
        rc = EINVAL;                                        \
        goto err;                                           \
    }                                                       \
    (__v) = (struct nrf51_hal_spi *) nrf51_hal_spis[(__n)]; \
    if ((__v) == NULL) {                                    \
        rc = EINVAL;                                        \
        goto err;                                           \
    }

#if (MYNEWT_VAL(SPI_0_MASTER)  || MYNEWT_VAL(SPI_1_MASTER) )
static void
nrf51_irqm_handler(struct nrf51_hal_spi *spi)
{
    NRF_SPI_Type *spim;

    spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
    if (spim->EVENTS_READY) {
        spim->EVENTS_READY = 0;

        if (spi->spi_xfr_flag == 0) {
            return;
        }

        if (spi->nhs_rxbuf) {
            spi->nhs_rxbuf[spi->nhs_rxd_bytes] = spim->RXD;
        }
        ++spi->nhs_rxd_bytes;
        if (spi->nhs_rxd_bytes == spi->nhs_buflen) {
            spi->spi_xfr_flag = 0;
            spim->INTENCLR = SPI_INTENCLR_READY_Msk;
            if (spi->txrx_cb_func) {
                spi->txrx_cb_func(spi->txrx_cb_arg, spi->nhs_buflen);
            }
        }
        if (spi->nhs_txd_bytes != spi->nhs_buflen) {
            spim->TXD = spi->nhs_txbuf[spi->nhs_txd_bytes];
            ++spi->nhs_txd_bytes;
        }
    }
}
#endif

#if (MYNEWT_VAL(SPI_1_SLAVE) )
static void
nrf51_irqs_handler(struct nrf51_hal_spi *spi)
{
    uint8_t xfr_len;
    NRF_SPIS_Type *spis;

    spis = spi->nhs_spi.spis;

    /* Semaphore acquired event */
    if (spis->EVENTS_ACQUIRED) {
        spis->EVENTS_ACQUIRED = 0;

        if (spi->slave_state == HAL_SPI_SLAVE_STATE_ACQ_SEM) {
            if (spi->nhs_txbuf == NULL) {
                spis->TXDPTR = 0;
                spis->MAXTX  = 0;
            } else {
                spis->TXDPTR = (uint32_t)spi->nhs_txbuf;
                spis->MAXTX  = spi->nhs_buflen;
            }

            if (spi->nhs_rxbuf == NULL) {
                spis->RXDPTR = 0;
                spis->MAXRX  = 0;

            } else {
                spis->RXDPTR = (uint32_t)spi->nhs_rxbuf;
                spis->MAXRX  = spi->nhs_buflen;
            }
            spis->TASKS_RELEASE = 1;
            spi->slave_state = HAL_SPI_SLAVE_STATE_READY;
        }
    }

    /* SPI transaction complete */
    if (spis->EVENTS_END) {
        spis->EVENTS_END = 0;

        if (spi->slave_state == HAL_SPI_SLAVE_STATE_READY) {
            if (spi->txrx_cb_func) {
                /* Get transfer length */
                if (spi->nhs_txbuf == NULL) {
                    xfr_len = (uint8_t)spis->AMOUNTRX;

                } else {
                    xfr_len = (uint8_t)spis->AMOUNTTX;
                }
                spi->txrx_cb_func(spi->txrx_cb_arg, xfr_len);
            }
            spi->slave_state = HAL_SPI_SLAVE_STATE_IDLE;
        }
    }

}
#endif

/* Interrupt handlers for SPI ports */
#if MYNEWT_VAL(SPI_0_MASTER)
void
nrf51_spi0_irq_handler(void)
{
    if (nrf51_hal_spi0.spi_type == HAL_SPI_TYPE_MASTER) {
        nrf51_irqm_handler(&nrf51_hal_spi0);
    } else {
        assert(0);
    }
}
#endif

#if MYNEWT_VAL(SPI_1_MASTER)  || MYNEWT_VAL(SPI_1_SLAVE)
void
nrf51_spi1_irq_handler(void)
{
    if (nrf51_hal_spi1.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_1_MASTER)
        nrf51_irqm_handler(&nrf51_hal_spi1);
#else
        assert(0);
#endif
    } else {
#if MYNEWT_VAL(SPI_1_SLAVE)
        nrf51_irqs_handler(&nrf51_hal_spi1);
#else
        assert(0);
#endif
    }
}
#endif

static int
hal_spi_config_master(struct nrf51_hal_spi *spi,
                      struct hal_spi_settings *settings)
{
    int rc;
    uint32_t nrf_config;
    uint32_t frequency;
    NRF_SPI_Type *spim;

    spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
    memcpy(&spi->spi_cfg, settings, sizeof(*settings));

    /* Only 8-bit word sizes supported. */
    rc = 0;
    switch (settings->data_mode) {
        case HAL_SPI_MODE0:
            nrf_config = (SPI_CONFIG_CPOL_ActiveHigh << SPI_CONFIG_CPOL_Pos) |
                         (SPI_CONFIG_CPHA_Leading << SPI_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE1:
            nrf_config = (SPI_CONFIG_CPOL_ActiveHigh << SPI_CONFIG_CPOL_Pos) |
                         (SPI_CONFIG_CPHA_Trailing << SPI_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE2:
            nrf_config = (SPI_CONFIG_CPOL_ActiveLow << SPI_CONFIG_CPOL_Pos) |
                         (SPI_CONFIG_CPHA_Leading << SPI_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE3:
            nrf_config = (SPI_CONFIG_CPOL_ActiveLow << SPI_CONFIG_CPOL_Pos) |
                         (SPI_CONFIG_CPHA_Trailing << SPI_CONFIG_CPHA_Pos);
            break;
        default:
            nrf_config = 0;
            rc = EINVAL;
            break;
    }

    if (settings->data_order == HAL_SPI_LSB_FIRST) {
        nrf_config |= SPI_CONFIG_ORDER_LsbFirst;
    }
    spim->CONFIG = nrf_config;

    switch (settings->baudrate) {
        case 125:
            frequency = SPI_FREQUENCY_FREQUENCY_K125;
            break;
        case 250:
            frequency = SPI_FREQUENCY_FREQUENCY_K250;
            break;
        case 500:
            frequency = SPI_FREQUENCY_FREQUENCY_K500;
            break;
        case 1000:
            frequency = SPI_FREQUENCY_FREQUENCY_M1;
            break;
        case 2000:
            frequency = SPI_FREQUENCY_FREQUENCY_M2;
            break;
        case 4000:
            frequency = SPI_FREQUENCY_FREQUENCY_M4;
            break;
        case 8000:
            frequency = SPI_FREQUENCY_FREQUENCY_M8;
            break;
        default:
            frequency = 0;
            rc = EINVAL;
            break;
    }
    spim->FREQUENCY = frequency;

    /* Only supports 8-bit word size */
    if (settings->word_size != HAL_SPI_WORD_SIZE_8BIT) {
        rc = EINVAL;
    }

    return rc;
}

static int
hal_spi_config_slave(struct nrf51_hal_spi *spi,
                     struct hal_spi_settings *settings)
{
    int rc;
    uint32_t nrf_config;
    NRF_SPIS_Type *spis;

    spis = spi->nhs_spi.spis;
    memcpy(&spi->spi_cfg, settings, sizeof(*settings));

    rc = 0;
    switch (settings->data_mode) {
        case HAL_SPI_MODE0:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveHigh << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Leading << SPIS_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE1:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveHigh << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Trailing << SPIS_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE2:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveLow << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Leading << SPIS_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE3:
            nrf_config = (SPIS_CONFIG_CPOL_ActiveLow << SPIS_CONFIG_CPOL_Pos) |
                         (SPIS_CONFIG_CPHA_Trailing << SPIS_CONFIG_CPHA_Pos);
            break;
        default:
            nrf_config = 0;
            rc = EINVAL;
            break;
    }

    if (settings->data_order == HAL_SPI_LSB_FIRST) {
        nrf_config |= SPIS_CONFIG_ORDER_LsbFirst;
    }
    spis->CONFIG = nrf_config;

    /* Only 8-bit word sizes supported. */
    if (settings->word_size != HAL_SPI_WORD_SIZE_8BIT) {
        rc = EINVAL;
    }
    return rc;
}

static int
hal_spi_init_master(struct nrf51_hal_spi *spi,
                    struct nrf51_hal_spi_cfg *cfg,
                    nrf51_spi_irq_handler_t handler)
{
    NRF_SPI_Type *spim;

    /* Configure SCK pin */
    if (spi->spi_cfg.data_mode <= HAL_SPI_MODE1) {
        NRF_GPIO->OUTCLR = (1UL << cfg->sck_pin);
    } else {
        NRF_GPIO->OUTSET = (1UL << cfg->sck_pin);
    }
    NRF_GPIO->PIN_CNF[cfg->sck_pin] =
        (GPIO_PIN_CNF_DIR_Output        << GPIO_PIN_CNF_DIR_Pos)
      | (GPIO_PIN_CNF_INPUT_Connect     << GPIO_PIN_CNF_INPUT_Pos)
      | (GPIO_PIN_CNF_PULL_Disabled     << GPIO_PIN_CNF_PULL_Pos)
      | (GPIO_PIN_CNF_DRIVE_S0S1        << GPIO_PIN_CNF_DRIVE_Pos)
      | (GPIO_PIN_CNF_SENSE_Disabled    << GPIO_PIN_CNF_SENSE_Pos);

    /*  Configure MOSI */
    NRF_GPIO->OUTCLR = (1UL << cfg->mosi_pin);
    NRF_GPIO->PIN_CNF[cfg->mosi_pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);

    /* Configure MISO */
    NRF_GPIO->PIN_CNF[cfg->miso_pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
    spim->PSELSCK = cfg->sck_pin;
    spim->PSELMOSI = cfg->mosi_pin;
    spim->PSELMISO = cfg->miso_pin;

    spim->INTENCLR = SPI_INTENCLR_READY_Msk;

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

    return 0;
}

static int
hal_spi_init_slave(struct nrf51_hal_spi *spi,
                   struct nrf51_hal_spi_cfg *cfg,
                   nrf51_spi_irq_handler_t handler)
{
    NRF_SPIS_Type *spis;

    NRF_GPIO->PIN_CNF[cfg->miso_pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    NRF_GPIO->PIN_CNF[cfg->mosi_pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    NRF_GPIO->PIN_CNF[cfg->ss_pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_PULL_Pullup  << GPIO_PIN_CNF_PULL_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    NRF_GPIO->PIN_CNF[cfg->sck_pin] =
        ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
        ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);

    spis = (NRF_SPIS_Type *)spi->nhs_spi.spis;
    spis->PSELSCK  = cfg->sck_pin;
    spis->PSELMOSI = cfg->mosi_pin;
    spis->PSELMISO = cfg->miso_pin;
    spis->PSELCSN  = cfg->ss_pin;

    /* Disable interrupt and clear any interrupt events */
    spis->INTENCLR = SPIS_INTENSET_ACQUIRED_Msk | SPIS_INTENSET_END_Msk;
    spis->EVENTS_END = 0;
    spis->EVENTS_ACQUIRED = 0;

    /* Enable END_ACQUIRE shortcut. */
    spis->SHORTS = SPIS_SHORTS_END_ACQUIRE_Msk;

    /* Set interrupt vector and enable IRQ */
    NVIC_SetVector(spi->irq_num, (uint32_t)handler);
    NVIC_SetPriority(spi->irq_num, (1 << __NVIC_PRIO_BITS) - 1);
    NVIC_ClearPendingIRQ(spi->irq_num);
    NVIC_EnableIRQ(spi->irq_num);

    return 0;
}

static void
hal_spi_master_send_first(NRF_SPI_Type *spim, uint8_t txval)
{
    while (spim->EVENTS_READY) {
        (void)spim->RXD;
        spim->EVENTS_READY = 0;
    }
    spim->TXD = txval;
}

/**
 * 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;
    struct nrf51_hal_spi *spi;
    nrf51_spi_irq_handler_t irq_handler;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    /* Check for valid arguments */
    rc = EINVAL;
    if (cfg == NULL) {
        goto err;
    }

    if ((spi_type != HAL_SPI_TYPE_MASTER) && (spi_type != HAL_SPI_TYPE_SLAVE)) {
        goto err;
    }

    irq_handler = NULL;
    spi->spi_type  = spi_type;
    if (spi_num == 0) {
#if MYNEWT_VAL(SPI_0_MASTER)
        spi->irq_num = SPI0_TWI0_IRQn;
        irq_handler = nrf51_spi0_irq_handler;
        if (spi_type == HAL_SPI_TYPE_MASTER) {
            spi->nhs_spi.spim = NRF_SPI0;
        } else {
            assert(0);
        }
#else
        goto err;
#endif
    } else if (spi_num == 1) {
#if MYNEWT_VAL(SPI_1_MASTER)  || MYNEWT_VAL(SPI_1_SLAVE)
        spi->irq_num = SPI1_TWI1_IRQn;
        irq_handler = nrf51_spi1_irq_handler;
        if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_1_MASTER)
            spi->nhs_spi.spim = NRF_SPI1;
#else
            assert(0);
#endif
        } else {
#if MYNEWT_VAL(SPI_1_SLAVE)
            spi->nhs_spi.spis = NRF_SPIS1;
#else
            assert(0);
#endif
        }
#else
        goto err;
#endif
    } else {
        goto err;
    }

    if (spi_type == HAL_SPI_TYPE_MASTER) {
        rc = hal_spi_init_master(spi, (struct nrf51_hal_spi_cfg *)cfg,
                                 irq_handler);


    } else {
        rc = hal_spi_init_slave(spi, (struct nrf51_hal_spi_cfg *)cfg,
                                irq_handler);
    }

err:
    return (rc);
}

/**
 * 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)
{
    int rc;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

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

err:
    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)
{
    int rc;
    NRF_SPIS_Type *spis;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->spi_type  == HAL_SPI_TYPE_MASTER) {
        spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
        spim->ENABLE = (SPI_ENABLE_ENABLE_Enabled << SPI_ENABLE_ENABLE_Pos);
    } else {
        if (spi->txrx_cb_func == NULL) {
            rc = EINVAL;
            goto err;
        }

        spis = spi->nhs_spi.spis;
        spis->EVENTS_END = 0;
        spis->EVENTS_ACQUIRED = 0;
        spis->INTENSET = SPIS_INTENSET_ACQUIRED_Msk | SPIS_INTENSET_END_Msk;
        spis->ENABLE = (SPIS_ENABLE_ENABLE_Enabled << SPIS_ENABLE_ENABLE_Pos);
    }
    rc = 0;

err:
    return rc;
}

/**
 * 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)
{
    int rc;
    NRF_SPIS_Type *spis;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->spi_type  == HAL_SPI_TYPE_MASTER) {
        spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
        spim->INTENCLR = SPI_INTENCLR_READY_Msk;
        spi->spi_xfr_flag = 0;
        spim->ENABLE = 0;
    } else {
        spis = spi->nhs_spi.spis;
        spis->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
        spis->EVENTS_END = 0;
        spis->EVENTS_ACQUIRED = 0;
        spis->ENABLE = 0;
        spi->slave_state = HAL_SPI_SLAVE_STATE_IDLE;
        spi->nhs_txbuf = NULL;
        spi->nhs_rxbuf = NULL;
    }
    rc = 0;

err:
    return rc;
}

/**
 * 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)
{
    int rc;
    uint16_t retval;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->spi_type  == HAL_SPI_TYPE_MASTER) {
        spim = (NRF_SPI_Type *) spi->nhs_spi.spim;
        spim->EVENTS_READY = 0;
        spim->TXD = (uint8_t)val;
        while (!spim->EVENTS_READY) {}
        spim->EVENTS_READY = 0;
        retval = (uint16_t)spim->RXD;
    } else {
        retval = 0xFFFF;
    }

    return retval;

err:
    return rc;
}

/**
 * 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)
{
    int rc;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    /*
     * This looks odd, but the ENABLE register is in the same location for
     * SPIM, SPI and SPIS
     */
    spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
    if (spim->ENABLE != 0) {
        rc = -1;
    } else {
        spi->txrx_cb_func = txrx_cb;
        spi->txrx_cb_arg = arg;
        rc = 0;
    }

err:
    return rc;
}

/**
 * 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 len)
{
    int i;
    int rc;
    int txcnt;
    uint8_t *txd, *rxd;
    uint8_t rxval;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    rc = EINVAL;
    if (!len) {
        goto err;
    }

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->spi_type  == HAL_SPI_TYPE_MASTER) {
        /* Must have a txbuf for master! */
        if (txbuf == NULL) {
            goto err;
        }

        spim = (NRF_SPI_Type *) spi->nhs_spi.spim;
        spim->INTENCLR = SPI_INTENCLR_READY_Msk;

        /* Blocking spi transfer */
        txd = (uint8_t *)txbuf;
        hal_spi_master_send_first(spim, txd[0]);
        txcnt = len - 1;
        rxd = (uint8_t *)rxbuf;
        for (i = 0; i < len; ++i) {
            if (txcnt) {
                ++txd;
                spim->TXD = *txd;
                --txcnt;
            }
            while (!spim->EVENTS_READY) {}
            spim->EVENTS_READY = 0;
            rxval = spim->RXD;
            if (rxbuf) {
                *rxd = rxval;
                ++rxd;
            }
        }
        return 0;
    }

err:
    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 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_noblock(int spi_num, void *txbuf, void *rxbuf, int len)
{
    int rc;
    uint8_t *txd;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    rc = EINVAL;
    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    if ((spi->txrx_cb_func == NULL) || (len == 0)) {
        goto err;
    }

    if (spi->spi_type  == HAL_SPI_TYPE_MASTER) {
        /* Must have a txbuf for master! */
        if (txbuf == NULL) {
            goto err;
        }

        /* Not allowed if transfer in progress */
        if (spi->spi_xfr_flag) {
            rc = -1;
            goto err;
        }

        spim = (NRF_SPI_Type *) spi->nhs_spi.spim;
        spim->INTENCLR = SPI_INTENCLR_READY_Msk;

        spi->spi_xfr_flag = 1;
        spi->nhs_buflen = (uint16_t)len;
        spi->nhs_txbuf = txbuf;
        spi->nhs_rxbuf = rxbuf;
        spi->nhs_rxd_bytes = 0;
        txd = (uint8_t *)txbuf;
        hal_spi_master_send_first(spim, txd[0]);
        spi->nhs_txd_bytes = 1;
        if (len > 1) {
            spim->TXD = txd[1];
            ++spi->nhs_txd_bytes;
        }
        spim->INTENSET = SPI_INTENSET_READY_Msk;
    } else {
        /* Must have txbuf or rxbuf */
        if ((txbuf == NULL) && (rxbuf == NULL)) {
            goto err;
        }

        /* XXX: not sure what to for length > 255 */
        if (len > 255) {
            goto err;
        }

        /*
         * Ready the slave for a transfer. Do not allow this to be called
         * if the slave has already been readied or is requesting the
         * semaphore
         */
        if (spi->slave_state != HAL_SPI_SLAVE_STATE_IDLE) {
            rc = -1;
            goto err;
        }

        spi->nhs_rxbuf = rxbuf;
        spi->nhs_txbuf = txbuf;
        spi->nhs_buflen = len;
        spi->slave_state = HAL_SPI_SLAVE_STATE_ACQ_SEM;
        spi->nhs_spi.spis->TASKS_ACQUIRE = 1;
    }
    return 0;

err:
    return rc;
}

/**
 * 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)
{
    int rc;
    NRF_SPIS_Type *spis;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);
    if (spi->spi_type  == HAL_SPI_TYPE_SLAVE) {
        spis = spi->nhs_spi.spis;
        spis->DEF = (uint8_t)val;
        spis->ORC = (uint8_t)val;
        rc = 0;
    } else {
        rc = EINVAL;
    }

err:
    return rc;
}

/**
 * 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)
{
    int rc;
    NRF_SPI_Type *spim;
    struct nrf51_hal_spi *spi;

    NRF51_HAL_SPI_RESOLVE(spi_num, spi);

    rc = 0;
    if (spi->spi_type  == HAL_SPI_TYPE_MASTER) {
        spim = (NRF_SPI_Type *)spi->nhs_spi.spim;
        if (spi->spi_xfr_flag) {
            spim->INTENCLR = SPI_INTENCLR_READY_Msk;
            spim->ENABLE = 0;
            spim->EVENTS_READY = 0;
            spi->spi_xfr_flag = 0;
            spim->INTENSET = SPI_INTENSET_READY_Msk;
        }
    } else {
        /* Only way I can see doing this is to disable, then re-enable */
        rc = hal_spi_disable(spi_num);
        if (rc) {
            goto err;
        }
        rc = hal_spi_enable(spi_num);
        if (rc) {
            goto err;
        }
    }

err:
    return rc;
}
