blob: d4dc3a1215d32168a39ffebdeae9657ffc1486ab [file] [log] [blame]
/*
* 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/nrf52_hal.h"
#include "nrf.h"
#ifndef min
#define min(a, b) ((a)<(b)?(a):(b))
#endif
#ifdef NRF52840_XXAA
#define SPIM_TXD_MAXCNT_MAX 65535
#else
#define SPIM_TXD_MAXCNT_MAX 255
#endif
/* IRQ handler type */
typedef void (*nrf52_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 NRF52_HAL_SPI_MAX (3)
/* 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 nrf52_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 */
struct hal_spi_settings spi_cfg; /* Slave and master */
/* Pointer to HW registers */
union {
NRF_SPIM_Type *spim;
NRF_SPIS_Type *spis;
} nhs_spi;
/* IRQ number */
IRQn_Type irq_num;
uint8_t *nhs_txbuf; /* Pointer to TX buffer */
uint8_t *nhs_rxbuf; /* Pointer to RX buffer */
uint16_t nhs_buflen; /* Length of buffer */
uint16_t nhs_bytes_txq; /* Number of bytes queued for TX */
/* 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 nrf52_hal_spi nrf52_hal_spi0;
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
struct nrf52_hal_spi nrf52_hal_spi1;
#endif
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
struct nrf52_hal_spi nrf52_hal_spi2;
#endif
static const struct nrf52_hal_spi *nrf52_hal_spis[NRF52_HAL_SPI_MAX] = {
#if MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE)
&nrf52_hal_spi0,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
&nrf52_hal_spi1,
#else
NULL,
#endif
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
&nrf52_hal_spi2,
#else
NULL,
#endif
};
#define NRF52_HAL_SPI_RESOLVE(__n, __v) \
if ((__n) >= NRF52_HAL_SPI_MAX) { \
rc = EINVAL; \
goto err; \
} \
(__v) = (struct nrf52_hal_spi *) nrf52_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))
static void
nrf52_irqm_handler(struct nrf52_hal_spi *spi)
{
NRF_SPIM_Type *spim;
uint16_t xfr_bytes;
uint16_t next_len;
spim = spi->nhs_spi.spim;
/* Should not occur but if no transfer just leave */
if (spi->spi_xfr_flag == 0) {
return;
}
if ((spim->EVENTS_STARTED) && (spim->INTENSET & SPIM_INTENSET_STARTED_Msk)) {
spim->EVENTS_STARTED = 0;
xfr_bytes = spim->TXD.MAXCNT;
spi->nhs_bytes_txq += xfr_bytes;
if (spi->nhs_bytes_txq < spi->nhs_buflen) {
spi->nhs_txbuf += xfr_bytes;
next_len = min(SPIM_TXD_MAXCNT_MAX,
spi->nhs_buflen - spi->nhs_bytes_txq);
spim->TXD.PTR = (uint32_t)spi->nhs_txbuf;
spim->TXD.MAXCNT = next_len;
/* If no RX buffer was provided, we already set it to dummy one */
if (spi->nhs_rxbuf) {
spi->nhs_rxbuf += xfr_bytes;
spim->RXD.PTR = (uint32_t)spi->nhs_rxbuf;
spim->RXD.MAXCNT = next_len;
}
spim->SHORTS |= SPIM_SHORTS_END_START_Msk;
spim->INTENSET = SPIM_INTENSET_STARTED_Msk;
spim->INTENCLR = SPIM_INTENSET_END_Msk;
} else {
spim->SHORTS &= ~SPIM_SHORTS_END_START_Msk;
spim->INTENSET = SPIM_INTENSET_END_Msk;
spim->INTENCLR = SPIM_INTENSET_STARTED_Msk;
}
}
if (spim->EVENTS_END) {
spim->EVENTS_END = 0;
if (spim->INTENSET & SPIM_INTENSET_END_Msk) {
if (spi->txrx_cb_func) {
spi->txrx_cb_func(spi->txrx_cb_arg, spi->nhs_buflen);
}
spi->spi_xfr_flag = 0;
spim->SHORTS &= ~SPIM_SHORTS_END_START_Msk;
spim->INTENCLR = SPIM_INTENSET_STARTED_Msk | SPIM_INTENSET_END_Msk;
}
}
}
#endif
#if (MYNEWT_VAL(SPI_0_SLAVE) || MYNEWT_VAL(SPI_1_SLAVE) || MYNEWT_VAL(SPI_2_SLAVE))
static void
nrf52_irqs_handler(struct nrf52_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->TXD.PTR = 0;
spis->TXD.MAXCNT = 0;
} else {
spis->TXD.PTR = (uint32_t)spi->nhs_txbuf;
spis->TXD.MAXCNT = spi->nhs_buflen;
}
if (spi->nhs_rxbuf == NULL) {
spis->RXD.PTR = 0;
spis->RXD.MAXCNT = 0;
} else {
spis->RXD.PTR = (uint32_t)spi->nhs_rxbuf;
spis->RXD.MAXCNT = 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 = 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
nrf52_spi0_irq_handler(void)
{
os_trace_isr_enter();
if (nrf52_hal_spi0.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_0_MASTER)
nrf52_irqm_handler(&nrf52_hal_spi0);
#endif
} else {
#if MYNEWT_VAL(SPI_0_SLAVE)
nrf52_irqs_handler(&nrf52_hal_spi0);
#endif
}
os_trace_isr_exit();
}
#endif
#if MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE)
void
nrf52_spi1_irq_handler(void)
{
os_trace_isr_enter();
if (nrf52_hal_spi1.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_1_MASTER)
nrf52_irqm_handler(&nrf52_hal_spi1);
#endif
} else {
#if MYNEWT_VAL(SPI_1_SLAVE)
nrf52_irqs_handler(&nrf52_hal_spi1);
#endif
}
os_trace_isr_exit();
}
#endif
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
void
nrf52_spi2_irq_handler(void)
{
os_trace_isr_enter();
if (nrf52_hal_spi2.spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_2_MASTER)
nrf52_irqm_handler(&nrf52_hal_spi2);
#endif
} else {
#if MYNEWT_VAL(SPI_2_SLAVE)
nrf52_irqs_handler(&nrf52_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 nrf52_hal_spi *spi,
struct hal_spi_settings *settings)
{
int rc;
uint32_t nrf_config;
uint32_t frequency;
NRF_SPIM_Type *spim;
spim = spi->nhs_spi.spim;
memcpy(&spi->spi_cfg, settings, sizeof(*settings));
/* 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 nrf52_hal_spi *spi,
struct hal_spi_settings *settings)
{
int rc;
uint32_t nrf_config;
NRF_SPIS_Type *spis;
spis = spi->nhs_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 nrf52_hal_spi *spi,
struct nrf52_hal_spi_cfg *cfg,
nrf52_spi_irq_handler_t handler)
{
NRF_SPIM_Type *spim;
NRF_GPIO_Type *port;
uint32_t pin;
/* Configure SCK */
port = HAL_GPIO_PORT(cfg->sck_pin);
pin = HAL_GPIO_INDEX(cfg->sck_pin);
if (spi->spi_cfg.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);
/* 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->nhs_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 nrf52_hal_spi *spi,
struct nrf52_hal_spi_cfg *cfg,
nrf52_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->nhs_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 nrf52_hal_spi *spi;
nrf52_spi_irq_handler_t irq_handler;
NRF52_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 = nrf52_spi0_irq_handler;
if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_0_MASTER)
spi->nhs_spi.spim = NRF_SPIM0;
#else
assert(0);
#endif
} else {
#if MYNEWT_VAL(SPI_0_SLAVE)
spi->nhs_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 = nrf52_spi1_irq_handler;
if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_1_MASTER)
spi->nhs_spi.spim = NRF_SPIM1;
#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 if (spi_num == 2) {
#if MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE)
spi->irq_num = SPIM2_SPIS2_SPI2_IRQn;
irq_handler = nrf52_spi2_irq_handler;
if (spi_type == HAL_SPI_TYPE_MASTER) {
#if MYNEWT_VAL(SPI_2_MASTER)
spi->nhs_spi.spim = NRF_SPIM2;
#else
assert(0);
#endif
} else {
#if MYNEWT_VAL(SPI_2_SLAVE)
spi->nhs_spi.spis = NRF_SPIS2;
#else
assert(0);
#endif
}
#else
goto err;
#endif
} else {
goto err;
}
hal_spi_disable(spi_num);
if (spi_type == HAL_SPI_TYPE_MASTER) {
rc = hal_spi_init_master(spi, (struct nrf52_hal_spi_cfg *)cfg,
irq_handler);
} else {
rc = hal_spi_init_slave(spi, (struct nrf52_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 nrf52_hal_spi *spi;
NRF_SPIM_Type *spim;
NRF52_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->nhs_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_SPI_Type *nrf_spi;
struct nrf52_hal_spi *spi;
NRF52_HAL_SPI_RESOLVE(spi_num, spi);
if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
/* For now, enable this in normal SPI mode (not spim) */
nrf_spi = (NRF_SPI_Type *)spi->nhs_spi.spim;
nrf_spi->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_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 nrf52_hal_spi *spi;
NRF52_HAL_SPI_RESOLVE(spi_num, spi);
if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
spim = spi->nhs_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->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;
spi->nhs_buflen = 0;
spi->nhs_bytes_txq = 0;
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 *spi;
struct nrf52_hal_spi *hal_spi;
NRF52_HAL_SPI_RESOLVE(spi_num, hal_spi);
if (hal_spi->spi_type == HAL_SPI_TYPE_MASTER) {
spi = (NRF_SPI_Type *)hal_spi->nhs_spi.spim;
spi->EVENTS_READY = 0;
spi->TXD = (uint8_t)val;
while (!spi->EVENTS_READY) {}
spi->EVENTS_READY = 0;
retval = (uint16_t)spi->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_SPIM_Type *spim;
struct nrf52_hal_spi *spi;
NRF52_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->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;
uint32_t enabled;
uint8_t *txd, *rxd;
uint8_t rxval;
NRF_SPI_Type *spi;
NRF_SPIM_Type *spim;
struct nrf52_hal_spi *hal_spi;
rc = EINVAL;
if (!len) {
goto err;
}
NRF52_HAL_SPI_RESOLVE(spi_num, hal_spi);
if (hal_spi->spi_type == HAL_SPI_TYPE_MASTER) {
/* Must have a txbuf for master! */
if (txbuf == NULL) {
goto err;
}
/*
* If SPIM is enabled, we want to stop, disable, then enable
* the legacy SPI interface.
*/
spim = hal_spi->nhs_spi.spim;
enabled = spim->ENABLE;
if (enabled == SPIM_ENABLE_ENABLE_Enabled) {
spim->INTENCLR = NRF_SPI_IRQ_DISABLE_ALL;
hal_spi_stop_transfer(spim);
spim->ENABLE = 0;
enabled = 0;
}
spi = (NRF_SPI_Type *)spim;
if (enabled == 0) {
spi->ENABLE = (SPI_ENABLE_ENABLE_Enabled << SPI_ENABLE_ENABLE_Pos);
}
while (spi->EVENTS_READY) {
rxval = (uint8_t)spi->RXD;
spi->EVENTS_READY = 0;
}
txd = (uint8_t *)txbuf;
spi->TXD = *txd;
txcnt = len - 1;
rxd = (uint8_t *)rxbuf;
for (i = 0; i < len; ++i) {
if (txcnt) {
++txd;
spi->TXD = *txd;
--txcnt;
}
while (!spi->EVENTS_READY) {}
spi->EVENTS_READY = 0;
rxval = (uint8_t)spi->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;
NRF_SPIM_Type *spim;
struct nrf52_hal_spi *spi;
rc = EINVAL;
NRF52_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->nhs_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->nhs_bytes_txq = 0;
spi->nhs_buflen = len;
spi->nhs_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->nhs_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->EVENTS_STARTED = 0;
if (spi->nhs_buflen < 256) {
spim->INTENCLR = SPIM_INTENSET_STARTED_Msk;
spim->INTENSET = SPIM_INTENSET_END_Msk;
} else {
spim->INTENCLR = SPIM_INTENSET_END_Msk;
spim->INTENSET = SPIM_INTENSET_STARTED_Msk;
}
spim->TASKS_START = 1;
} 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->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 nrf52_hal_spi *spi;
NRF52_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_SPIM_Type *spim;
struct nrf52_hal_spi *spi;
NRF52_HAL_SPI_RESOLVE(spi_num, spi);
rc = 0;
if (spi->spi_type == HAL_SPI_TYPE_MASTER) {
spim = spi->nhs_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 */
hal_spi_disable(spi_num);
hal_spi_enable(spi_num);
}
err:
return rc;
}