457 lines
13 KiB
C
457 lines
13 KiB
C
/* spis_nrf5.c - SPI slave driver for Nordic nRF5x SoCs */
|
|
/*
|
|
* Copyright (c) 2016, 2017 Linaro Limited.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_SPI_LEVEL
|
|
|
|
#include <device.h>
|
|
#include <errno.h>
|
|
|
|
#include <logging/sys_log.h>
|
|
#include <sys_io.h>
|
|
#include <board.h>
|
|
#include <init.h>
|
|
#include <gpio.h>
|
|
#include <spi.h>
|
|
#include <nrf.h>
|
|
#include <toolchain.h>
|
|
|
|
typedef void (*spis_nrf5_config_t)(void);
|
|
|
|
struct spis_nrf5_config {
|
|
NRF_SPIS_Type *regs; /* Registers */
|
|
spis_nrf5_config_t config_func; /* IRQ config func pointer */
|
|
u8_t sck_pin; /* SCK GPIO pin number */
|
|
u8_t mosi_pin; /* MOSI GPIO pin number */
|
|
u8_t miso_pin; /* MISO GPIO pin number */
|
|
u8_t csn_pin; /* CSN GPIO pin number */
|
|
u8_t def; /* Default character */
|
|
};
|
|
|
|
struct spis_nrf5_data {
|
|
u8_t error;
|
|
struct k_sem device_sync_sem; /* synchronisation semaphore */
|
|
};
|
|
|
|
#define DEV_CFG(dev) \
|
|
((const struct spis_nrf5_config * const)(dev)->config->config_info)
|
|
#define DEV_DATA(dev) \
|
|
((struct spis_nrf5_data * const)(dev)->driver_data)
|
|
#define SPI_REGS(dev) \
|
|
((DEV_CFG(dev))->regs)
|
|
|
|
/* Register fields */
|
|
|
|
#define NRF5_SPIS_SHORTCUT_END_ACQUIRE \
|
|
(SPIS_SHORTS_END_ACQUIRE_Enabled << SPIS_SHORTS_END_ACQUIRE_Pos)
|
|
|
|
#define NRF5_SPIS_ORDER_MSB \
|
|
(SPIS_CONFIG_ORDER_MsbFirst << SPIS_CONFIG_ORDER_Pos)
|
|
#define NRF5_SPIS_ORDER_LSB \
|
|
(SPIS_CONFIG_ORDER_LsbFirst << SPIS_CONFIG_ORDER_Pos)
|
|
|
|
#define NRF5_SPIS_CPHA_LEADING \
|
|
(SPIS_CONFIG_CPHA_Leading << SPIS_CONFIG_CPHA_Pos)
|
|
#define NRF5_SPIS_CPHA_TRAILING \
|
|
(SPIS_CONFIG_CPHA_Trailing << SPIS_CONFIG_CPHA_Pos)
|
|
|
|
#define NRF5_SPIS_CPOL_HIGH \
|
|
(SPIS_CONFIG_CPOL_ActiveHigh << SPIS_CONFIG_CPOL_Pos)
|
|
#define NRF5_SPIS_CPOL_LOW \
|
|
(SPIS_CONFIG_CPOL_ActiveLow << SPIS_CONFIG_CPOL_Pos)
|
|
|
|
#define NRF5_SPIS_ENABLED \
|
|
(SPIS_ENABLE_ENABLE_Enabled << SPIS_ENABLE_ENABLE_Pos)
|
|
|
|
#define NRF5_SPIS_CSN_DISABLED_CFG 0xff /* CS disabled value from Kconfig */
|
|
#if defined(CONFIG_SOC_SERIES_NRF51X)
|
|
#define NRF5_SPIS_CSN_DISABLED (~0U) /* CS disabled register value */
|
|
#elif defined(CONFIG_SOC_SERIES_NRF52X)
|
|
#define NRF5_SPIS_CSN_DISABLED (1U << 31) /* CS disabled register value */
|
|
#endif
|
|
|
|
static inline bool is_buf_in_ram(const void *buf)
|
|
{
|
|
return ((((uintptr_t) buf) & 0xE0000000) == 0x20000000);
|
|
}
|
|
|
|
static void spis_nrf5_print_cfg_registers(struct device *dev)
|
|
{
|
|
__unused NRF_SPIS_Type *regs = SPI_REGS(dev);
|
|
__unused u32_t sck, miso, mosi, csn;
|
|
__unused u32_t rxd_ptr, rxd_max, rxd_amount;
|
|
__unused u32_t txd_ptr, txd_max, txd_amount;
|
|
|
|
#if defined(CONFIG_SOC_SERIES_NRF51X)
|
|
sck = regs->PSELSCK;
|
|
miso = regs->PSELMISO;
|
|
mosi = regs->PSELMOSI;
|
|
csn = regs->PSELCSN;
|
|
rxd_ptr = regs->RXDPTR;
|
|
rxd_max = regs->MAXRX;
|
|
rxd_amount = regs->AMOUNTRX;
|
|
txd_ptr = regs->TXDPTR;
|
|
txd_max = regs->MAXTX;
|
|
txd_amount = regs->AMOUNTTX;
|
|
#elif defined(CONFIG_SOC_SERIES_NRF52X)
|
|
sck = regs->PSEL.SCK;
|
|
miso = regs->PSEL.MISO;
|
|
mosi = regs->PSEL.MOSI;
|
|
csn = regs->PSEL.CSN;
|
|
rxd_ptr = regs->RXD.PTR;
|
|
rxd_max = regs->RXD.MAXCNT;
|
|
rxd_amount = regs->RXD.AMOUNT;
|
|
txd_ptr = regs->TXD.PTR;
|
|
txd_max = regs->TXD.MAXCNT;
|
|
txd_amount = regs->TXD.AMOUNT;
|
|
#endif /* defined(CONFIG_SOC_SERIES_NRF51X) */
|
|
|
|
SYS_LOG_DBG("\n"
|
|
"SHORTS: %x, IRQ: %x, SEMSTAT: %x\n"
|
|
"CONFIG: %x, STATUS: %x, ENABLE: %x\n"
|
|
"SCKPIN: %x, MISOPIN: %x, MOSIPIN: %x, CSNPIN: %x\n"
|
|
"RXD (PTR: %x, MAX: %x, AMOUNT: %x)\n"
|
|
"TXD (PTR: %x, MAX: %x, AMOUNT: %x)",
|
|
regs->SHORTS, regs->INTENSET, regs->SEMSTAT,
|
|
regs->CONFIG, regs->STATUS, regs->ENABLE,
|
|
sck, miso, mosi, csn,
|
|
rxd_ptr, rxd_max, rxd_amount,
|
|
txd_ptr, txd_max, txd_amount);
|
|
}
|
|
|
|
/**
|
|
* @brief Configure the SPI host controller for operating against slaves
|
|
* @param dev Pointer to the device structure for the driver instance
|
|
* @param config Pointer to the application provided configuration
|
|
*
|
|
* @return 0 if successful, another DEV_* code otherwise.
|
|
*/
|
|
static int spis_nrf5_configure(struct device *dev, struct spi_config *config)
|
|
{
|
|
NRF_SPIS_Type *spi_regs = SPI_REGS(dev);
|
|
u32_t flags;
|
|
|
|
/* make sure module is disabled */
|
|
spi_regs->ENABLE = 0;
|
|
|
|
/* TODO: Muck with IRQ priority if needed */
|
|
|
|
/* Clear any pending events */
|
|
spi_regs->EVENTS_ACQUIRED = 0;
|
|
spi_regs->EVENTS_ENDRX = 0;
|
|
spi_regs->EVENTS_END = 0;
|
|
spi_regs->INTENCLR = 0xFFFFFFFF; /* do we need to clear INT ?*/
|
|
spi_regs->SHORTS = NRF5_SPIS_SHORTCUT_END_ACQUIRE;
|
|
spi_regs->INTENSET |= (SPIS_INTENSET_ACQUIRED_Msk |
|
|
SPIS_INTENSET_END_Msk);
|
|
|
|
/* default transmit and over-read characters */
|
|
spi_regs->DEF = DEV_CFG(dev)->def;
|
|
spi_regs->ORC = 0x000000AA;
|
|
|
|
/* user configuration */
|
|
flags = config->config;
|
|
if (flags & SPI_TRANSFER_LSB) {
|
|
spi_regs->CONFIG = NRF5_SPIS_ORDER_LSB;
|
|
} else {
|
|
spi_regs->CONFIG = NRF5_SPIS_ORDER_MSB;
|
|
}
|
|
if (flags & SPI_MODE_CPHA) {
|
|
spi_regs->CONFIG |= NRF5_SPIS_CPHA_TRAILING;
|
|
} else {
|
|
spi_regs->CONFIG |= NRF5_SPIS_CPHA_LEADING;
|
|
}
|
|
if (flags & SPI_MODE_CPOL) {
|
|
spi_regs->CONFIG |= NRF5_SPIS_CPOL_LOW;
|
|
} else {
|
|
spi_regs->CONFIG |= NRF5_SPIS_CPOL_HIGH;
|
|
}
|
|
|
|
/* Enable the SPIS - peripherals sharing same ID will be disabled */
|
|
spi_regs->ENABLE = NRF5_SPIS_ENABLED;
|
|
|
|
spis_nrf5_print_cfg_registers(dev);
|
|
|
|
SYS_LOG_DBG("SPI Slave Driver configured");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Read and/or write a defined amount of data through an SPI driver
|
|
*
|
|
* @param dev Pointer to the device structure for the driver instance
|
|
* @param tx_buf Memory buffer that data should be transferred from
|
|
* @param tx_buf_len Size of the memory buffer available for reading from
|
|
* @param rx_buf Memory buffer that data should be transferred to
|
|
* @param rx_buf_len Size of the memory buffer available for writing to
|
|
*
|
|
* @return 0 if successful, another DEV_* code otherwise.
|
|
*/
|
|
static int spis_nrf5_transceive(struct device *dev, const void *tx_buf,
|
|
u32_t tx_buf_len, void *rx_buf, u32_t rx_buf_len)
|
|
{
|
|
NRF_SPIS_Type *spi_regs = SPI_REGS(dev);
|
|
struct spis_nrf5_data *priv_data = DEV_DATA(dev);
|
|
|
|
__ASSERT(!((tx_buf_len && !tx_buf) || (rx_buf_len && !rx_buf)),
|
|
"spis_nrf5_transceive: ERROR - NULL buffers");
|
|
|
|
/* Buffer needs to be in RAM for EasyDMA to work */
|
|
if (!tx_buf || !is_buf_in_ram(tx_buf)) {
|
|
SYS_LOG_ERR("Invalid TX buf %p", tx_buf);
|
|
return -EINVAL;
|
|
}
|
|
if (!rx_buf || !is_buf_in_ram(rx_buf)) {
|
|
SYS_LOG_ERR("Invalid RX buf %p", rx_buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv_data->error = 0;
|
|
|
|
if (spi_regs->SEMSTAT == 1) {
|
|
#if defined(CONFIG_SOC_SERIES_NRF51X)
|
|
spi_regs->TXDPTR = (u32_t) tx_buf;
|
|
spi_regs->RXDPTR = (u32_t) rx_buf;
|
|
spi_regs->MAXTX = tx_buf_len;
|
|
spi_regs->MAXRX = rx_buf_len;
|
|
#elif defined(CONFIG_SOC_SERIES_NRF52X)
|
|
spi_regs->TXD.PTR = (u32_t) tx_buf;
|
|
spi_regs->RXD.PTR = (u32_t) rx_buf;
|
|
spi_regs->TXD.MAXCNT = tx_buf_len;
|
|
spi_regs->RXD.MAXCNT = rx_buf_len;
|
|
#endif
|
|
spi_regs->TASKS_RELEASE = 1;
|
|
} else {
|
|
SYS_LOG_ERR("Can't get SEM; unfinished transfer?");
|
|
return -EIO;
|
|
}
|
|
|
|
/* wait for transfer to complete */
|
|
k_sem_take(&priv_data->device_sync_sem, K_FOREVER);
|
|
|
|
if (priv_data->error) {
|
|
priv_data->error = 0;
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Complete SPI module data transfer operations.
|
|
* @param dev Pointer to the device structure for the driver instance
|
|
* @param error Error condition (0 = no error, otherwise an error occurred)
|
|
* @return None.
|
|
*/
|
|
static void spis_nrf5_complete(struct device *dev, u32_t error)
|
|
{
|
|
__unused NRF_SPIS_Type *spi_regs = SPI_REGS(dev);
|
|
__unused u32_t txd_amount, rxd_amount;
|
|
struct spis_nrf5_data *priv_data = DEV_DATA(dev);
|
|
|
|
#if defined(CONFIG_SOC_SERIES_NRF51X)
|
|
txd_amount = spi_regs->AMOUNTTX;
|
|
rxd_amount = spi_regs->AMOUNTRX;
|
|
#elif defined(CONFIG_SOC_SERIES_NRF52X)
|
|
txd_amount = spi_regs->RXD.AMOUNT;
|
|
rxd_amount = spi_regs->TXD.AMOUNT;
|
|
#endif
|
|
|
|
SYS_LOG_DBG("bytes transferred: TX: %u, RX: %u [err %u (%s)]",
|
|
txd_amount, rxd_amount,
|
|
error, error == 0 ? "OK" : "ERR");
|
|
|
|
priv_data->error = error ? 1 : 0;
|
|
|
|
k_sem_give(&priv_data->device_sync_sem);
|
|
}
|
|
|
|
/**
|
|
* @brief SPI module interrupt handler.
|
|
* @param arg Pointer to the device structure for the driver instance
|
|
* @return None.
|
|
*/
|
|
static void spis_nrf5_isr(void *arg)
|
|
{
|
|
struct device *dev = arg;
|
|
NRF_SPIS_Type *spi_regs = SPI_REGS(dev);
|
|
u32_t error;
|
|
u32_t tmp;
|
|
|
|
/* We get an interrupt for the following reasons:
|
|
* 1. Semaphore ACQUIRED:
|
|
* Semaphore is assigned to the CPU again (always happens
|
|
* after END if the END_ACQUIRE SHORTS is set)
|
|
* 2. End of Granted SPI transaction:
|
|
* Used to unblocked the caller, finishing the transaction
|
|
*/
|
|
|
|
/* NOTE:
|
|
* Section 15.8.1 of nrf52 manual suggests reading back the register
|
|
* to cause a 4-cycle delay to prevent the interrupt from
|
|
* re-occurring
|
|
*/
|
|
|
|
if (spi_regs->EVENTS_END) {
|
|
spi_regs->EVENTS_END = 0;
|
|
/* force register flush (per spec) */
|
|
tmp = spi_regs->EVENTS_END;
|
|
|
|
/* Read and clear error flags. */
|
|
error = spi_regs->STATUS;
|
|
spi_regs->STATUS = error;
|
|
|
|
spis_nrf5_complete(dev, error);
|
|
}
|
|
if (spi_regs->EVENTS_ACQUIRED) {
|
|
spi_regs->EVENTS_ACQUIRED = 0;
|
|
/* force registesr flush (per spec) */
|
|
tmp = spi_regs->EVENTS_ACQUIRED;
|
|
}
|
|
}
|
|
|
|
static const struct spi_driver_api nrf5_spis_api = {
|
|
.transceive = spis_nrf5_transceive,
|
|
.configure = spis_nrf5_configure,
|
|
.slave_select = NULL,
|
|
};
|
|
|
|
#if defined(CONFIG_SOC_SERIES_NRF51X)
|
|
static void spis_configure_psel(NRF_SPIS_Type *spi_regs,
|
|
const struct spis_nrf5_config *cfg)
|
|
{
|
|
spi_regs->PSELMOSI = cfg->mosi_pin;
|
|
spi_regs->PSELMISO = cfg->miso_pin;
|
|
spi_regs->PSELSCK = cfg->sck_pin;
|
|
if (cfg->csn_pin == NRF5_SPIS_CSN_DISABLED_CFG) {
|
|
spi_regs->PSELCSN = NRF5_SPIS_CSN_DISABLED;
|
|
} else {
|
|
spi_regs->PSELCSN = cfg->csn_pin;
|
|
}
|
|
}
|
|
#elif defined(CONFIG_SOC_SERIES_NRF52X)
|
|
static void spis_configure_psel(NRF_SPIS_Type *spi_regs,
|
|
const struct spis_nrf5_config *cfg)
|
|
{
|
|
spi_regs->PSEL.MOSI = cfg->mosi_pin;
|
|
spi_regs->PSEL.MISO = cfg->miso_pin;
|
|
spi_regs->PSEL.SCK = cfg->sck_pin;
|
|
if (cfg->csn_pin == NRF5_SPIS_CSN_DISABLED_CFG) {
|
|
spi_regs->PSEL.CSN = NRF5_SPIS_CSN_DISABLED;
|
|
} else {
|
|
spi_regs->PSEL.CSN = cfg->csn_pin;
|
|
}
|
|
}
|
|
#else
|
|
#error "Unsupported NRF5 SoC"
|
|
#endif
|
|
|
|
static int spis_nrf5_init(struct device *dev)
|
|
{
|
|
NRF_SPIS_Type *spi_regs = SPI_REGS(dev);
|
|
struct spis_nrf5_data *priv_data = DEV_DATA(dev);
|
|
const struct spis_nrf5_config *cfg = DEV_CFG(dev);
|
|
struct device *gpio_dev;
|
|
int ret;
|
|
|
|
SYS_LOG_DBG("SPI Slave driver init: %p", dev);
|
|
|
|
/* Enable constant latency for faster SPIS response */
|
|
NRF_POWER->TASKS_CONSTLAT = 1;
|
|
|
|
spi_regs->ENABLE = 0;
|
|
|
|
gpio_dev = device_get_binding(CONFIG_GPIO_NRF5_P0_DEV_NAME);
|
|
|
|
ret = gpio_pin_configure(gpio_dev, cfg->miso_pin,
|
|
GPIO_DIR_IN | GPIO_PUD_NORMAL);
|
|
__ASSERT_NO_MSG(!ret);
|
|
|
|
ret = gpio_pin_configure(gpio_dev, cfg->mosi_pin,
|
|
GPIO_DIR_IN | GPIO_PUD_NORMAL);
|
|
__ASSERT_NO_MSG(!ret);
|
|
|
|
ret = gpio_pin_configure(gpio_dev, cfg->sck_pin,
|
|
GPIO_DIR_IN | GPIO_PUD_NORMAL);
|
|
__ASSERT_NO_MSG(!ret);
|
|
|
|
if (cfg->csn_pin != 0xff) {
|
|
ret = gpio_pin_configure(gpio_dev, cfg->csn_pin,
|
|
GPIO_DIR_IN | GPIO_PUD_PULL_UP);
|
|
__ASSERT_NO_MSG(!ret);
|
|
}
|
|
|
|
spis_configure_psel(spi_regs, cfg);
|
|
|
|
cfg->config_func();
|
|
|
|
k_sem_init(&priv_data->device_sync_sem, 0, 1);
|
|
|
|
SYS_LOG_DBG("SPI Slave driver initialized on device: %p", dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* system bindings */
|
|
#ifdef CONFIG_SPIS0_NRF52
|
|
|
|
static void spis_config_irq_0(void);
|
|
|
|
static struct spis_nrf5_data spis_nrf5_data_0;
|
|
|
|
static const struct spis_nrf5_config spis_nrf5_config_0 = {
|
|
.regs = NRF_SPIS0,
|
|
.config_func = spis_config_irq_0,
|
|
.sck_pin = CONFIG_SPIS0_NRF52_GPIO_SCK_PIN,
|
|
.mosi_pin = CONFIG_SPIS0_NRF52_GPIO_MOSI_PIN,
|
|
.miso_pin = CONFIG_SPIS0_NRF52_GPIO_MISO_PIN,
|
|
.csn_pin = CONFIG_SPIS0_NRF52_GPIO_CSN_PIN,
|
|
.def = CONFIG_SPIS0_NRF52_DEF,
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(spis_nrf5_port_0, CONFIG_SPI_0_NAME, spis_nrf5_init,
|
|
&spis_nrf5_data_0, &spis_nrf5_config_0, PRE_KERNEL_1,
|
|
CONFIG_SPI_INIT_PRIORITY, &nrf5_spis_api);
|
|
|
|
static void spis_config_irq_0(void)
|
|
{
|
|
IRQ_CONNECT(NRF5_IRQ_SPI0_TWI0_IRQn, CONFIG_SPI_0_IRQ_PRI,
|
|
spis_nrf5_isr, DEVICE_GET(spis_nrf5_port_0), 0);
|
|
irq_enable(NRF5_IRQ_SPI0_TWI0_IRQn);
|
|
}
|
|
|
|
#endif /* CONFIG_SPIS0_NRF52 */
|
|
|
|
#ifdef CONFIG_SPIS1_NRF5
|
|
|
|
static void spis_config_irq_1(void);
|
|
|
|
static struct spis_nrf5_data spis_nrf5_data_1;
|
|
|
|
static const struct spis_nrf5_config spis_nrf5_config_1 = {
|
|
.regs = NRF_SPIS1,
|
|
.config_func = spis_config_irq_1,
|
|
.sck_pin = CONFIG_SPIS1_NRF5_GPIO_SCK_PIN,
|
|
.mosi_pin = CONFIG_SPIS1_NRF5_GPIO_MOSI_PIN,
|
|
.miso_pin = CONFIG_SPIS1_NRF5_GPIO_MISO_PIN,
|
|
.csn_pin = CONFIG_SPIS1_NRF5_GPIO_CSN_PIN,
|
|
.def = CONFIG_SPIS1_NRF5_DEF,
|
|
};
|
|
|
|
DEVICE_AND_API_INIT(spis_nrf5_port_1, CONFIG_SPI_1_NAME, spis_nrf5_init,
|
|
&spis_nrf5_data_1, &spis_nrf5_config_1, PRE_KERNEL_1,
|
|
CONFIG_SPI_INIT_PRIORITY, &nrf5_spis_api);
|
|
|
|
static void spis_config_irq_1(void)
|
|
{
|
|
IRQ_CONNECT(NRF5_IRQ_SPI1_TWI1_IRQn, CONFIG_SPI_1_IRQ_PRI,
|
|
spis_nrf5_isr, DEVICE_GET(spis_nrf5_port_1), 0);
|
|
irq_enable(NRF5_IRQ_SPI1_TWI1_IRQn);
|
|
}
|
|
|
|
#endif /* CONFIG_SPIS1_NRF5 */
|