1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/esp32/periph/spi.c
2023-03-27 03:10:56 +02:00

485 lines
14 KiB
C

/*
* Copyright (C) 2022 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 for ESP32 SoCs
*
* The implementation uses the ESP-IDF Low level interface in polling mode
* without DMA.
*
* @TODO
* - transaction interrupts to avoid busy waiting in polling mode
* - DMA transfer
*
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
*/
#include <assert.h>
#include <string.h>
#include "esp_common.h"
#include "log.h"
#include "cpu.h"
#include "gpio_arch.h"
#include "mutex.h"
#include "periph/spi.h"
#include "syscalls.h"
#include "driver/periph_ctrl.h"
#include "esp_attr.h"
#include "esp_rom_gpio.h"
#include "hal/spi_hal.h"
#include "hal/spi_types.h"
#include "soc/rtc.h"
#include "esp_idf_api/gpio.h"
#undef MHZ
#include "macros/units.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/* Ensure that the SPIn_* symbols define SPI_DEV(n) */
#if !defined(SPI0_CTRL) && defined(SPI1_CTRL)
#error "SPI_DEV(1) is used but SPI_DEV(0) is not defined"
#endif
/* SPI bus descriptor structure */
struct _spi_bus_t {
mutex_t lock; /* mutex for each SPI interface */
spi_host_device_t hostid; /* SPI hostid as used by ESP-IDF */
const spi_signal_conn_t *periph; /* SPI peripheral descriptor */
spi_hal_timing_conf_t timing; /* calculated SPI timing parameters */
spi_clk_t clk_last; /* SPI clock speed used last time in Hz */
uint8_t mode_last; /* SPI mode used last time */
bool pins_initialized; /* SPI pins initialized */
};
static struct _spi_bus_t _spi[] = {
#ifdef SPI0_CTRL
{
.pins_initialized = false,
.lock = MUTEX_INIT_LOCKED,
.hostid = spi_config[0].ctrl,
.periph = &spi_periph_signal[spi_config[0].ctrl],
.clk_last = 0,
.mode_last = UINT8_MAX,
},
#endif
#ifdef SPI1_CTRL
{
.pins_initialized = false,
.lock = MUTEX_INIT_LOCKED,
.hostid = spi_config[1].ctrl,
.periph = &spi_periph_signal[spi_config[1].ctrl],
.clk_last = 0,
.mode_last = UINT8_MAX,
},
#endif
};
_Static_assert(SPI_NUMOF == ARRAY_SIZE(_spi),
"Size of bus descriptor table doesn't match SPI_NUMOF");
_Static_assert(SPI_NUMOF <= SPI_NUMOF_MAX,
"Number of defined SPI devices is greater than the number of supported devices");
void IRAM_ATTR spi_init(spi_t bus)
{
DEBUG("%s bus=%u\n", __func__, bus);
assert(bus < SPI_NUMOF_MAX);
assert(bus < SPI_NUMOF);
/* initialize pins */
spi_init_pins(bus);
/* check whether pins could be initialized, otherwise return, CS is not
initialized in spi_init_pins */
if (gpio_get_pin_usage(spi_config[bus].sck) != _SPI &&
gpio_get_pin_usage(spi_config[bus].miso) != _SPI &&
gpio_get_pin_usage(spi_config[bus].mosi) != _SPI &&
gpio_get_pin_usage(spi_config[bus].cs) != _SPI) {
return;
}
/* enable (power on) the according SPI module */
periph_module_enable(_spi[bus].periph->module);
/* initialize SPI peripheral */
spi_ll_master_init(_spi[bus].periph->hw);
/* bring the bus into a defined state (one-line mode) */
spi_ll_master_set_line_mode(_spi[bus].periph->hw, (spi_line_mode_t){ 1, 1, 1 });
spi_ll_set_rx_lsbfirst(_spi[bus].periph->hw, false);
spi_ll_set_tx_lsbfirst(_spi[bus].periph->hw, false);
/* acquire and release to set default parameters */
spi_acquire(bus, GPIO_UNDEF, SPI_MODE_0, SPI_CLK_100KHZ);
spi_release(bus);
return;
}
static int _init_spi_pin(gpio_t pin, gpio_mode_t mode)
{
int res;
if (!gpio_is_valid(pin)) {
return 0;
}
res = gpio_init(pin, mode);
if (res) {
return res;
}
/* store the usage type in GPIO table */
gpio_set_pin_usage(pin, _SPI);
return 0;
}
void spi_init_pins(spi_t bus)
{
assert(bus < SPI_NUMOF);
/* avoid multiple pin initializations */
if (_spi[bus].pins_initialized) {
return;
}
_spi[bus].pins_initialized = true;
DEBUG("%s bus=%u\n", __func__, bus);
if (_init_spi_pin(spi_config[bus].sck, GPIO_OUT) ||
_init_spi_pin(spi_config[bus].mosi, GPIO_OUT) ||
_init_spi_pin(spi_config[bus].miso, GPIO_IN)) {
LOG_TAG_ERROR("spi",
"SPI_DEV(%d) pins could not be initialized\n", bus);
return;
}
if (spi_init_cs(bus, spi_config[bus].cs) != SPI_OK) {
LOG_TAG_ERROR("spi",
"SPI_DEV(%d) CS signal could not be initialized\n",
bus);
return;
}
/* TODO the IO_MUX should be used instead of GPIO matrix routing for
lower delays and higher clock rates whenever possible */
/* connect SCK and MOSI pins to the output signal through the GPIO matrix */
if (gpio_is_valid(spi_config[bus].sck)) {
esp_rom_gpio_connect_out_signal(spi_config[bus].sck,
_spi[bus].periph->spiclk_out, false, false);
}
if (gpio_is_valid(spi_config[bus].mosi)) {
esp_rom_gpio_connect_out_signal(spi_config[bus].mosi,
_spi[bus].periph->spid_out, false, false);
}
/* connect MISO input signal to the MISO pin through the GPIO matrix */
if (gpio_is_valid(spi_config[bus].miso)) {
esp_rom_gpio_connect_in_signal(spi_config[bus].miso,
_spi[bus].periph->spiq_in, false);
}
mutex_unlock(&_spi[bus].lock);
}
int spi_init_cs(spi_t bus, spi_cs_t cs)
{
DEBUG("%s bus=%u cs=%u\n", __func__, bus, cs);
assert(bus < SPI_NUMOF);
if (!gpio_is_valid(cs)) {
return SPI_OK;
}
/* 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;
}
void spi_deinit_pins(spi_t bus)
{
assert(bus < SPI_NUMOF);
/* avoid multiple pin deinitializations */
if (!_spi[bus].pins_initialized) {
return;
}
_spi[bus].pins_initialized = false;
if (gpio_is_valid(spi_config[bus].sck)) {
esp_idf_gpio_reset_pin(spi_config[bus].sck);
gpio_set_pin_usage(spi_config[bus].sck, _GPIO);
}
if (gpio_is_valid(spi_config[bus].mosi)) {
esp_idf_gpio_reset_pin(spi_config[bus].mosi);
gpio_set_pin_usage(spi_config[bus].mosi, _GPIO);
}
if (gpio_is_valid(spi_config[bus].miso)) {
esp_idf_gpio_reset_pin(spi_config[bus].miso);
gpio_set_pin_usage(spi_config[bus].miso, _GPIO);
}
if (gpio_is_valid(spi_config[bus].cs)) {
esp_idf_gpio_reset_pin(spi_config[bus].cs);
gpio_set_pin_usage(spi_config[bus].cs, _GPIO);
}
mutex_lock(&_spi[bus].lock);
}
void 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);
assert(bus < SPI_NUMOF);
/* if parameter cs is GPIO_UNDEF, the default CS pin is used */
cs = (cs == GPIO_UNDEF) ? spi_config[bus].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);
assert(0);
}
/* lock the bus */
mutex_lock(&_spi[bus].lock);
/*
* set SPI mode
* see ESP32 Technical Reference, Section 7.4.1, Table 27
* https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
*/
/* hardware CS not used (TODO) */
spi_ll_master_select_cs(_spi[bus].periph->hw, INT_MAX);
spi_ll_master_set_cs_setup(_spi[bus].periph->hw, 2);
spi_ll_master_set_mode(_spi[bus].periph->hw, mode);
spi_ll_set_half_duplex(_spi[bus].periph->hw, false);
int delay_mode = (mode == SPI_MODE_0 || mode == SPI_MODE_3) ? 2 : 1;
spi_ll_set_miso_delay(_spi[bus].periph->hw, delay_mode, 0);
spi_ll_set_mosi_delay(_spi[bus].periph->hw, 0, 0);
/*
* set SPI clock
* see ESP32 Technical Reference, Section 7.8 SPI_CLOCK_REG
* https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
*/
/* check whether timing has to be recalculated (time consuming) */
if (clk != _spi[bus].clk_last) {
uint32_t apb_clk = rtc_clk_apb_freq_get();
uint32_t clk_reg;
if (apb_clk / 5 < clk) {
LOG_TAG_ERROR("spi", "APB clock rate (%"PRIu32" Hz) has to be at "
"least 5 times SPI clock rate (%d Hz)\n",
apb_clk, clk);
assert(false);
}
/* duty cycle is measured in is 1/256th, 50% = 128 */
int _clk = spi_ll_master_cal_clock(apb_clk, clk,
128, &clk_reg);
_spi[bus].clk_last = clk;
_spi[bus].timing.clock_reg = clk_reg;
_spi[bus].timing.timing_miso_delay = 0;
_spi[bus].timing.timing_dummy = 0;
DEBUG("%s bus %d: SPI clock frequency: clk=%d eff=%d "
"reg=%08"PRIx32"\n",
__func__, bus, clk, _clk, clk_reg);
}
spi_ll_master_set_clock_by_reg(_spi[bus].periph->hw,
&_spi[bus].timing.clock_reg);
#if defined(CPU_FAM_ESP32C3) || defined(CPU_FAM_ESP32S3)
/*
* If the SPI mode has been changed, the clock signal is only set to the
* correct level at the beginning of the transfer on the ESP32x3. However,
* if a generic GPIO is used as CS signal instead of the hardware CS,
* the CS signal is already LOW at this time. Thus, the clock signal will
* have the wrong level when the SPI mode is changed and the CS signal
* becomes LOW.
* The following is a workaround by receiving a dummy byte without pulling
* the CS signal LOW when the mode has been changed.
*/
if (_spi[bus].mode_last != mode) {
uint8_t temp = 0xff;
spi_transfer_bytes(bus, GPIO_UNDEF, false, &temp, &temp, 1);
_spi[bus].mode_last = mode;
}
#elif defined(CPU_FAM_ESP32) || defined(CPU_FAM_ESP32S2)
/* This workaround isn't needed on ESP32 and ESP32-S2 */
#else
#error Platform implementation is missing
#endif
}
void IRAM_ATTR spi_release(spi_t bus)
{
DEBUG("%s bus=%u\n", __func__, bus);
assert(bus < SPI_NUMOF);
/* release the bus */
mutex_unlock(&_spi[bus].lock);
}
#if defined(CPU_FAM_ESP32)
static const char* _spi_names[] = { "CSPI/FSPI", "HSPI", "VSPI" };
#elif defined(CPU_FAM_ESP32C3)
static const char* _spi_names[] = { "SPI", "FSPI" };
#elif defined(CPU_FAM_ESP32S2)
static const char* _spi_names[] = { "SPI", "FSPI", "HSPI" };
#elif defined(CPU_FAM_ESP32S3)
static const char* _spi_names[] = { "SPI", "FSPI", "SPI" };
#else
#error Platform implementation required
#endif
_Static_assert(ARRAY_SIZE(_spi_names) == SOC_SPI_PERIPH_NUM,
"Number of _spi_names doesn't match SOC_SPI_PERIPH_NUM");
void spi_print_config(void)
{
for (unsigned bus = 0; bus < SPI_NUMOF; bus++) {
printf("\tSPI_DEV(%u)\t%s ", bus, _spi_names[_spi[bus].hostid]);
printf("sck=%d ", spi_config[bus].sck);
printf("miso=%d ", spi_config[bus].miso);
printf("mosi=%d ", spi_config[bus].mosi);
printf("cs0=%d\n", spi_config[bus].cs);
}
}
static const uint8_t _spi_empty_out[SOC_SPI_MAXIMUM_BUFFER_SIZE] = { 0 };
static void IRAM_ATTR _spi_transfer(uint8_t bus,
const void *out, void *in, size_t len)
{
/* transfer one block with a maximum size of SOC_SPI_MAXIMUM_BUFFER_SIZE */
DEBUG("%s bus=%u out=%p in=%p len=%u\n", __func__, bus, out, in, len);
/* wait until an existing transfer is finished */
while (spi_ll_get_running_cmd(_spi[bus].periph->hw)) {}
/* prepare the transfer */
spi_ll_set_half_duplex(_spi[bus].periph->hw, false);
spi_ll_set_command_bitlen(_spi[bus].periph->hw, 0);
spi_ll_set_addr_bitlen(_spi[bus].periph->hw, 0);
spi_ll_set_mosi_bitlen(_spi[bus].periph->hw, (uint32_t)len << 3);
spi_ll_set_miso_bitlen(_spi[bus].periph->hw, (uint32_t)len << 3);
spi_ll_enable_mosi(_spi[bus].periph->hw, 1);
/* write output data to the buffer of the SPI controller */
spi_ll_write_buffer(_spi[bus].periph->hw, out ? out : _spi_empty_out, len << 3);
/* start the transfer */
spi_ll_master_user_start(_spi[bus].periph->hw);
/* wait until the transfer is finished */
while (spi_ll_get_running_cmd(_spi[bus].periph->hw)) {}
/* read input data from the buffer of the SPI controller */
if (in) {
spi_ll_read_buffer(_spi[bus].periph->hw, in, len << 3);
}
}
void IRAM_ATTR spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
const void *out, void *in, size_t len)
{
assert(bus < SPI_NUMOF);
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 (IS_ACTIVE(ENABLE_DEBUG)) {
if (out) {
DEBUG("out = ");
for (size_t i = 0; i < len; i++) {
DEBUG("%02x ", ((const uint8_t *)out)[i]);
}
DEBUG("\n");
}
}
if (cs != SPI_CS_UNDEF) {
gpio_clear(cs);
}
const uint8_t *blk_out = out;
uint8_t *blk_in = in;
size_t idx = 0;
while (idx < len) {
/* maximum non-DMA transfer size is SOC_SPI_MAXIMUM_BUFFER_SIZE */
size_t blk_len = MIN(len - idx, SOC_SPI_MAXIMUM_BUFFER_SIZE);
_spi_transfer(bus, blk_out, blk_in, blk_len);
blk_out = (out) ? blk_out + blk_len : NULL;
blk_in = (in) ? blk_in + blk_len : NULL;
idx += blk_len;
system_wdt_feed();
}
if (!cont && (cs != SPI_CS_UNDEF)) {
gpio_set (cs);
}
if (IS_ACTIVE(ENABLE_DEBUG)) {
if (in) {
DEBUG("in = ");
for (size_t i = 0; i < len; i++) {
DEBUG("%02x ", ((const uint8_t *)in)[i]);
}
DEBUG("\n");
}
}
}