mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
547 lines
17 KiB
C
547 lines
17 KiB
C
/*
|
|
* Copyright (C) 2018 Gunar Schorcht
|
|
*
|
|
* 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_esp32
|
|
* @ingroup drivers_periph_spi
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level SPI driver implementation
|
|
*
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
*
|
|
* @}
|
|
*/
|
|
#define ENABLE_DEBUG (0)
|
|
#include "debug.h"
|
|
#include "esp_common.h"
|
|
#include "log.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "cpu.h"
|
|
#include "mutex.h"
|
|
#include "periph/spi.h"
|
|
|
|
#include "driver/periph_ctrl.h"
|
|
#include "rom/ets_sys.h"
|
|
#include "soc/gpio_reg.h"
|
|
#include "soc/gpio_sig_map.h"
|
|
#include "soc/gpio_struct.h"
|
|
#include "soc/io_mux_reg.h"
|
|
#include "soc/spi_reg.h"
|
|
#include "soc/spi_struct.h"
|
|
|
|
#include "gpio_arch.h"
|
|
|
|
#define SPI_BLOCK_SIZE 64 /* number of bytes per SPI transfer */
|
|
|
|
#define FSPI (1) /* controller SPI1 realizes interface FSPI */
|
|
#define HSPI (2) /* controller SPI2 realizes interface HSPI */
|
|
#define VSPI (3) /* controller SPI3 realizes interface VSPI */
|
|
|
|
/* pins of FSI are fixed */
|
|
#define FSPI_SCK GPIO6
|
|
#define FSPI_MISO GPIO7
|
|
#define FSPI_MOSI GPIO8
|
|
|
|
/** stucture which decribes all properties of one SPI bus */
|
|
struct _spi_bus_t {
|
|
spi_dev_t* regs; /* pointer to register data struct of the SPI device */
|
|
uint8_t controller; /* number of the controller used */
|
|
uint8_t mod; /* peripheral hardware module of the SPI interface */
|
|
uint8_t int_src; /* peripheral interrupt source used by the SPI device */
|
|
uint8_t pin_sck; /* SCK pin */
|
|
uint8_t pin_mosi; /* MOSI pin */
|
|
uint8_t pin_miso; /* MISO pin */
|
|
uint8_t pin_cs; /* CS pin */
|
|
uint8_t signal_sck; /* SCK signal from the controller */
|
|
uint8_t signal_mosi; /* MOSI signal from the controller */
|
|
uint8_t signal_miso; /* MISO signal to the controller */
|
|
mutex_t lock; /* mutex for each possible SPI interface */
|
|
bool initialized; /* interface already initialized */
|
|
bool pins_initialized; /* pins interface initialized */
|
|
};
|
|
|
|
static struct _spi_bus_t _spi[] = {
|
|
#ifdef SPI0_DEV
|
|
{
|
|
.controller = SPI0_DEV,
|
|
.pin_cs = SPI0_CS0,
|
|
#if SPI0_DEV != FSPI
|
|
.pin_sck = SPI0_SCK,
|
|
.pin_mosi = SPI0_MOSI,
|
|
.pin_miso = SPI0_MISO,
|
|
#else
|
|
.pin_sck = FSPI_SCK,
|
|
.pin_mosi = FSPI_MOSI,
|
|
.pin_miso = FSPI_MISO,
|
|
#endif
|
|
.initialized = false,
|
|
.pins_initialized = false,
|
|
.lock = MUTEX_INIT
|
|
},
|
|
#endif
|
|
|
|
#ifdef SPI1_DEV
|
|
{
|
|
.controller = SPI1_DEV,
|
|
.pin_cs = SPI1_CS0,
|
|
#if SPI1_DEV != FSPI
|
|
.pin_sck = SPI1_SCK,
|
|
.pin_mosi = SPI1_MOSI,
|
|
.pin_miso = SPI1_MISO,
|
|
#else
|
|
.pin_sck = FSPI_SCK,
|
|
.pin_mosi = FSPI_MOSI,
|
|
.pin_miso = FSPI_MISO,
|
|
#endif
|
|
.initialized = false,
|
|
.pins_initialized = false,
|
|
.lock = MUTEX_INIT
|
|
},
|
|
#endif
|
|
|
|
#ifdef SPI2_DEV
|
|
{
|
|
.controller = SPI2_DEV,
|
|
.pin_cs = SPI2_CS0,
|
|
#if SPI2_DEV != FSPI
|
|
.pin_sck = SPI2_SCK,
|
|
.pin_mosi = SPI2_MOSI,
|
|
.pin_miso = SPI2_MISO,
|
|
#else
|
|
.pin_sck = FSPI_SCK,
|
|
.pin_mosi = FSPI_MOSI,
|
|
.pin_miso = FSPI_MISO,
|
|
#endif
|
|
.initialized = false,
|
|
.pins_initialized = false,
|
|
.lock = MUTEX_INIT
|
|
},
|
|
#endif
|
|
};
|
|
|
|
/* the number of SPI bus devices used */
|
|
const unsigned spi_bus_num = sizeof(_spi) / sizeof(_spi[0]);
|
|
|
|
#define CHECK_SPI_DEV(bus) { \
|
|
CHECK_PARAM(bus < spi_bus_num); \
|
|
if (_spi[bus].regs == NULL) { \
|
|
LOG_TAG_ERROR("spi", "SPI_DEV(%d) is not available\n", bus); \
|
|
return; \
|
|
} \
|
|
}
|
|
|
|
#define CHECK_SPI_DEV_RET(bus,error) { \
|
|
CHECK_PARAM_RET(bus < spi_bus_num, error); \
|
|
if (_spi[bus].regs == NULL) { \
|
|
LOG_TAG_ERROR("spi", "SPI_DEV(%d) is not available\n", bus); \
|
|
return error; \
|
|
} \
|
|
}
|
|
/*
|
|
* GPIOs that were once initialized as SPI interface pins can not be used
|
|
* afterwards for anything else. Therefore, SPI interfaces are not initialized
|
|
* until they are used for the first time. The *spi_init* function is just a
|
|
* dummy for source code compatibility. The initialization of an SPI interface
|
|
* is performed by the *_spi_init_internal* function, which is called either by
|
|
* the *spi_init_cs* function or the *spi_acquire* function when the interface
|
|
* is used for the first time.
|
|
*/
|
|
|
|
void IRAM_ATTR spi_init (spi_t bus)
|
|
{
|
|
CHECK_PARAM(bus < spi_bus_num);
|
|
|
|
switch (_spi[bus].controller) {
|
|
case FSPI: _spi[bus].regs = &SPI1;
|
|
_spi[bus].mod = PERIPH_SPI_MODULE;
|
|
_spi[bus].int_src = ETS_SPI1_INTR_SOURCE;
|
|
_spi[bus].signal_sck = SPICLK_OUT_IDX;
|
|
_spi[bus].signal_mosi = SPID_OUT_IDX;
|
|
_spi[bus].signal_miso = SPIQ_IN_IDX;
|
|
break;
|
|
case HSPI: _spi[bus].regs = &SPI2;
|
|
_spi[bus].mod = PERIPH_HSPI_MODULE;
|
|
_spi[bus].int_src = ETS_SPI2_INTR_SOURCE;
|
|
_spi[bus].signal_sck = HSPICLK_OUT_IDX;
|
|
_spi[bus].signal_mosi = HSPID_OUT_IDX;
|
|
_spi[bus].signal_miso = HSPIQ_IN_IDX;
|
|
break;
|
|
case VSPI: _spi[bus].regs = &SPI3;
|
|
_spi[bus].mod = PERIPH_VSPI_MODULE;
|
|
_spi[bus].int_src = ETS_SPI3_INTR_SOURCE;
|
|
_spi[bus].signal_sck = VSPICLK_OUT_IDX;
|
|
_spi[bus].signal_mosi = VSPID_OUT_IDX;
|
|
_spi[bus].signal_miso = VSPIQ_IN_IDX;
|
|
break;
|
|
default: break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Internal initialization function when the interface is used the first time */
|
|
static void _spi_init_internal (spi_t bus)
|
|
{
|
|
CHECK_SPI_DEV(bus);
|
|
|
|
/* avoid multiple initializations */
|
|
if (_spi[bus].initialized) {
|
|
return;
|
|
}
|
|
_spi[bus].initialized = true;
|
|
|
|
DEBUG("%s bus=%u\n", __func__, bus);
|
|
|
|
/* initialize pins */
|
|
spi_init_pins(bus);
|
|
|
|
/* check whether pins could be initialized, otherwise return */
|
|
if (gpio_get_pin_usage(_spi[bus].pin_sck) != _SPI &&
|
|
gpio_get_pin_usage(_spi[bus].pin_miso) != _SPI &&
|
|
gpio_get_pin_usage(_spi[bus].pin_mosi) != _SPI &&
|
|
gpio_get_pin_usage(_spi[bus].pin_cs) != _SPI) {
|
|
return;
|
|
}
|
|
|
|
/* enable (power on) the according SPI module */
|
|
periph_module_enable(_spi[bus].mod);
|
|
|
|
/* bring the bus into a defined state */
|
|
_spi[bus].regs->user.val = SPI_USR_MOSI | SPI_CK_I_EDGE | SPI_DOUTDIN |
|
|
SPI_CS_SETUP | SPI_CS_HOLD;
|
|
|
|
/* set byte order to little endian for read and write operations */
|
|
_spi[bus].regs->user.wr_byte_order = 0;
|
|
_spi[bus].regs->user.rd_byte_order = 0;
|
|
|
|
/* set bit order to most significant first for read and write operations */
|
|
_spi[bus].regs->ctrl.wr_bit_order = 0;
|
|
_spi[bus].regs->ctrl.rd_bit_order = 0;
|
|
|
|
/* reset all DIO or QIO flags */
|
|
_spi[bus].regs->ctrl.fread_qio = 0;
|
|
_spi[bus].regs->ctrl.fread_dio = 0;
|
|
_spi[bus].regs->ctrl.fread_quad = 0;
|
|
_spi[bus].regs->ctrl.fread_dual = 0;
|
|
|
|
/* disable fast read mode and write protection */
|
|
_spi[bus].regs->ctrl.fastrd_mode = 0;
|
|
_spi[bus].regs->ctrl.wp = 0;
|
|
|
|
/* aquire and release to set default parameters */
|
|
spi_acquire(bus, GPIO_UNDEF, SPI_MODE_0, SPI_CLK_1MHZ);
|
|
spi_release(bus);
|
|
}
|
|
|
|
void spi_init_pins(spi_t bus)
|
|
{
|
|
CHECK_SPI_DEV(bus);
|
|
|
|
/* call initialization of the SPI interface if it is not initialized yet */
|
|
if (!_spi[bus].initialized) {
|
|
_spi_init_internal(bus);
|
|
}
|
|
|
|
/* avoid multiple pin initializations */
|
|
if (_spi[bus].pins_initialized) {
|
|
return;
|
|
}
|
|
_spi[bus].pins_initialized = true;
|
|
|
|
DEBUG("%s bus=%u\n", __func__, bus);
|
|
|
|
/* in case of SPI_DEV(2) all pins are already initialized
|
|
as SPI pins */
|
|
if (bus != SPI_DEV(2)) {
|
|
/* if not already initialized as SPI, try to initialize the pins */
|
|
if (gpio_init (_spi[bus].pin_sck, GPIO_OUT) ||
|
|
gpio_init (_spi[bus].pin_mosi, GPIO_OUT) ||
|
|
gpio_init (_spi[bus].pin_miso, GPIO_IN)) {
|
|
LOG_TAG_ERROR("spi",
|
|
"SPI_DEV(%d) pins could not be initialized\n", bus);
|
|
return;
|
|
}
|
|
if (spi_init_cs(bus, _spi[bus].pin_cs) != SPI_OK) {
|
|
LOG_TAG_ERROR("spi",
|
|
"SPI_DEV(%d) CS signal could not be initialized\n",
|
|
bus);
|
|
return;
|
|
}
|
|
/* store the usage type in GPIO table */
|
|
gpio_set_pin_usage(_spi[bus].pin_sck, _SPI);
|
|
gpio_set_pin_usage(_spi[bus].pin_mosi, _SPI);
|
|
gpio_set_pin_usage(_spi[bus].pin_miso, _SPI);
|
|
|
|
/* connect SCK and MOSI pins to the output signal through the GPIO matrix */
|
|
GPIO.func_out_sel_cfg[_spi[bus].pin_sck].func_sel = _spi[bus].signal_sck;
|
|
GPIO.func_out_sel_cfg[_spi[bus].pin_mosi].func_sel = _spi[bus].signal_mosi;
|
|
/* connect MISO input signal to the MISO pin through the GPIO matrix */
|
|
GPIO.func_in_sel_cfg[_spi[bus].signal_miso].sig_in_sel = 1;
|
|
GPIO.func_in_sel_cfg[_spi[bus].signal_miso].sig_in_inv = 0;
|
|
GPIO.func_in_sel_cfg[_spi[bus].signal_miso].func_sel = _spi[bus].pin_miso;
|
|
}
|
|
else {
|
|
LOG_TAG_WARNING("spi", "Using SPI_DEV(2) is dangerous\n");
|
|
}
|
|
}
|
|
|
|
int spi_init_cs(spi_t bus, spi_cs_t cs)
|
|
{
|
|
DEBUG("%s bus=%u cs=%u\n", __func__, bus, cs);
|
|
|
|
CHECK_SPI_DEV_RET(bus, SPI_NODEV);
|
|
|
|
/* call initialization of the SPI interface if it is not initialized yet */
|
|
if (!_spi[bus].initialized) {
|
|
_spi_init_internal(bus);
|
|
}
|
|
|
|
/* return if pin is already initialized as SPI CS signal */
|
|
if (gpio_get_pin_usage(cs) == _SPI) {
|
|
return SPI_OK;
|
|
}
|
|
|
|
/* check whether CS pin is used otherwise */
|
|
if (gpio_get_pin_usage(cs) != _GPIO) {
|
|
return SPI_NOCS;
|
|
}
|
|
|
|
/* initialize the pin */
|
|
gpio_init(cs, GPIO_OUT);
|
|
gpio_set (cs);
|
|
|
|
/* pin cannot be used for anything else */
|
|
gpio_set_pin_usage(cs, _SPI);
|
|
|
|
return SPI_OK;
|
|
}
|
|
|
|
int IRAM_ATTR spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
|
|
{
|
|
DEBUG("%s bus=%u cs=%u mode=%u clk=%u\n", __func__, bus, cs, mode, clk);
|
|
|
|
CHECK_SPI_DEV_RET(bus, SPI_NODEV);
|
|
|
|
/* call initialization of the SPI interface if it is not initialized yet */
|
|
if (!_spi[bus].initialized) {
|
|
_spi_init_internal(bus);
|
|
}
|
|
|
|
/* if parameter cs is GPIO_UNDEF, the default CS pin is used */
|
|
cs = (cs == GPIO_UNDEF) ? _spi[bus].pin_cs : cs;
|
|
|
|
/* if the CS pin used is not yet initialized, we do it now */
|
|
if (gpio_get_pin_usage(cs) != _SPI && spi_init_cs(bus, cs) != SPI_OK) {
|
|
LOG_TAG_ERROR("spi",
|
|
"SPI_DEV(%d) CS signal could not be initialized\n",
|
|
bus);
|
|
return SPI_NOCS;
|
|
}
|
|
|
|
/* lock the bus */
|
|
mutex_lock(&_spi[bus].lock);
|
|
|
|
/* set SPI mode, see Table 25 and Section 7.4.2 in Technical Reference */
|
|
_spi[bus].regs->pin.ck_idle_edge = (mode == SPI_MODE_2 || mode == SPI_MODE_3);
|
|
_spi[bus].regs->user.ck_out_edge = (mode == SPI_MODE_1 || mode == SPI_MODE_2);
|
|
_spi[bus].regs->ctrl2.miso_delay_mode = (mode == SPI_MODE_0 || mode == SPI_MODE_3) ? 2 : 1;
|
|
_spi[bus].regs->ctrl2.miso_delay_num = 0;
|
|
_spi[bus].regs->ctrl2.mosi_delay_mode = 0;
|
|
_spi[bus].regs->ctrl2.mosi_delay_num = 0;
|
|
|
|
/* set SPI clock, see Technical Reference */
|
|
|
|
uint32_t spi_clkdiv_pre;
|
|
uint32_t spi_clkcnt_N;
|
|
|
|
switch (clk) {
|
|
case SPI_CLK_10MHZ: spi_clkdiv_pre = 2; /* predivides 80 MHz to 40 MHz */
|
|
spi_clkcnt_N = 4; /* 4 cycles results into 10 MHz */
|
|
break;
|
|
case SPI_CLK_5MHZ: spi_clkdiv_pre = 2; /* predivides 80 MHz to 40 MHz */
|
|
spi_clkcnt_N = 8; /* 8 cycles results into 5 MHz */
|
|
break;
|
|
case SPI_CLK_1MHZ: spi_clkdiv_pre = 2; /* predivides 80 MHz to 40 MHz */
|
|
spi_clkcnt_N = 40; /* 40 cycles results into 1 MHz */
|
|
break;
|
|
case SPI_CLK_400KHZ: spi_clkdiv_pre = 20; /* predivides 80 MHz to 4 MHz */
|
|
spi_clkcnt_N = 10; /* 10 cycles results into 400 kHz */
|
|
break;
|
|
case SPI_CLK_100KHZ: spi_clkdiv_pre = 20; /* predivides 80 MHz to 4 MHz */
|
|
spi_clkcnt_N = 40; /* 20 cycles results into 100 kHz */
|
|
break;
|
|
default: spi_clkdiv_pre = 20; /* predivides 80 MHz to 4 MHz */
|
|
spi_clkcnt_N = 40; /* 20 cycles results into 100 kHz */
|
|
}
|
|
|
|
/* register values are set to deviders-1 */
|
|
spi_clkdiv_pre--;
|
|
spi_clkcnt_N--;
|
|
|
|
DEBUG("%s spi_clkdiv_prev=%u spi_clkcnt_N=%u\n",
|
|
__func__, spi_clkdiv_pre, spi_clkcnt_N);
|
|
|
|
/* SPI clock is derived from APB clock by dividers */
|
|
_spi[bus].regs->clock.clk_equ_sysclk = 0;
|
|
|
|
/* set SPI clock deviders */
|
|
_spi[bus].regs->clock.clkdiv_pre = spi_clkdiv_pre;
|
|
_spi[bus].regs->clock.clkcnt_n = spi_clkcnt_N;
|
|
_spi[bus].regs->clock.clkcnt_h = (spi_clkcnt_N+1)/2-1;
|
|
_spi[bus].regs->clock.clkcnt_l = spi_clkcnt_N;
|
|
|
|
DEBUG("%s bus %d: SPI_CLOCK_REG=%08x\n",
|
|
__func__, bus, _spi[bus].regs->clock.val);
|
|
|
|
return SPI_OK;
|
|
}
|
|
|
|
void IRAM_ATTR spi_release(spi_t bus)
|
|
{
|
|
CHECK_SPI_DEV(bus);
|
|
|
|
/* release the bus */
|
|
mutex_unlock(&_spi[bus].lock);
|
|
}
|
|
|
|
static const char* _spi_names[] = { "SSPI", "FSPI", "HSPI", "VSPI" };
|
|
|
|
void spi_print_config(void)
|
|
{
|
|
for (unsigned bus = 0; bus < spi_bus_num; bus++) {
|
|
ets_printf("\tSPI_DEV(%d)\t%s ", bus, _spi_names[_spi[bus].controller]);
|
|
ets_printf("sck=%d " , _spi[bus].pin_sck);
|
|
ets_printf("miso=%d ", _spi[bus].pin_miso);
|
|
ets_printf("mosi=%d ", _spi[bus].pin_mosi);
|
|
ets_printf("cs=%d\n" , _spi[bus].pin_cs);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Following functions are from the hardware SPI driver of the esp-open-rtos
|
|
* project.
|
|
*
|
|
* Copyright (c) Ruslan V. Uss, 2016
|
|
* BSD Licensed as described in the file LICENSE
|
|
* https://github.com/SuperHouse/esp-open-rtos/blob/master/LICENSE
|
|
*/
|
|
|
|
inline static void IRAM_ATTR _set_size(uint8_t bus, uint8_t bytes)
|
|
{
|
|
uint32_t bits = ((uint32_t)bytes << 3) - 1;
|
|
|
|
_spi[bus].regs->mosi_dlen.val = bits;
|
|
_spi[bus].regs->miso_dlen.val = bits;
|
|
}
|
|
|
|
inline static void IRAM_ATTR _wait(uint8_t bus)
|
|
{
|
|
/* SPI_CMD_REG.SPI_USR is cleared when operation has been finished */
|
|
while (_spi[bus].regs->cmd.usr) {}
|
|
}
|
|
|
|
inline static void IRAM_ATTR _start(uint8_t bus)
|
|
{
|
|
/* set SPI_CMD_REG.SPI_USR to start an operation */
|
|
_spi[bus].regs->cmd.usr = 1;
|
|
}
|
|
|
|
inline static void IRAM_ATTR _store_data(uint8_t bus, const void *data, size_t len)
|
|
{
|
|
uint8_t words = len / 4;
|
|
uint8_t tail = len % 4;
|
|
|
|
memcpy((void *)_spi[bus].regs->data_buf, data, len - tail);
|
|
|
|
if (!tail) {
|
|
return;
|
|
}
|
|
|
|
uint32_t last = 0;
|
|
uint8_t *offs = (uint8_t *)data + len - tail;
|
|
for (uint8_t i = 0; i < tail; i++) {
|
|
last = last | (offs[i] << (i * 8));
|
|
}
|
|
_spi[bus].regs->data_buf[words] = last;
|
|
}
|
|
|
|
static const uint8_t spi_empty_out[SPI_BLOCK_SIZE] = { 0 };
|
|
|
|
static void IRAM_ATTR _spi_buf_transfer(uint8_t bus, const void *out, void *in, size_t len)
|
|
{
|
|
DEBUG("%s bus=%u out=%p in=%p len=%u\n", __func__, bus, out, in, len);
|
|
|
|
/* transfer one block data */
|
|
_wait(bus);
|
|
_set_size(bus, len);
|
|
_store_data(bus, out ? out : spi_empty_out, len);
|
|
_start(bus);
|
|
_wait(bus);
|
|
if (in) {
|
|
memcpy(in, (void *)_spi[bus].regs->data_buf, len);
|
|
}
|
|
}
|
|
|
|
void IRAM_ATTR spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
|
|
const void *out, void *in, size_t len)
|
|
{
|
|
CHECK_SPI_DEV(bus);
|
|
|
|
DEBUG("%s bus=%u cs=%u cont=%d out=%p in=%p len=%u\n",
|
|
__func__, bus, cs, cont, out, in, len);
|
|
|
|
if (!len) {
|
|
return;
|
|
}
|
|
|
|
#if ENABLE_DEBUG
|
|
if (out) {
|
|
DEBUG("out = ");
|
|
for (size_t i = 0; i < len; i++) {
|
|
DEBUG("%02x ", ((const uint8_t *)out)[i]);
|
|
}
|
|
DEBUG("\n");
|
|
}
|
|
#endif
|
|
|
|
gpio_clear (cs != SPI_CS_UNDEF ? cs : _spi[bus].pin_cs);
|
|
|
|
size_t blocks = len / SPI_BLOCK_SIZE;
|
|
uint8_t tail = len % SPI_BLOCK_SIZE;
|
|
|
|
DEBUG("%s bus=%u cs=%u blocks=%d tail=%d\n",
|
|
__func__, bus, cs, blocks, tail);
|
|
|
|
for (size_t i = 0; i < blocks; i++) {
|
|
_spi_buf_transfer(bus,
|
|
out ? (const uint8_t *)out + i * SPI_BLOCK_SIZE : NULL,
|
|
in ? (uint8_t *)in + i * SPI_BLOCK_SIZE : NULL, SPI_BLOCK_SIZE);
|
|
}
|
|
if (tail) {
|
|
_spi_buf_transfer(bus,
|
|
out ? (const uint8_t *)out + blocks * SPI_BLOCK_SIZE : 0,
|
|
in ? (uint8_t *)in + blocks * SPI_BLOCK_SIZE : NULL, tail);
|
|
}
|
|
if (!cont) {
|
|
gpio_set (cs != SPI_CS_UNDEF ? cs : _spi[bus].pin_cs);
|
|
}
|
|
|
|
#if ENABLE_DEBUG
|
|
if (in) {
|
|
DEBUG("in = ");
|
|
for (size_t i = 0; i < len; i++) {
|
|
DEBUG("%02x ", ((const uint8_t *)in)[i]);
|
|
}
|
|
DEBUG("\n");
|
|
}
|
|
#endif
|
|
}
|