/*
 * 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/cmsis_nvic.h>
#include <hal/hal_spi.h>
#include "mcu/nrf91_hal.h"
#include "nrf.h"
#include "nrfx_config.h"

#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif

#define SPIM_TXD_MAXCNT_MAX             65535

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

/* 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.
 */

/* The maximum number of SPI interfaces we will allow */
#define NRF91_HAL_SPI_MAX (4)

/* 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 nrf91_hal_spi {
    uint8_t spi_type;
    uint8_t spi_xfr_flag;   /* Master only */
    uint8_t dummy_rx;       /* Master only */
    uint8_t slave_state;    /* Slave only */
    uint16_t buflen;
    uint16_t bytes_txd;
    struct hal_spi_settings spi_cfg; /* Slave and master */

    /* Pointer to HW registers */
    union {
        NRF_SPIM_Type *spim;
        NRF_SPIS_Type *spis;
    } spi;

    /* IRQ number */
    IRQn_Type irq_num;

    /* Pointers to tx/rx buffers */
    uint8_t *txbuf;
    uint8_t *rxbuf;

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

#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)
struct nrf91_hal_spi nrf91_hal_spi0;
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
struct nrf91_hal_spi nrf91_hal_spi1;
#endif
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
struct nrf91_hal_spi nrf91_hal_spi2;
#endif
#if MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_3_SLAVE)
struct nrf91_hal_spi nrf91_hal_spi3;
#endif

static const struct nrf91_hal_spi *nrf91_hal_spis[NRF91_HAL_SPI_MAX] = {
#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)
    &nrf91_hal_spi0,
#else
    NULL,
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
    &nrf91_hal_spi1,
#else
    NULL,
#endif
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
    &nrf91_hal_spi2,
#else
    NULL,
#endif
#if MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_3_SLAVE) 
    &nrf91_hal_spi3,
#else
    NULL,
#endif
};

#define NRF91_HAL_SPI_RESOLVE(__n, __v)                     \
    if ((__n) >= NRF91_HAL_SPI_MAX) {                       \
        rc = EINVAL;                                        \
        goto err;                                           \
    }                                                       \
    (__v) = (struct nrf91_hal_spi *) nrf91_hal_spis[(__n)]; \
    if ((__v) == NULL) {                                    \
        rc = EINVAL;                                        \
        goto err;                                           \
    }

#if (MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_3_MASTER))
static void
nrf91_irqm_handler(struct nrf91_hal_spi *spi)
{
    NRF_SPIM_Type *spim;
    uint16_t xfr_bytes;
    uint16_t len;

    spim = spi->spi.spim;
    if (spim->EVENTS_END) {
        spim->EVENTS_END = 0;

        /* Should not occur but if no transfer just leave  */
        if (spi->spi_xfr_flag == 0) {
            return;
        }

        /* Are there more bytes to send? */
        xfr_bytes = spim->TXD.AMOUNT;
        spi->bytes_txd += xfr_bytes;
        if (spi->bytes_txd < spi->buflen) {
            spi->txbuf += xfr_bytes;
            len = spi->buflen - spi->bytes_txd;
            len = min(SPIM_TXD_MAXCNT_MAX, len);
            spim->TXD.PTR = (uint32_t)spi->txbuf;
            spim->TXD.MAXCNT = len;

            /* If no rxbuf, we need to set rxbuf and maxcnt to 1 */
            if (spi->rxbuf) {
                spi->rxbuf += xfr_bytes;
                spim->RXD.PTR = (uint32_t)spi->rxbuf;
                spim->RXD.MAXCNT = len;
            }
            spim->TASKS_START = 1;
        } else {
            spi->spi_xfr_flag = 0;
            spim->INTENCLR = SPIM_INTENSET_END_Msk;
            if (spi->txrx_cb_func) {
                spi->txrx_cb_func(spi->txrx_cb_arg, spi->buflen);
            }
        }
    }
}
#endif

