449 lines
11 KiB
C
449 lines
11 KiB
C
/*
|
|
* Copyright (c) 2017 Intel Corp.
|
|
*
|
|
* SPDX-License-Identifier:0xApache-2.0
|
|
*/
|
|
|
|
/*
|
|
* #define CONFIG_ASSERT
|
|
* #define __ASSERT_ON 1
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <spi.h>
|
|
#include <soc.h>
|
|
#include <nrf.h>
|
|
#include <misc/util.h>
|
|
#include <gpio.h>
|
|
|
|
#define SYS_LOG_DOMAIN "spim"
|
|
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_SPI_LEVEL
|
|
#include <logging/sys_log.h>
|
|
|
|
/* @todo
|
|
*
|
|
* Add support for 52840 spim2.
|
|
*/
|
|
|
|
struct spim_nrf52_config {
|
|
volatile NRF_SPIM_Type *base;
|
|
void (*irq_config_func)(struct device *dev);
|
|
struct spi_config default_cfg;
|
|
struct {
|
|
uint8_t sck;
|
|
uint8_t mosi;
|
|
uint8_t miso;
|
|
#define SS_UNUSED 255
|
|
/* Pin number of up to 4 slave devices */
|
|
uint8_t ss[4];
|
|
} psel;
|
|
uint8_t orc;
|
|
};
|
|
|
|
struct spim_nrf52_data {
|
|
struct k_sem sem;
|
|
struct device *gpio_port;
|
|
uint8_t slave;
|
|
uint8_t stopped:1;
|
|
uint8_t txd:1;
|
|
uint8_t rxd:1;
|
|
#if (SYS_LOG_LEVEL > SYS_LOG_LEVEL_INFO)
|
|
uint32_t tx_cnt;
|
|
uint32_t rx_cnt;
|
|
#endif
|
|
};
|
|
|
|
#define NRF52_SPIM_INT_END SPIM_INTENSET_END_Msk
|
|
#define NRF52_SPIM_INT_ENDRX SPIM_INTENSET_ENDRX_Msk
|
|
#define NRF52_SPIM_INT_ENDTX SPIM_INTENSET_ENDTX_Msk
|
|
#define NRF52_SPIM_ENABLE SPIM_ENABLE_ENABLE_Enabled
|
|
#define NRF52_SPIM_DISABLE SPIM_ENABLE_ENABLE_Disabled
|
|
|
|
static void spim_nrf52_print_cfg_registers(struct device *dev)
|
|
{
|
|
const struct spim_nrf52_config *config = dev->config->config_info;
|
|
volatile NRF_SPIM_Type *spim = config->base;
|
|
|
|
SYS_LOG_DBG("\n"
|
|
"SHORTS=0x%x INT=0x%x FREQUENCY=0x%x CONFIG=0x%x\n"
|
|
"ENABLE=0x%x SCKPIN=%d MISOPIN=%d MOSIPIN=%d\n"
|
|
"RXD.(PTR=0x%x MAXCNT=0x%x AMOUNT=0x%x)\n"
|
|
"TXD.(PTR=0x%x MAXCNT=0x%x AMOUNT=0x%x)\n",
|
|
spim->SHORTS, spim->INTENSET, spim->FREQUENCY, spim->CONFIG,
|
|
spim->ENABLE, spim->PSEL.SCK, spim->PSEL.MISO, spim->PSEL.MOSI,
|
|
spim->RXD.PTR, spim->RXD.MAXCNT, spim->RXD.AMOUNT,
|
|
spim->TXD.PTR, spim->TXD.MAXCNT, spim->TXD.AMOUNT);
|
|
|
|
/* maybe unused */
|
|
(void)spim;
|
|
}
|
|
|
|
static int spim_nrf52_configure(struct device *dev,
|
|
struct spi_config *spi_config)
|
|
{
|
|
const struct spim_nrf52_config *config = dev->config->config_info;
|
|
struct spim_nrf52_data *data = dev->driver_data;
|
|
volatile NRF_SPIM_Type *spim = config->base;
|
|
uint32_t flags;
|
|
|
|
SYS_LOG_DBG("config=0x%x max_sys_freq=%d", spi_config->config,
|
|
spi_config->max_sys_freq);
|
|
|
|
/* make sure SPIM block is off */
|
|
spim->ENABLE = NRF52_SPIM_DISABLE;
|
|
|
|
spim->INTENCLR = 0xffffffffUL;
|
|
|
|
spim->SHORTS = 0;
|
|
|
|
spim->ORC = config->orc;
|
|
|
|
spim->TXD.LIST = 0;
|
|
spim->RXD.LIST = 0;
|
|
|
|
spim->TXD.MAXCNT = 0;
|
|
spim->RXD.MAXCNT = 0;
|
|
|
|
spim->EVENTS_END = 0;
|
|
spim->EVENTS_ENDTX = 0;
|
|
spim->EVENTS_ENDRX = 0;
|
|
spim->EVENTS_STOPPED = 0;
|
|
spim->EVENTS_STARTED = 0;
|
|
|
|
data->stopped = 1;
|
|
data->txd = 0;
|
|
data->rxd = 0;
|
|
#if (SYS_LOG_LEVEL > SYS_LOG_LEVEL_INFO)
|
|
data->tx_cnt = 0;
|
|
data->rx_cnt = 0;
|
|
#endif
|
|
|
|
switch (spi_config->max_sys_freq) {
|
|
case 125000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_K125;
|
|
break;
|
|
case 250000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_K250;
|
|
break;
|
|
case 500000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_K500;
|
|
break;
|
|
case 1000000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M1;
|
|
break;
|
|
case 2000000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M2;
|
|
break;
|
|
case 4000000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M4;
|
|
break;
|
|
case 8000000:
|
|
spim->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M8;
|
|
default:
|
|
SYS_LOG_ERR("unsupported frequency sck=%d\n",
|
|
spi_config->max_sys_freq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
flags = spi_config->config;
|
|
|
|
/* nrf5 supports only 8 bit word size */
|
|
if (SPI_WORD_SIZE_GET(flags) != 8) {
|
|
SYS_LOG_ERR("unsupported word size\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (flags & SPI_MODE_LOOP) {
|
|
SYS_LOG_ERR("loopback unsupported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spim->CONFIG = (flags & SPI_TRANSFER_LSB) ? SPIM_CONFIG_ORDER_LsbFirst
|
|
: SPIM_CONFIG_ORDER_MsbFirst;
|
|
spim->CONFIG |= (flags & SPI_MODE_CPOL) ? SPIM_CONFIG_CPOL_ActiveLow
|
|
: SPIM_CONFIG_CPOL_ActiveHigh;
|
|
spim->CONFIG |= (flags & SPI_MODE_CPHA) ? SPIM_CONFIG_CPHA_Trailing
|
|
: SPIM_CONFIG_CPHA_Leading;
|
|
|
|
spim->INTENSET = NRF52_SPIM_INT_END;
|
|
|
|
spim_nrf52_print_cfg_registers(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spim_nrf52_slave_select(struct device *dev, uint32_t slave)
|
|
{
|
|
struct spim_nrf52_data *data = dev->driver_data;
|
|
const struct spim_nrf52_config *config = dev->config->config_info;
|
|
|
|
__ASSERT((slave > 0) && (slave <= 4), "slave=%d", slave);
|
|
|
|
slave--;
|
|
|
|
if (config->psel.ss[slave] == SS_UNUSED) {
|
|
SYS_LOG_ERR("Slave %d is not configured\n", slave);
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->slave = slave;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void spim_nrf52_csn(struct device *gpio_port, uint32_t pin,
|
|
bool select)
|
|
{
|
|
int status;
|
|
|
|
status = gpio_pin_write(gpio_port, pin, select ? 0x0 : 0x1);
|
|
__ASSERT_NO_MSG(status == 0);
|
|
}
|
|
|
|
static int spim_nrf52_transceive(struct device *dev, const void *tx_buf,
|
|
uint32_t tx_buf_len, void *rx_buf,
|
|
uint32_t rx_buf_len)
|
|
{
|
|
const struct spim_nrf52_config *config = dev->config->config_info;
|
|
struct spim_nrf52_data *data = dev->driver_data;
|
|
volatile NRF_SPIM_Type *spim = config->base;
|
|
|
|
SYS_LOG_DBG("transceive tx_buf=0x%p rx_buf=0x%p tx_len=0x%x "
|
|
"rx_len=0x%x\n", tx_buf, rx_buf, tx_buf_len, rx_buf_len);
|
|
|
|
if (spim->ENABLE) {
|
|
return -EALREADY;
|
|
}
|
|
spim->ENABLE = NRF52_SPIM_ENABLE;
|
|
|
|
__ASSERT_NO_MSG(data->stopped);
|
|
data->stopped = 0;
|
|
|
|
if (tx_buf_len) {
|
|
__ASSERT_NO_MSG(tx_buf);
|
|
spim->TXD.MAXCNT = tx_buf_len;
|
|
spim->TXD.PTR = (uint32_t)tx_buf;
|
|
data->txd = 0;
|
|
#if (SYS_LOG_LEVEL > SYS_LOG_LEVEL_INFO)
|
|
data->tx_cnt = 0;
|
|
#endif
|
|
} else {
|
|
spim->TXD.MAXCNT = 0;
|
|
}
|
|
|
|
if (rx_buf_len) {
|
|
__ASSERT_NO_MSG(rx_buf);
|
|
spim->RXD.MAXCNT = rx_buf_len;
|
|
spim->RXD.PTR = (uint32_t)rx_buf;
|
|
data->rxd = 0;
|
|
#if (SYS_LOG_LEVEL > SYS_LOG_LEVEL_INFO)
|
|
data->rx_cnt = 0;
|
|
#endif
|
|
} else {
|
|
spim->RXD.MAXCNT = 0;
|
|
}
|
|
|
|
if (data->slave != SS_UNUSED) {
|
|
spim_nrf52_csn(data->gpio_port, config->psel.ss[data->slave],
|
|
true);
|
|
}
|
|
|
|
spim->INTENSET = NRF52_SPIM_INT_END;
|
|
|
|
SYS_LOG_DBG("spi_xfer %s/%s CS%d\n", rx_buf_len ? "R" : "-",
|
|
tx_buf_len ? "W" : "-", data->slave);
|
|
|
|
/* start SPI transfer transaction */
|
|
spim->TASKS_START = 1;
|
|
|
|
/* Wait for the transfer to complete */
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
|
|
if (data->slave != SS_UNUSED) {
|
|
spim_nrf52_csn(data->gpio_port, config->psel.ss[data->slave],
|
|
false);
|
|
}
|
|
|
|
/* Disable SPIM block for power saving */
|
|
spim->INTENCLR = 0xffffffffUL;
|
|
spim->ENABLE = NRF52_SPIM_DISABLE;
|
|
|
|
#if (SYS_LOG_LEVEL > SYS_LOG_LEVEL_INFO)
|
|
SYS_LOG_DBG("xfer complete rx_cnt=0x%x tx_cnt=0x%x rxd=%d txd=%d "
|
|
"stopped=%d\n", data->rx_cnt, data->tx_cnt, data->rxd,
|
|
data->txd, data->stopped);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void spim_nrf52_isr(void *arg)
|
|
{
|
|
struct device *dev = arg;
|
|
const struct spim_nrf52_config *config = dev->config->config_info;
|
|
struct spim_nrf52_data *data = dev->driver_data;
|
|
volatile NRF_SPIM_Type *spim = config->base;
|
|
|
|
if (spim->EVENTS_END) {
|
|
data->rxd = 1;
|
|
data->txd = 1;
|
|
|
|
/* assume spi transaction has stopped */
|
|
data->stopped = 1;
|
|
|
|
/* Cortex M4 specific EVENTS register clearing requires 4 cycles
|
|
* delayto avoid re-triggering of interrupt. Call to
|
|
* k_sem_give() will ensure this limit.
|
|
*/
|
|
spim->EVENTS_END = 0;
|
|
|
|
#if (SYS_LOG_LEVEL > SYS_LOG_LEVEL_INFO)
|
|
data->rx_cnt = spim->RXD.AMOUNT;
|
|
data->tx_cnt = spim->TXD.AMOUNT;
|
|
SYS_LOG_DBG("endrxtx rx_cnt=%d tx_cnt=%d", data->rx_cnt,
|
|
data->tx_cnt);
|
|
#endif
|
|
k_sem_give(&data->sem);
|
|
}
|
|
}
|
|
|
|
static int spim_nrf52_init(struct device *dev)
|
|
{
|
|
const struct spim_nrf52_config *config = dev->config->config_info;
|
|
struct spim_nrf52_data *data = dev->driver_data;
|
|
volatile NRF_SPIM_Type *spim = config->base;
|
|
int status;
|
|
int i;
|
|
|
|
SYS_LOG_DBG("%s", dev->config->name);
|
|
|
|
data->gpio_port = device_get_binding(CONFIG_GPIO_NRF5_P0_DEV_NAME);
|
|
|
|
k_sem_init(&data->sem, 0, UINT_MAX);
|
|
|
|
for (i = 0; i < sizeof(config->psel.ss); i++) {
|
|
if (config->psel.ss[i] != SS_UNUSED) {
|
|
status = gpio_pin_configure(data->gpio_port,
|
|
config->psel.ss[i],
|
|
GPIO_DIR_OUT);
|
|
__ASSERT_NO_MSG(status == 0);
|
|
|
|
spim_nrf52_csn(data->gpio_port, config->psel.ss[i],
|
|
false);
|
|
SYS_LOG_DBG("CS%d=%d\n", i, config->psel.ss[i]);
|
|
}
|
|
}
|
|
|
|
data->slave = SS_UNUSED;
|
|
|
|
status = gpio_pin_configure(data->gpio_port, config->psel.sck,
|
|
GPIO_DIR_OUT);
|
|
__ASSERT_NO_MSG(status == 0);
|
|
|
|
status = gpio_pin_configure(data->gpio_port, config->psel.mosi,
|
|
GPIO_DIR_OUT);
|
|
__ASSERT_NO_MSG(status == 0);
|
|
|
|
status = gpio_pin_configure(data->gpio_port, config->psel.miso,
|
|
GPIO_DIR_IN);
|
|
__ASSERT_NO_MSG(status == 0);
|
|
|
|
spim->PSEL.SCK = config->psel.sck;
|
|
spim->PSEL.MOSI = config->psel.mosi;
|
|
spim->PSEL.MISO = config->psel.miso;
|
|
|
|
status = spim_nrf52_configure(dev, (void *)&config->default_cfg);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
config->irq_config_func(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct spi_driver_api spim_nrf52_driver_api = {
|
|
.configure = spim_nrf52_configure,
|
|
.slave_select = spim_nrf52_slave_select,
|
|
.transceive = spim_nrf52_transceive,
|
|
};
|
|
|
|
/* i2c & spi (SPIM, SPIS, SPI) instance with the same id (e.g. I2C_0 and SPI_0)
|
|
* can NOT be used at the same time on nRF5x chip family.
|
|
*/
|
|
#if defined(CONFIG_SPIM0_NRF52) && !defined(CONFIG_I2C_0)
|
|
static void spim_nrf52_config_func_0(struct device *dev);
|
|
|
|
static const struct spim_nrf52_config spim_nrf52_config_0 = {
|
|
.base = NRF_SPIM0,
|
|
.irq_config_func = spim_nrf52_config_func_0,
|
|
.default_cfg = {
|
|
.config = CONFIG_SPI_0_DEFAULT_CFG,
|
|
.max_sys_freq = CONFIG_SPI_0_DEFAULT_BAUD_RATE,
|
|
},
|
|
.psel = {
|
|
.sck = CONFIG_SPIM0_NRF52_GPIO_SCK_PIN,
|
|
.mosi = CONFIG_SPIM0_NRF52_GPIO_MOSI_PIN,
|
|
.miso = CONFIG_SPIM0_NRF52_GPIO_MISO_PIN,
|
|
.ss = { CONFIG_SPIM0_NRF52_GPIO_SS_PIN_0,
|
|
CONFIG_SPIM0_NRF52_GPIO_SS_PIN_1,
|
|
CONFIG_SPIM0_NRF52_GPIO_SS_PIN_2,
|
|
CONFIG_SPIM0_NRF52_GPIO_SS_PIN_3 },
|
|
},
|
|
.orc = CONFIG_SPIM0_NRF52_ORC,
|
|
};
|
|
|
|
static struct spim_nrf52_data spim_nrf52_data_0;
|
|
|
|
DEVICE_AND_API_INIT(spim_nrf52_0, CONFIG_SPI_0_NAME, spim_nrf52_init,
|
|
&spim_nrf52_data_0, &spim_nrf52_config_0,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&spim_nrf52_driver_api);
|
|
|
|
static void spim_nrf52_config_func_0(struct device *dev)
|
|
{
|
|
IRQ_CONNECT(NRF5_IRQ_SPI0_TWI0_IRQn, CONFIG_SPI_0_IRQ_PRI,
|
|
spim_nrf52_isr, DEVICE_GET(spim_nrf52_0), 0);
|
|
|
|
irq_enable(NRF5_IRQ_SPI0_TWI0_IRQn);
|
|
}
|
|
#endif /* CONFIG_SPIM0_NRF52 && !CONFIG_I2C_0 */
|
|
|
|
#if defined(CONFIG_SPIM1_NRF52) && !defined(CONFIG_I2C_1)
|
|
static void spim_nrf52_config_func_1(struct device *dev);
|
|
|
|
static const struct spim_nrf52_config spim_nrf52_config_1 = {
|
|
.base = NRF_SPIM1,
|
|
.irq_config_func = spim_nrf52_config_func_1,
|
|
.default_cfg = {
|
|
.config = CONFIG_SPI_1_DEFAULT_CFG,
|
|
.max_sys_freq = CONFIG_SPI_1_DEFAULT_BAUD_RATE,
|
|
},
|
|
.psel = {
|
|
.sck = CONFIG_SPIM1_NRF52_GPIO_SCK_PIN,
|
|
.mosi = CONFIG_SPIM1_NRF52_GPIO_MOSI_PIN,
|
|
.miso = CONFIG_SPIM1_NRF52_GPIO_MISO_PIN,
|
|
.ss = { CONFIG_SPIM1_NRF52_GPIO_SS_PIN_0,
|
|
CONFIG_SPIM1_NRF52_GPIO_SS_PIN_1,
|
|
CONFIG_SPIM1_NRF52_GPIO_SS_PIN_2,
|
|
CONFIG_SPIM1_NRF52_GPIO_SS_PIN_3 },
|
|
},
|
|
.orc = CONFIG_SPIM0_NRF52_ORC,
|
|
};
|
|
|
|
static struct spim_nrf52_data spim_nrf52_data_1;
|
|
|
|
DEVICE_AND_API_INIT(spim_nrf52_1, CONFIG_SPI_1_NAME, spim_nrf52_init,
|
|
&spim_nrf52_data_1, &spim_nrf52_config_1,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&spim_nrf52_driver_api);
|
|
|
|
static void spim_nrf52_config_func_1(struct device *dev)
|
|
{
|
|
IRQ_CONNECT(NRF5_IRQ_SPI1_TWI1_IRQn, CONFIG_SPI_1_IRQ_PRI,
|
|
spim_nrf52_isr, DEVICE_GET(spim_nrf52_1), 0);
|
|
|
|
irq_enable(NRF5_IRQ_SPI1_TWI1_IRQn);
|
|
}
|
|
#endif /* CONFIG_SPIM1_NRF52 && !CONFIG_I2C_1 */
|