1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-28 16:01:18 +01:00

nrf52: Add EasyDMA-based SPI periph driver

This commit is contained in:
Koen Zandberg 2020-05-18 19:15:35 +02:00
parent 1139c0737f
commit 720ccad7dd
No known key found for this signature in database
GPG Key ID: 0895A893E6D2985B
3 changed files with 295 additions and 0 deletions

View File

@ -5,5 +5,12 @@ ifneq (,$(filter nrf802154,$(USEMODULE)))
USEMODULE += netdev_ieee802154
endif
# The nrf52832 requires gpio IRQ with SPI to work around errata 58
ifneq (,$(filter nrf52832xxaa,$(CPU_MODEL)))
ifneq (,$(filter periph_spi,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio_irq
endif
endif
include $(RIOTCPU)/nrf5x_common/Makefile.dep
include $(RIOTCPU)/cortexm_common/Makefile.dep

View File

@ -27,6 +27,14 @@
extern "C" {
#endif
/**
* @brief Enable the workaround for the SPI single byte transmit errata (No.
* 58 on the nrf52832)
*/
#ifdef CPU_MODEL_NRF52832XXAA
#define ERRATA_SPI_SINGLE_BYTE_WORKAROUND (1)
#endif
/**
* @brief System core clock speed, fixed to 64MHz for all NRF52x CPUs
*/
@ -54,6 +62,14 @@ extern "C" {
*/
#define ADC_NUMOF (9U)
/**
* @brief SPI temporary buffer size for storing const data in RAM before
* initiating DMA transfer
*/
#ifndef CONFIG_SPI_MBUF_SIZE
#define CONFIG_SPI_MBUF_SIZE 64
#endif
/**
* @brief nRF52 specific naming of ADC lines (for convenience)
*/
@ -180,6 +196,20 @@ typedef struct {
} uart_conf_t;
#endif
/**
* @brief SPI configuration values
*/
typedef struct {
NRF_SPIM_Type *dev; /**< SPI device used */
gpio_t sclk; /**< CLK pin */
gpio_t mosi; /**< MOSI pin */
gpio_t miso; /**< MISO pin */
#if ERRATA_SPI_SINGLE_BYTE_WORKAROUND
uint8_t ppi; /**< PPI channel */
#endif
} spi_conf_t;
/**
* @brief Common SPI/I2C interrupt callback
*

258
cpu/nrf52/periph/spi.c Normal file
View File

@ -0,0 +1,258 @@
/*
* Copyright (C) 2014-2016 Freie Universität Berlin
* Copyright (C) 2020 Inria
* Copyright (C) 2020 Koen Zandberg <koen@bergzand.net>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_nrf52
* @ingroup drivers_periph_spi
* @{
*
* @file
* @brief Low-level SPI driver implementation based on the SPIM peripheral
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Frank Holtz <frank-riot2015@holtznet.de>
* @author Jan Wagner <mail@jwagner.eu>
* @author Koen Zandberg <koen@bergzand.net>
*
* @}
*/
#include "cpu.h"
#include "mutex.h"
#include "assert.h"
#include "periph/spi.h"
#include "periph/gpio.h"
#include "periph_cpu.h"
#include <string.h>
#define RAM_MASK (0x20000000)
/**
* @brief array holding one pre-initialized mutex for each SPI device
*/
static mutex_t locks[SPI_NUMOF];
/**
* @brief array with a busy mutex for each SPI device, used to block the
* thread until the transfer is done
*/
static mutex_t busy[SPI_NUMOF];
static uint8_t _mbuf[SPI_NUMOF][CONFIG_SPI_MBUF_SIZE];
static void spi_isr_handler(void *arg);
static inline NRF_SPIM_Type *dev(spi_t bus)
{
return (NRF_SPIM_Type *)spi_config[bus].dev;
}
static inline bool _in_ram(const uint8_t *data)
{
return ((uint32_t)data & RAM_MASK);
}
#ifdef ERRATA_SPI_SINGLE_BYTE_WORKAROUND
void spi_gpio_handler(void *arg)
{
spi_t bus = (spi_t)arg;
/**
* Immediately disable the IRQ, we only care about one PPI event per
* transfer
*/
gpio_irq_disable(spi_config[bus].sclk);
}
#endif
/**
* @brief Work-around for transmitting 1 byte with SPIM on the nrf52832.
* @warning Must not be used when transmitting multiple bytes.
* @warning After this workaround is used, the user must reset the PPI channel
* and the GPIOTE channel before attempting to transmit multiple bytes.
*
* @param bus The SPI instance that is in use.
*/
static void _setup_workaround_for_ftpan_58(spi_t bus)
{
#ifdef ERRATA_SPI_SINGLE_BYTE_WORKAROUND
gpio_init_int(spi_config[bus].sclk, GPIO_OUT, GPIO_BOTH,
spi_gpio_handler, (void *)bus);
gpio_irq_disable(spi_config[bus].sclk);
uint8_t channel = gpio_int_get_exti(spi_config[bus].sclk);
assert(channel != 0xff);
// Stop the spim instance when SCK toggles.
NRF_PPI->CH[spi_config[bus].ppi].EEP =
(uint32_t)&NRF_GPIOTE->EVENTS_IN[channel];
NRF_PPI->CH[spi_config[bus].ppi].TEP = (uint32_t)&dev(bus)->TASKS_STOP;
#else
(void)bus;
#endif
}
static void _enable_workaround(spi_t bus)
{
#ifdef ERRATA_SPI_SINGLE_BYTE_WORKAROUND
/**
* The spim instance cannot be stopped mid-byte, so it will finish
* transmitting the first byte and then stop. Effectively ensuring
* that only 1 byte is transmitted.
*/
NRF_PPI->CHENSET = 1U << spi_config[bus].ppi;
gpio_irq_enable(spi_config[bus].sclk);
#else
(void)bus;
#endif
}
static void _clear_workaround(spi_t bus)
{
#ifdef ERRATA_SPI_SINGLE_BYTE_WORKAROUND
NRF_PPI->CHENCLR = 1U << spi_config[bus].ppi;
#else
(void)bus;
#endif
}
void spi_init(spi_t bus)
{
assert(bus < SPI_NUMOF);
/* initialize mutex */
mutex_init(&locks[bus]);
mutex_init(&busy[bus]);
mutex_lock(&busy[bus]);
/* initialize pins */
spi_init_pins(bus);
}
void spi_init_pins(spi_t bus)
{
gpio_init(spi_config[bus].sclk, GPIO_OUT);
gpio_init(spi_config[bus].mosi, GPIO_OUT);
gpio_init(spi_config[bus].miso, GPIO_IN);
/* select pins for the SPI device */
SPI_SCKSEL = spi_config[bus].sclk;
SPI_MOSISEL = spi_config[bus].mosi;
SPI_MISOSEL = spi_config[bus].miso;
_setup_workaround_for_ftpan_58(bus);
spi_twi_irq_register_spi(dev(bus), spi_isr_handler, (void *)bus);
}
int spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
{
(void)cs;
mutex_lock(&locks[bus]);
/* configure bus */
dev(bus)->CONFIG = mode;
dev(bus)->FREQUENCY = clk;
/* enable the bus */
dev(bus)->ENABLE = SPIM_ENABLE_ENABLE_Enabled;
return SPI_OK;
}
void spi_release(spi_t bus)
{
/* power off everything */
dev(bus)->ENABLE = 0;
mutex_unlock(&locks[bus]);
}
static size_t _transfer(spi_t bus, const uint8_t *out_buf, uint8_t *in_buf,
size_t remaining_len)
{
uint8_t transfer_len = remaining_len > UINT8_MAX ? UINT8_MAX : remaining_len;
const uint8_t *out_mbuf = out_buf;
/**
* Copy the out buffer in case it resides in flash, EasyDMA only works from
* RAM
*/
if (out_buf && !_in_ram(out_buf)) {
/* The SPI MBUF can be smaller than UINT8_MAX */
transfer_len = transfer_len > CONFIG_SPI_MBUF_SIZE
? CONFIG_SPI_MBUF_SIZE : transfer_len;
memcpy(_mbuf[bus], out_buf, transfer_len);
out_mbuf = _mbuf[bus];
}
uint8_t out_len = (out_buf) ? transfer_len : 0;
uint8_t in_len = (in_buf) ? transfer_len : 0;
dev(bus)->TXD.PTR = (uint32_t)out_mbuf;
dev(bus)->RXD.PTR = (uint32_t)in_buf;
dev(bus)->TXD.MAXCNT = out_len;
dev(bus)->RXD.MAXCNT = in_len;
/* clear any spurious END events */
dev(bus)->EVENTS_END = 0;
dev(bus)->TASKS_START = 1;
return transfer_len;
}
void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
const void *out, void *in, size_t len)
{
const uint8_t *out_buf = out;
uint8_t *in_buf = in;
assert(out_buf || in_buf);
if (cs != SPI_CS_UNDEF) {
gpio_clear((gpio_t)cs);
}
/* Enable the workaround when the length is only 1 byte */
size_t _len = len;
if (_len == 1) {
_enable_workaround(bus);
}
/* Enable IRQ */
dev(bus)->INTENSET = SPIM_INTENSET_END_Msk;
do {
size_t transfer_len = _transfer(bus, out_buf, in_buf, len);
/* Block until the irq releases the mutex, then lock it again for the
* next transfer */
mutex_lock(&busy[bus]);
out_buf += out_buf ? transfer_len : 0;
in_buf += in_buf ? transfer_len : 0;
len -= transfer_len;
} while (len);
/* Disable IRQ */
dev(bus)->INTENCLR = SPIM_INTENCLR_END_Msk;
/**
* While we could always disable the workaround, only doing this when
* required spares us some cycles by not having to write to volatile
* registers
*/
if (_len == 1) {
_clear_workaround(bus);
}
if ((cs != SPI_CS_UNDEF) && (!cont)) {
gpio_set((gpio_t)cs);
}
}
void spi_isr_handler(void *arg)
{
spi_t bus = (spi_t)arg;
mutex_unlock(&busy[bus]);
dev(bus)->EVENTS_END = 0;
}