#if (MYNEWT_VAL(SPI_0_SLAVE) || MYNEWT_VAL(SPI_1_SLAVE) || \
     MYNEWT_VAL(SPI_2_SLAVE) || MYNEWT_VAL(SPI_3_SLAVE))
static void
nrf91_irqs_handler(struct nrf91_hal_spi *spi)
{
    uint8_t xfr_len;
    NRF_SPIS_Type *spis;

    spis = spi->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->txbuf == NULL) {
                spis->TXD.PTR = 0;
                spis->TXD.MAXCNT = 0;
            } else {
                spis->TXD.PTR = (uint32_t)spi->txbuf;
                spis->TXD.MAXCNT = spi->buflen;
            }

            if (spi->rxbuf == NULL) {
                spis->RXD.PTR = 0;
                spis->RXD.MAXCNT = 0;
            } else {
                spis->RXD.PTR = (uint32_t)spi->rxbuf;
                spis->RXD.MAXCNT = spi->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->txbuf == NULL) {
                    xfr_len = spis->RXD.AMOUNT;
                } else {
                    xfr_len = spis->TXD.AMOUNT;
                }
                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) || MYNEWT_VAL(SPI_0_SLAVE)
void
nrf91_spi0_irq_handler(void)
{
    os_trace_isr_enter();
    if (nrf91_hal_spi0.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_0_MASTER)
        nrf91_irqm_handler(&nrf91_hal_spi0);
#endif
    } else {
#if MYNEWT_VAL(SPI_0_SLAVE)
        nrf91_irqs_handler(&nrf91_hal_spi0);
#endif
    }
    os_trace_isr_exit();
}
#endif

#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
void
nrf91_spi1_irq_handler(void)
{
    os_trace_isr_enter();
    if (nrf91_hal_spi1.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_1_MASTER)
        nrf91_irqm_handler(&nrf91_hal_spi1);
#endif
    } else {
#if MYNEWT_VAL(SPI_1_SLAVE)
        nrf91_irqs_handler(&nrf91_hal_spi1);
#endif
    }
    os_trace_isr_exit();
}
#endif

#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
void
nrf91_spi2_irq_handler(void)
{
    os_trace_isr_enter();
    if (nrf91_hal_spi2.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_2_MASTER)
        nrf91_irqm_handler(&nrf91_hal_spi2);
#endif
    } else {
#if MYNEWT_VAL(SPI_2_SLAVE)
        nrf91_irqs_handler(&nrf91_hal_spi2);
#endif
    }
    os_trace_isr_exit();
}
#endif

#if MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_3_SLAVE)
void
nrf91_spi3_irq_handler(void)
{
    os_trace_isr_enter();
    if (nrf91_hal_spi3.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_3_MASTER)
        nrf91_irqm_handler(&nrf91_hal_spi3);
#endif
    } else {
#if MYNEWT_VAL(SPI_3_SLAVE)
        nrf91_irqs_handler(&nrf91_hal_spi2);
#endif
    }
    os_trace_isr_exit();
}
#endif

static void
hal_spi_stop_transfer(NRF_SPIM_Type *spim)
{
    spim->TASKS_STOP = 1;
    while (!spim->EVENTS_STOPPED) {}
    spim->EVENTS_STOPPED = 0;
}

static int
hal_spi_config_master(struct nrf91_hal_spi *spi,
                      struct hal_spi_settings *settings)
{
    int rc;
    uint32_t nrf_config;
    uint32_t frequency;
    NRF_SPIM_Type *spim;
    NRF_GPIO_Type *port;
    uint32_t pin;

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

    /*
     * Configure SCK. NOTE: this is done here in the config API as the data
     * mode is not set at init time so we do it here when we configure the SPI.
     */
    pin = spim->PSEL.SCK & SPIM_PSEL_SCK_PIN_Msk;
    port = NRF_P0;
    if (settings->data_mode <= HAL_SPI_MODE1) {
        port->OUTCLR = (1UL << pin);
    } else {
        port->OUTSET = (1UL << pin);
    }
    port->PIN_CNF[pin] =
        (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
        (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);

    /* Only 8-bit word sizes supported. */
    rc = 0;
    switch (settings->word_size) {
        case HAL_SPI_WORD_SIZE_8BIT:
            break;
        default:
            rc = EINVAL;
            break;
    }

    switch (settings->data_mode) {
        case HAL_SPI_MODE0:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveHigh << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Leading << SPIM_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE1:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveHigh << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Trailing << SPIM_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE2:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveLow << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Leading << SPIM_CONFIG_CPHA_Pos);
            break;
        case HAL_SPI_MODE3:
            nrf_config = (SPIM_CONFIG_CPOL_ActiveLow << SPIM_CONFIG_CPOL_Pos) |
                         (SPIM_CONFIG_CPHA_Trailing << SPIM_CONFIG_CPHA_Pos);
            break;
        default:
            nrf_config = 0;
            rc = EINVAL;
            break;
    }

    /* NOTE: msb first is 0 so no check done */
    if (settings->data_order == HAL_SPI_LSB_FIRST) {
        nrf_config |= SPIM_CONFIG_ORDER_LsbFirst;
    }
    spim->CONFIG = nrf_config;

    switch (settings->baudrate) {
        case 125:
            frequency = SPIM_FREQUENCY_FREQUENCY_K125;
            break;
        case 250:
            frequency = SPIM_FREQUENCY_FREQUENCY_K250;
            break;
        case 500:
            frequency = SPIM_FREQUENCY_FREQUENCY_K500;
            break;
        case 1000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M1;
            break;
        case 2000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M2;
            break;
        case 4000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M4;
            break;
        case 8000:
            frequency = SPIM_FREQUENCY_FREQUENCY_M8;
            break;
        default:
            frequency = 0;
            rc = EINVAL;
            break;
    }
    spim->FREQUENCY = frequency;

    return rc;
}

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

    spis = spi->spi.spis;

    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. */
    switch (settings->word_size) {
        case HAL_SPI_WORD_SIZE_8BIT:
            break;
        default:
            rc = EINVAL;
            break;
    }

    return rc;
}

static int
hal_spi_init_master(struct nrf91_hal_spi *spi,
                    struct nrf91_hal_spi_cfg *cfg,
                    nrf91_spi_irq_handler_t handler)
{
    NRF_SPIM_Type *spim;
    NRF_GPIO_Type *port;
    uint32_t pin;

    /*  Configure MOSI */
    port = HAL_GPIO_PORT(cfg->mosi_pin);
    pin = HAL_GPIO_INDEX(cfg->mosi_pin);
    port->OUTCLR = (1UL << pin);
    port->PIN_CNF[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 */
    port = HAL_GPIO_PORT(cfg->miso_pin);
    pin = HAL_GPIO_INDEX(cfg->miso_pin);
    port->PIN_CNF[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_SPIM_Type *)spi->spi.spim;
    spim->PSEL.SCK = cfg->sck_pin;
    spim->PSEL.MOSI = cfg->mosi_pin;
    spim->PSEL.MISO = cfg->miso_pin;

    spim->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
    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 nrf91_hal_spi *spi,
                   struct nrf91_hal_spi_cfg *cfg,
                   nrf91_spi_irq_handler_t handler)
{
    NRF_SPIS_Type *spis;
    NRF_GPIO_Type *port;
    uint32_t pin;

    /* NOTE: making this pin an input is correct! See datasheet */
    port = HAL_GPIO_PORT(cfg->miso_pin);
    pin = HAL_GPIO_INDEX(cfg->miso_pin);
    port->PIN_CNF[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);

    port = HAL_GPIO_PORT(cfg->mosi_pin);
    pin = HAL_GPIO_INDEX(cfg->mosi_pin);
    port->PIN_CNF[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);

    port = HAL_GPIO_PORT(cfg->ss_pin);
    pin = HAL_GPIO_INDEX(cfg->ss_pin);
    port->PIN_CNF[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);

    port = HAL_GPIO_PORT(cfg->sck_pin);
    pin = HAL_GPIO_INDEX(cfg->sck_pin);
    port->PIN_CNF[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->spi.spis;
    spis->PSEL.SCK = cfg->sck_pin;
    spis->PSEL.MOSI = cfg->mosi_pin;
    spis->PSEL.MISO = cfg->miso_pin;
    spis->PSEL.CSN = 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;
}

/**
 * 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 nrf91_hal_spi *spi;
    nrf91_spi_irq_handler_t irq_handler;

    NRF91_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) || MYNEWT_VAL(SPI_0_SLAVE)
        spi->irq_num = SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQn;
        irq_handler = nrf91_spi0_irq_handler;
        if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_0_MASTER)
            spi->spi.spim = NRF_SPIM0;
#else
            assert(0);
#endif
        } else {
#if MYNEWT_VAL(SPI_0_SLAVE)
            spi->spi.spis = NRF_SPIS0;
#else
            assert(0);
#endif
        }
#else
        goto err;
#endif
    } else if (spi_num == 1) {
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
        spi->irq_num = SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQn;
        irq_handler = nrf91_spi1_irq_handler;
        if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_1_MASTER)
            spi->spi.spim = NRF_SPIM1;
#else
            assert(0);
#endif
        } else {
#if MYNEWT_VAL(SPI_1_SLAVE)
            spi->spi.spis = NRF_SPIS1;
#else
            assert(0);
#endif
        }
#else
        goto err;
#endif
    } else if (spi_num == 2) {
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
        spi->irq_num = SPIM2_SPIS2_SPI2_IRQn;
        irq_handler = nrf91_spi2_irq_handler;
        if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_2_MASTER)
            spi->spi.spim = NRF_SPIM2;
#else
            assert(0);
#endif
        } else {
#if MYNEWT_VAL(SPI_2_SLAVE)
            spi->spi.spis = NRF_SPIS2;
#else
            assert(0);
#endif
        }
#else
        goto err;
#endif
    } else if (spi_num == 3) {
#if MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
        spi->irq_num = SPIM3_IRQn;
        irq_handler = nrf91_spi3_irq_handler;
        if (spi_type == HAL_SPI_TYPE_MASTER) {
            spi->spi.spim = NRF_SPIM3;
        } else {
            assert(0);
        }
#else
        goto err;
#endif
    } else {
        goto err;
    }

    rc = hal_spi_disable(spi_num);
    if (rc) {
        goto err;
    }

    if (spi_type == HAL_SPI_TYPE_MASTER) {
        rc = hal_spi_init_master(spi, (struct nrf91_hal_spi_cfg *)cfg,
                                 irq_handler);
    } else {
        rc = hal_spi_init_slave(spi, (struct nrf91_hal_spi_cfg *)cfg,
                                irq_handler);
    }

err:
    return (rc);
}

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

    hal_cfg.sck_pin = cfg->pin_sck;
    hal_cfg.mosi_pin = cfg->pin_mosi;
    hal_cfg.miso_pin = cfg->pin_miso;
    hal_cfg.ss_pin = cfg->pin_ss;

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

/**
 * 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 nrf91_hal_spi *spi;
    NRF_SPIM_Type *spim;

    NRF91_HAL_SPI_RESOLVE(spi_num, spi);

    /*
     * This looks odd, but the ENABLE register is in the same location for
     * SPIM, SPI and SPIS
     */
    spim = spi->spi.spim;
    if (spim->ENABLE != 0) {
        return -1;
    }

    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_SPIM_Type *nrf_spi;
    struct nrf91_hal_spi *spi;

    NRF91_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
        nrf_spi = (NRF_SPIM_Type *)spi->spi.spim;
        nrf_spi->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);
    } else {
        if (spi->txrx_cb_func == NULL) {
            rc = EINVAL;
            goto err;
        }

        spis = spi->spi.spis;
        spis->EVENTS_END = 0;
        spis->EVENTS_ACQUIRED = 0;
        spis->INTENSET = SPIS_INTENSET_END_Msk | SPIS_INTENSET_ACQUIRED_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_SPIM_Type *spim;
    struct nrf91_hal_spi *spi;

    NRF91_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
        spim = spi->spi.spim;
        spim->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;

        if (spi->spi_xfr_flag) {
            hal_spi_stop_transfer(spim);
            spi->spi_xfr_flag = 0;
        }
        spim->ENABLE = 0;
    } else {
        spis = spi->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->txbuf = NULL;
    spi->rxbuf = NULL;
    spi->buflen = 0;
    spi->bytes_txd = 0;

    rc = 0;

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_SPIM_Type *spim;
    struct nrf91_hal_spi *spi;

    NRF91_HAL_SPI_RESOLVE(spi_num, spi);

    /*
     * This looks odd, but the ENABLE register is in the same location for
     * SPIM, SPI and SPIS
     */
    spim = spi->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;
}

/**
 * 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;
    NRF_SPIM_Type *spim;
    struct nrf91_hal_spi *spi;

    rc = EINVAL;
    NRF91_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 = spi->spi.spim;
        spim->INTENCLR = SPIM_INTENCLR_END_Msk;
        spi->spi_xfr_flag = 1;

        /* Must be enabled for SPIM as opposed to SPI */
        if (spim->ENABLE != SPIM_ENABLE_ENABLE_Enabled) {
            spim->ENABLE = 0;
            spim->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);
        }

        /* Set internal data structure information */
        spi->bytes_txd = 0;
        spi->buflen = len;
        spi->txbuf = txbuf;

        len = min(SPIM_TXD_MAXCNT_MAX, len);

        /* Set chip registers */
        spim->TXD.PTR = (uint32_t)txbuf;
        spim->TXD.MAXCNT = len;

        /* If no rxbuf, we need to set rxbuf and maxcnt to 1 */
        spi->rxbuf = rxbuf;
        if (rxbuf == NULL) {
            spim->RXD.PTR = (uint32_t)&spi->dummy_rx;
            spim->RXD.MAXCNT = 1;
        } else {
            spim->RXD.PTR = (uint32_t)rxbuf;
            spim->RXD.MAXCNT = len;
        }

        spim->EVENTS_END = 0;
        spim->EVENTS_STOPPED = 0;
        spim->TASKS_START = 1;
        spim->INTENSET = SPIM_INTENSET_END_Msk;
    } else {
        /* Must have txbuf or rxbuf */
        if ((txbuf == NULL) && (rxbuf == NULL)) {
            goto err;
        }

        /* XXX: what to do here? */
        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->rxbuf = rxbuf;
        spi->txbuf = txbuf;
        spi->buflen = len;
        spi->slave_state = HAL_SPI_SLAVE_STATE_ACQ_SEM;
        spi->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 nrf91_hal_spi *spi;

    NRF91_HAL_SPI_RESOLVE(spi_num, spi);
    if (spi->spi_type  == HAL_SPI_TYPE_SLAVE) {
        spis = spi->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_SPIM_Type *spim;
    struct nrf91_hal_spi *spi;

    NRF91_HAL_SPI_RESOLVE(spi_num, spi);

    rc = 0;
    if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
        spim = spi->spi.spim;
        if (spi->spi_xfr_flag) {
            spim->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
            hal_spi_stop_transfer(spim);
            spi->spi_xfr_flag = 0;
            spim->INTENSET = SPIM_INTENSET_END_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;
}
