Merge branch 'feat/spi_slave_hd_esp32s2' into 'master'

spi_slave_hd: new driver for spi slave in half duplex mode

Closes IDF-1589

See merge request espressif/esp-idf!8386
This commit is contained in:
Michael (XIAO Xufeng) 2020-07-11 02:21:14 +08:00
commit 9d21b17384
38 changed files with 2694 additions and 133 deletions

View File

@ -40,7 +40,9 @@ if(IDF_TARGET STREQUAL "esp32s2")
list(APPEND srcs "esp32s2/rtc_tempsensor.c"
"esp32s2/touch_sensor.c"
"esp32s2/adc.c"
"esp32s2/adc2_init_cal.c")
"esp32s2/adc2_init_cal.c"
"spi_slave_hd.c"
)
# currently only S2 beta has its own target-specific includes
list(APPEND includes "esp32s2/include")
endif()

View File

@ -2,6 +2,7 @@
# Component Makefile
#
COMPONENT_SRCDIRS := . $(IDF_TARGET)
COMPONENT_OBJEXCLUDE += spi_slave_hd.o
COMPONENT_ADD_INCLUDEDIRS := include $(IDF_TARGET)/include $(IDF_TARGET)/include/driver

View File

@ -62,11 +62,12 @@ extern "C"
#define SPICOMMON_BUSFLAG_SLAVE 0 ///< Initialize I/O in slave mode
#define SPICOMMON_BUSFLAG_MASTER (1<<0) ///< Initialize I/O in master mode
#define SPICOMMON_BUSFLAG_IOMUX_PINS (1<<1) ///< Check using iomux pins. Or indicates the pins are configured through the IO mux rather than GPIO matrix.
#define SPICOMMON_BUSFLAG_SCLK (1<<2) ///< Check existing of SCLK pin. Or indicates CLK line initialized.
#define SPICOMMON_BUSFLAG_MISO (1<<3) ///< Check existing of MISO pin. Or indicates MISO line initialized.
#define SPICOMMON_BUSFLAG_MOSI (1<<4) ///< Check existing of MOSI pin. Or indicates CLK line initialized.
#define SPICOMMON_BUSFLAG_DUAL (1<<5) ///< Check MOSI and MISO pins can output. Or indicates bus able to work under DIO mode.
#define SPICOMMON_BUSFLAG_WPHD (1<<6) ///< Check existing of WP and HD pins. Or indicates WP & HD pins initialized.
#define SPICOMMON_BUSFLAG_GPIO_PINS (1<<2) ///< Force the signals to be routed through GPIO matrix. Or indicates the pins are routed through the GPIO matrix.
#define SPICOMMON_BUSFLAG_SCLK (1<<3) ///< Check existing of SCLK pin. Or indicates CLK line initialized.
#define SPICOMMON_BUSFLAG_MISO (1<<4) ///< Check existing of MISO pin. Or indicates MISO line initialized.
#define SPICOMMON_BUSFLAG_MOSI (1<<5) ///< Check existing of MOSI pin. Or indicates MOSI line initialized.
#define SPICOMMON_BUSFLAG_DUAL (1<<6) ///< Check MOSI and MISO pins can output. Or indicates bus able to work under DIO mode.
#define SPICOMMON_BUSFLAG_WPHD (1<<7) ///< Check existing of WP and HD pins. Or indicates WP & HD pins initialized.
#define SPICOMMON_BUSFLAG_QUAD (SPICOMMON_BUSFLAG_DUAL|SPICOMMON_BUSFLAG_WPHD) ///< Check existing of MOSI/MISO/WP/HD pins as output. Or indicates bus able to work under QIO mode.
#define SPICOMMON_BUSFLAG_NATIVE_PINS SPICOMMON_BUSFLAG_IOMUX_PINS

View File

@ -0,0 +1,168 @@
// Copyright 2010-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_types.h"
#include "soc/spi_caps.h"
#include "freertos/FreeRTOS.h"
#include "hal/spi_types.h"
#include "driver/spi_common.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C"
{
#endif
#if !SOC_SPI_SUPPORT_SLAVE_HD_VER2 && !CI_HEADER_CHECK
#error The SPI peripheral does not support this feature
#endif
/// Descriptor of data to send/receive
typedef struct {
uint8_t* data; ///< Buffer to send, must be DMA capable
size_t len; ///< Len of data to send/receive. For receiving the buffer length should be multiples of 4 bytes, otherwise the extra part will be truncated.
size_t trans_len; ///< Data actually received
void* arg; ///< Extra argument indiciating this data
} spi_slave_hd_data_t;
/// Information of SPI Slave HD event
typedef struct {
spi_event_t event; ///< Event type
spi_slave_hd_data_t* trans; ///< Corresponding transaction for SPI_EV_SEND and SPI_EV_RECV events
} spi_slave_hd_event_t;
/// Callback for SPI Slave HD
typedef bool (*slave_cb_t)(void* arg, spi_slave_hd_event_t* event, BaseType_t* awoken);
/// Channel of SPI Slave HD to do data transaction
typedef enum {
SPI_SLAVE_CHAN_TX = 0, ///< The output channel (RDDMA)
SPI_SLAVE_CHAN_RX = 1, ///< The input channel (WRDMA)
} spi_slave_chan_t;
/// Callback configuration structure for SPI Slave HD
typedef struct {
slave_cb_t cb_recv; ///< Callback when receive data
slave_cb_t cb_sent; ///< Callback when data sent
slave_cb_t cb_buffer_tx; ///< Callback when master reads from shared buffer
slave_cb_t cb_buffer_rx; ///< Callback when master writes to shared buffer
slave_cb_t cb_cmd9; ///< Callback when CMD9 received
slave_cb_t cb_cmdA; ///< Callback when CMDA received
void* arg; ///< Argument indicating this SPI Slave HD peripheral instance
} spi_slave_hd_callback_config_t;
/// Configuration structure for the SPI Slave HD driver
typedef struct {
int spics_io_num; ///< CS GPIO pin for this device
uint32_t flags; ///< Bitwise OR of SPI_SLAVE_HD_* flags
#define SPI_SLAVE_HD_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default MSB first
#define SPI_SLAVE_HD_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first
#define SPI_SLAVE_HD_BIT_LSBFIRST (SPI_SLAVE_HD_TXBIT_LSBFIRST|SPI_SLAVE_HD_RXBIT_LSBFIRST) ///< Transmit and receive LSB first
uint8_t mode; ///< SPI mode (0-3)
int command_bits; ///< command field bits, multiples of 8 and at least 8.
int address_bits; ///< address field bits, multiples of 8 and at least 8.
int dummy_bits; ///< dummy field bits, multiples of 8 and at least 8.
int queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_slave_hd_queue_trans but not yet finished using spi_slave_hd_get_trans_result) at the same time
int dma_chan; ///< DMA channel used
spi_slave_hd_callback_config_t cb_config; ///< Callback configuration
} spi_slave_hd_slot_config_t;
/**
* @brief Initialize the SPI Slave HD driver.
*
* @param host_id The host to use
* @param bus_config Bus configuration for the bus used
* @param config Configuration for the SPI Slave HD driver
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_ARG: invalid argument given
* - ESP_ERR_INVALID_STATE: function called in invalid state, may be some resources are already in use
* - ESP_ERR_NO_MEM: memory allocation failed
* - or other return value from `esp_intr_alloc`
*/
esp_err_t spi_slave_hd_init(spi_host_device_t host_id, const spi_bus_config_t *bus_config,
const spi_slave_hd_slot_config_t *config);
/**
* @brief Deinitialize the SPI Slave HD driver
*
* @param host_id The host to deinitialize the driver
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_ARG: if the host_id is not correct
*/
esp_err_t spi_slave_hd_deinit(spi_host_device_t host_id);
/**
* @brief Queue data transaction
*
* @param host_id Host to queue the transaction
* @param chan Channel to queue the data, SPI_SLAVE_CHAN_TX or SPI_SLAVE_CHAN_RX
* @param trans Descriptor of data to queue
* @param timeout Timeout before the data is queued
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_ARG: The input argument is invalid. Can be the following reason:
* - The buffer given is not DMA capable
* - The length of data is invalid (not larger than 0, or exceed the max transfer length)
* - The function is invalid
* - ESP_ERR_TIMEOUT: Cannot queue the data before timeout. This is quite possible if the master
* doesn't read/write the slave on time.
*/
esp_err_t spi_slave_hd_queue_trans(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t* trans, TickType_t timeout);
/**
* @brief Get the result of a data transaction
*
* @param host_id Host to queue the transaction
* @param chan Channel to get the result, SPI_SLAVE_CHAN_TX or SPI_SLAVE_CHAN_RX
* @param[out] out_trans Output descriptor of the returned transaction
* @param timeout Timeout before the result is got
* @return
* - ESP_OK: on success
* - ESP_ERR_INVALID_ARG: Function is not valid
* - ESP_ERR_TIMEOUT: There's no transaction done before timeout
*/
esp_err_t spi_slave_hd_get_trans_res(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t** out_trans, TickType_t timeout);
/**
* @brief Read the shared registers
*
* @param host_id Host to read the shared registers
* @param addr Address of register to read, 0 to ``SOC_SPI_MAXIMUM_BUFFER_SIZE-1``
* @param[out] out_data Output buffer to store the read data
* @param len Length to read, not larger than ``SOC_SPI_MAXIMUM_BUFFER_SIZE-addr``
*/
void spi_slave_hd_read_buffer(spi_host_device_t host_id, int addr, uint8_t *out_data, size_t len);
/**
* @brief Write the shared registers
*
* @param host_id Host to write the shared registers
* @param addr Address of register to write, 0 to ``SOC_SPI_MAXIMUM_BUFFER_SIZE-1``
* @param data Buffer holding the data to write
* @param len Length to write, ``SOC_SPI_MAXIMUM_BUFFER_SIZE-addr``
*/
void spi_slave_hd_write_buffer(spi_host_device_t host_id, int addr, uint8_t *data, size_t len);
#ifdef __cplusplus
}
#endif

View File

@ -268,8 +268,12 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
}
//check if the selected pins correspond to the iomux pins of the peripheral
bool use_iomux = bus_uses_iomux_pins(host, bus_config);
if (use_iomux) temp_flag |= SPICOMMON_BUSFLAG_IOMUX_PINS;
bool use_iomux = !(flags & SPICOMMON_BUSFLAG_GPIO_PINS) && bus_uses_iomux_pins(host, bus_config);
if (use_iomux) {
temp_flag |= SPICOMMON_BUSFLAG_IOMUX_PINS;
} else {
temp_flag |= SPICOMMON_BUSFLAG_GPIO_PINS;
}
uint32_t missing_flag = flags & ~temp_flag;
missing_flag &= ~SPICOMMON_BUSFLAG_MASTER;//don't check this flag

View File

@ -0,0 +1,377 @@
// Copyright 2010-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "driver/gpio.h"
#include "driver/spi_common_internal.h"
#include "hal/spi_slave_hd_hal.h"
#include "driver/spi_slave_hd.h"
//SPI1 can never be used as the slave
#define VALID_HOST(x) (x>SPI_HOST && x<=HSPI_HOST)
#define SPIHD_CHECK(cond,warn,ret) do{if(!(cond)){ESP_LOGE(TAG, warn); return ret;}} while(0)
typedef struct {
spi_slave_hd_hal_context_t hal;
int dma_chan;
intr_handle_t intr;
intr_handle_t intr_dma;
spi_slave_hd_callback_config_t callback;
QueueHandle_t tx_trans_queue;
QueueHandle_t tx_ret_queue;
QueueHandle_t rx_trans_queue;
QueueHandle_t rx_ret_queue;
spi_slave_hd_data_t* tx_desc;
spi_slave_hd_data_t* rx_desc;
uint32_t flags;
int max_transfer_sz;
portMUX_TYPE int_spinlock;
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock;
#endif
} spi_slave_hd_slot_t;
static spi_slave_hd_slot_t *spihost[SOC_SPI_PERIPH_NUM];
static const char TAG[] = "slave_hd";
static void spi_slave_hd_intr(void* arg);
esp_err_t spi_slave_hd_init(spi_host_device_t host_id, const spi_bus_config_t *bus_config,
const spi_slave_hd_slot_config_t *config)
{
bool spi_chan_claimed, dma_chan_claimed;
esp_err_t ret = ESP_OK;
SPIHD_CHECK(VALID_HOST(host_id), "invalid host", ESP_ERR_INVALID_ARG);
SPIHD_CHECK(config->dma_chan == 0 || config->dma_chan == host_id, "invalid dma channel", ESP_ERR_INVALID_ARG);
spi_chan_claimed = spicommon_periph_claim(host_id, "slave_hd");
SPIHD_CHECK(spi_chan_claimed, "host already in use", ESP_ERR_INVALID_STATE);
if ( config->dma_chan != 0 ) {
dma_chan_claimed = spicommon_dma_chan_claim(config->dma_chan);
if (!dma_chan_claimed) {
spicommon_periph_free(host_id);
SPIHD_CHECK(dma_chan_claimed, "dma channel already in use", ESP_ERR_INVALID_STATE);
}
}
spi_slave_hd_slot_t* host = malloc(sizeof(spi_slave_hd_slot_t));
if (host == NULL) {
ret = ESP_ERR_NO_MEM;
goto cleanup;
}
spihost[host_id] = host;
memset(host, 0, sizeof(spi_slave_hd_slot_t));
host->dma_chan = config->dma_chan;
host->int_spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
ret = spicommon_bus_initialize_io(host_id, bus_config, config->dma_chan,
SPICOMMON_BUSFLAG_SLAVE | bus_config->flags, &host->flags);
if (ret != ESP_OK) {
goto cleanup;
}
gpio_set_direction(config->spics_io_num, GPIO_MODE_INPUT);
spicommon_cs_initialize(host_id, config->spics_io_num, 0,
!(bus_config->flags & SPICOMMON_BUSFLAG_NATIVE_PINS));
host->dma_chan = config->dma_chan;
spi_slave_hd_hal_config_t hal_config = {
.host_id = host_id,
.tx_lsbfirst = (config->flags & SPI_SLAVE_HD_RXBIT_LSBFIRST),
.rx_lsbfirst = (config->flags & SPI_SLAVE_HD_TXBIT_LSBFIRST),
.dma_chan = config->dma_chan,
.mode = config->mode,
};
slave_hd_hal_init(&host->hal, &hal_config);
if (config->dma_chan != 0) {
//See how many dma descriptors we need and allocate them
int dma_desc_ct = (bus_config->max_transfer_sz + SPI_MAX_DMA_LEN - 1) / SPI_MAX_DMA_LEN;
if (dma_desc_ct == 0) dma_desc_ct = 1; //default to 4k when max is not given
host->max_transfer_sz = dma_desc_ct * SPI_MAX_DMA_LEN;
host->hal.dmadesc_tx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
host->hal.dmadesc_rx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
if (!host->hal.dmadesc_tx || !host->hal.dmadesc_rx ) {
ret = ESP_ERR_NO_MEM;
goto cleanup;
}
} else {
//We're limited to non-DMA transfers: the SPI work registers can hold 64 bytes at most.
host->max_transfer_sz = 0;
}
#ifdef CONFIG_PM_ENABLE
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "spi_slave", &host->pm_lock);
if (ret != ESP_OK) {
goto cleanup;
}
// Lock APB frequency while SPI slave driver is in use
esp_pm_lock_acquire(host->pm_lock);
#endif //CONFIG_PM_ENABLE
//Create queues
host->tx_trans_queue = xQueueCreate(config->queue_size, sizeof(spi_slave_hd_data_t *));
host->tx_ret_queue = xQueueCreate(config->queue_size, sizeof(spi_slave_hd_data_t *));
host->rx_trans_queue = xQueueCreate(config->queue_size, sizeof(spi_slave_hd_data_t *));
host->rx_ret_queue = xQueueCreate(config->queue_size, sizeof(spi_slave_hd_data_t *));
if (!host->tx_trans_queue || !host->tx_ret_queue ||
!host->rx_trans_queue || !host->rx_ret_queue) {
ret = ESP_ERR_NO_MEM;
goto cleanup;
}
ret = esp_intr_alloc(spicommon_irqsource_for_host(host_id), 0, spi_slave_hd_intr,
(void *)host, &host->intr);
if (ret != ESP_OK) {
goto cleanup;
}
ret = esp_intr_alloc(spicommon_irqdma_source_for_host(host_id), 0, spi_slave_hd_intr,
(void *)host, &host->intr_dma);
if (ret != ESP_OK) {
goto cleanup;
}
memcpy((uint8_t*)&host->callback, (uint8_t*)&config->cb_config, sizeof(spi_slave_hd_callback_config_t));
spi_event_t event = 0;
if (host->callback.cb_buffer_tx!=NULL) event |= SPI_EV_BUF_TX;
if (host->callback.cb_buffer_rx!=NULL) event |= SPI_EV_BUF_RX;
if (host->callback.cb_cmd9!=NULL) event |= SPI_EV_CMD9;
if (host->callback.cb_cmdA!=NULL) event |= SPI_EV_CMDA;
spi_slave_hd_hal_enable_event_intr(&host->hal, event);
return ESP_OK;
cleanup:
// Memory free is in the deinit function
spi_slave_hd_deinit(host_id);
return ret;
}
esp_err_t spi_slave_hd_deinit(spi_host_device_t host_id)
{
spi_slave_hd_slot_t *host = spihost[host_id];
if (host == NULL) return ESP_ERR_INVALID_ARG;
if (host->tx_trans_queue) vQueueDelete(host->tx_trans_queue);
if (host->tx_ret_queue) vQueueDelete(host->tx_ret_queue);
if (host->rx_trans_queue) vQueueDelete(host->rx_trans_queue);
if (host->rx_ret_queue) vQueueDelete(host->rx_ret_queue);
if (host) {
free(host->hal.dmadesc_tx);
free(host->hal.dmadesc_rx);
esp_intr_free(host->intr);
esp_intr_free(host->intr_dma);
#ifdef CONFIG_PM_ENABLE
if (host->pm_lock) {
esp_pm_lock_release(host->pm_lock);
esp_pm_lock_delete(host->pm_lock);
}
#endif
}
spicommon_periph_free(host_id);
if (host->dma_chan) {
spicommon_dma_chan_free(host->dma_chan);
}
free(host);
spihost[host_id] = NULL;
return ESP_OK;
}
static void tx_invoke(spi_slave_hd_slot_t* host)
{
portENTER_CRITICAL(&host->int_spinlock);
spi_slave_hd_hal_invoke_event_intr(&host->hal, SPI_EV_SEND);
portEXIT_CRITICAL(&host->int_spinlock);
}
static void rx_invoke(spi_slave_hd_slot_t* host)
{
portENTER_CRITICAL(&host->int_spinlock);
spi_slave_hd_hal_invoke_event_intr(&host->hal, SPI_EV_RECV);
portEXIT_CRITICAL(&host->int_spinlock);
}
static inline IRAM_ATTR BaseType_t intr_check_clear_callback(spi_slave_hd_slot_t* host, spi_event_t ev, slave_cb_t cb)
{
BaseType_t cb_awoken = pdFALSE;
if (spi_slave_hd_hal_check_clear_event(&host->hal, ev)) {
spi_slave_hd_event_t event = {.event = ev};
cb(host->callback.arg, &event, &cb_awoken);
}
return cb_awoken;
}
static IRAM_ATTR void spi_slave_hd_intr(void* arg)
{
spi_slave_hd_slot_t* host = (spi_slave_hd_slot_t*)arg;
BaseType_t awoken = pdFALSE;
spi_slave_hd_callback_config_t *callback = &host->callback;
ESP_EARLY_LOGV("spi_hd", "intr.");
awoken |= intr_check_clear_callback(host, SPI_EV_BUF_TX, callback->cb_buffer_tx);
awoken |= intr_check_clear_callback(host, SPI_EV_BUF_RX, callback->cb_buffer_rx);
awoken |= intr_check_clear_callback(host, SPI_EV_CMD9, callback->cb_cmd9);
awoken |= intr_check_clear_callback(host, SPI_EV_CMDA, callback->cb_cmdA);
BaseType_t ret;
bool tx_done = false;
bool rx_done = false;
portENTER_CRITICAL_ISR(&host->int_spinlock);
if (host->tx_desc && spi_slave_hd_hal_check_disable_event(&host->hal, SPI_EV_SEND)) {
tx_done = true;
}
if (host->rx_desc && spi_slave_hd_hal_check_disable_event(&host->hal, SPI_EV_RECV)) {
rx_done = true;
}
portEXIT_CRITICAL_ISR(&host->int_spinlock);
if (tx_done) {
bool ret_queue = true;
if (callback->cb_sent) {
spi_slave_hd_event_t ev = {
.event = SPI_EV_SEND,
.trans = host->tx_desc,
};
BaseType_t cb_awoken = pdFALSE;
ret_queue = callback->cb_sent(callback->arg, &ev, &cb_awoken);
awoken |= cb_awoken;
}
if (ret_queue) {
ret = xQueueSendFromISR(host->tx_ret_queue, &host->tx_desc, &awoken);
// The return queue is full. All the data remian in send_queue + ret_queue should not be more than the queue length.
assert(ret == pdTRUE);
}
host->tx_desc = NULL;
}
if (rx_done) {
bool ret_queue = true;
host->rx_desc->trans_len = spi_slave_hd_hal_rxdma_get_len(&host->hal);
if (callback->cb_recv) {
spi_slave_hd_event_t ev = {
.event = SPI_EV_RECV,
.trans = host->rx_desc,
};
BaseType_t cb_awoken = pdFALSE;
ret_queue = callback->cb_recv(callback->arg, &ev, &cb_awoken);
awoken |= cb_awoken;
}
if (ret_queue) {
ret = xQueueSendFromISR(host->rx_ret_queue, &host->rx_desc, &awoken);
// The return queue is full. All the data remian in send_queue + ret_queue should not be more than the queue length.
assert(ret == pdTRUE);
}
host->rx_desc = NULL;
}
bool tx_sent = false;
bool rx_sent = false;
if (!host->tx_desc) {
ret = xQueueReceiveFromISR(host->tx_trans_queue, &host->tx_desc, &awoken);
if (ret == pdTRUE) {
spi_slave_hd_hal_txdma(&host->hal, host->tx_desc->data, host->tx_desc->len);
tx_sent = true;
}
}
if (!host->rx_desc) {
ret = xQueueReceiveFromISR(host->rx_trans_queue, &host->rx_desc, &awoken);
if (ret == pdTRUE) {
spi_slave_hd_hal_rxdma(&host->hal, host->rx_desc->data, host->rx_desc->len);
rx_sent = true;
}
}
portENTER_CRITICAL_ISR(&host->int_spinlock);
if (rx_sent) {
spi_slave_hd_hal_enable_event_intr(&host->hal, SPI_EV_RECV);
}
if (tx_sent) {
spi_slave_hd_hal_enable_event_intr(&host->hal, SPI_EV_SEND);
}
portEXIT_CRITICAL_ISR(&host->int_spinlock);
if (awoken==pdTRUE) portYIELD_FROM_ISR();
}
esp_err_t spi_slave_hd_queue_trans(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t* trans, TickType_t timeout)
{
spi_slave_hd_slot_t* host = spihost[host_id];
SPIHD_CHECK(esp_ptr_dma_capable(trans->data), "The buffer should be DMA capable.", ESP_ERR_INVALID_ARG);
SPIHD_CHECK(trans->len <= host->max_transfer_sz && trans->len > 0, "Invalid buffer size", ESP_ERR_INVALID_ARG);
SPIHD_CHECK(chan == SPI_SLAVE_CHAN_TX || chan == SPI_SLAVE_CHAN_RX, "Invalid channel", ESP_ERR_INVALID_ARG);
if (chan == SPI_SLAVE_CHAN_TX) {
BaseType_t ret = xQueueSend(host->tx_trans_queue, &trans, timeout);
if (ret == pdFALSE) {
return ESP_ERR_TIMEOUT;
}
tx_invoke(host);
} else { //chan == SPI_SLAVE_CHAN_RX
BaseType_t ret = xQueueSend(host->rx_trans_queue, &trans, timeout);
if (ret == pdFALSE) {
return ESP_ERR_TIMEOUT;
}
rx_invoke(host);
}
return ESP_OK;
}
esp_err_t spi_slave_hd_get_trans_res(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t** out_trans, TickType_t timeout)
{
SPIHD_CHECK(chan == SPI_SLAVE_CHAN_TX || chan == SPI_SLAVE_CHAN_RX, "Invalid channel", ESP_ERR_INVALID_ARG);
spi_slave_hd_slot_t* host = spihost[host_id];
BaseType_t ret;
spi_slave_hd_data_t *data;
if (chan == SPI_SLAVE_CHAN_TX) {
ret = xQueueReceive(host->tx_ret_queue, &data, timeout);
} else { // chan == SPI_SLAVE_CHAN_RX
ret = xQueueReceive(host->rx_ret_queue, &data, timeout);
}
if (ret == pdFALSE) {
return ESP_ERR_TIMEOUT;
}
*out_trans = data;
return ESP_OK;
}
void spi_slave_hd_read_buffer(spi_host_device_t host_id, int addr, uint8_t *out_data, size_t len)
{
spi_slave_hd_hal_read_buffer(&spihost[host_id]->hal, addr, out_data, len);
}
void spi_slave_hd_write_buffer(spi_host_device_t host_id, int addr, uint8_t *data, size_t len)
{
spi_slave_hd_hal_write_buffer(&spihost[host_id]->hal, addr, data, len);
}

View File

@ -11,6 +11,7 @@
#include <string.h>
#include "param_test.h"
#include "soc/io_mux_reg.h"
#include "sdkconfig.h"
// All the tests using the header should use this definition as much as possible,
// so that the working host can be changed easily in the future.
@ -64,8 +65,8 @@
#define SLAVE_PIN_NUM_MOSI HSPI_IOMUX_PIN_NUM_MOSI
#define SLAVE_PIN_NUM_CLK HSPI_IOMUX_PIN_NUM_CLK
#define SLAVE_PIN_NUM_CS HSPI_IOMUX_PIN_NUM_CS
#define SLAVE_PIN_NUM_WP HSPI_IOMUX_PIN_NUM_WP
#define SLAVE_PIN_NUM_HD HSPI_IOMUX_PIN_NUM_HD
#define SLAVE_PIN_NUM_WP -1
#define SLAVE_PIN_NUM_HD -1
#define SLAVE_IOMUX_PIN_MISO -1
#define SLAVE_IOMUX_PIN_MOSI -1
@ -237,6 +238,16 @@ void spitest_slave_print_data(slave_rxdata_t *t, bool print_rxdata);
// Check whether master and slave data match
esp_err_t spitest_check_data(int len, spi_transaction_t *master_t, slave_rxdata_t *slave_t, bool check_master_data, bool check_slave_len, bool check_slave_data);
#define spitest_cmp_or_dump(expected, actual, len) ({\
int r = memcmp(expected, actual, len);\
if (r != 0) {\
ESP_LOG_BUFFER_HEXDUMP("expected", expected, len, ESP_LOG_INFO);\
ESP_LOG_BUFFER_HEXDUMP("actual", actual, len, ESP_LOG_WARN);\
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, len);\
}\
r;\
})
static inline int get_trans_len(spi_dup_t dup, spi_transaction_t *master_t)
{
if (dup!=HALF_DUPLEX_MISO) {

View File

@ -350,7 +350,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test 6 output pins...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_QUAD;
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_QUAD | SPICOMMON_BUSFLAG_GPIO_PINS;
//swap MOSI and MISO
cfg = (spi_bus_config_t){.mosi_io_num = spi_periph_signal[TEST_SPI_HOST].spiq_iomux_pin, .miso_io_num = spi_periph_signal[TEST_SPI_HOST].spid_iomux_pin, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = spi_periph_signal[TEST_SPI_HOST].spihd_iomux_pin, .quadwp_io_num = spi_periph_signal[TEST_SPI_HOST].spiwp_iomux_pin,
.max_transfer_sz = 8, .flags = flags_expected};
@ -360,7 +360,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test 4 output pins...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_DUAL;
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_DUAL | SPICOMMON_BUSFLAG_GPIO_PINS;
//swap MOSI and MISO
cfg = (spi_bus_config_t){.mosi_io_num = spi_periph_signal[TEST_SPI_HOST].spiq_iomux_pin, .miso_io_num = spi_periph_signal[TEST_SPI_HOST].spid_iomux_pin, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
@ -370,21 +370,21 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test master 5 output pins and MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_WPHD;
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_WPHD | SPICOMMON_BUSFLAG_GPIO_PINS;
cfg = (spi_bus_config_t){.mosi_io_num = spi_periph_signal[TEST_SPI_HOST].spid_iomux_pin, .miso_io_num = INPUT_ONLY_PIN, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = spi_periph_signal[TEST_SPI_HOST].spihd_iomux_pin, .quadwp_io_num = spi_periph_signal[TEST_SPI_HOST].spiwp_iomux_pin,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test slave 5 output pins and MISO on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_WPHD;
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_WPHD | SPICOMMON_BUSFLAG_GPIO_PINS;
cfg = (spi_bus_config_t){.mosi_io_num = INPUT_ONLY_PIN, .miso_io_num = spi_periph_signal[TEST_SPI_HOST].spiq_iomux_pin, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = spi_periph_signal[TEST_SPI_HOST].spihd_iomux_pin, .quadwp_io_num = spi_periph_signal[TEST_SPI_HOST].spiwp_iomux_pin,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test master 3 output pins and MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO;
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_GPIO_PINS;
cfg = (spi_bus_config_t){.mosi_io_num = spi_periph_signal[TEST_SPI_HOST].spid_iomux_pin, .miso_io_num = INPUT_ONLY_PIN, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
@ -392,7 +392,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
TEST_ASSERT_EQUAL_HEX32( flags_expected, flags_o );
ESP_LOGI(TAG, "test slave 3 output pins and MISO on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO;
flags_expected = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_GPIO_PINS;
cfg = (spi_bus_config_t){.mosi_io_num = INPUT_ONLY_PIN, .miso_io_num = spi_periph_signal[TEST_SPI_HOST].spiq_iomux_pin, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ESP_OK(spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
@ -415,7 +415,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check dual flag for master 5 output pins and MISO/MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_DUAL;
flags_expected = SPICOMMON_BUSFLAG_DUAL | SPICOMMON_BUSFLAG_GPIO_PINS;
cfg = (spi_bus_config_t){.mosi_io_num = spi_periph_signal[TEST_SPI_HOST].spid_iomux_pin, .miso_io_num = INPUT_ONLY_PIN, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = spi_periph_signal[TEST_SPI_HOST].spihd_iomux_pin, .quadwp_io_num = spi_periph_signal[TEST_SPI_HOST].spiwp_iomux_pin,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));
@ -426,7 +426,7 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_SLAVE, &flags_o));
ESP_LOGI(TAG, "check dual flag for master 3 output pins and MISO/MOSI on input-only pin...");
flags_expected = SPICOMMON_BUSFLAG_DUAL;
flags_expected = SPICOMMON_BUSFLAG_DUAL | SPICOMMON_BUSFLAG_GPIO_PINS;
cfg = (spi_bus_config_t){.mosi_io_num = spi_periph_signal[TEST_SPI_HOST].spid_iomux_pin, .miso_io_num = INPUT_ONLY_PIN, .sclk_io_num = spi_periph_signal[TEST_SPI_HOST].spiclk_iomux_pin, .quadhd_io_num = -1, .quadwp_io_num = -1,
.max_transfer_sz = 8, .flags = flags_expected};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, spicommon_bus_initialize_io(TEST_SPI_HOST, &cfg, 0, flags_expected|SPICOMMON_BUSFLAG_MASTER, &flags_o));

View File

@ -0,0 +1,576 @@
/*
Tests for the spi slave hd mode
*/
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "soc/spi_periph.h"
#include "driver/spi_master.h"
#include "esp_serial_slave_link/essl_spi.h"
#if SOC_SPI_SUPPORT_SLAVE_HD_VER2
#include "driver/spi_slave_hd.h"
#include "unity.h"
#include "test/test_common_spi.h"
#define TEST_DMA_MAX_SIZE 14000
#define TEST_BUFFER_SIZE 256 ///< buffer size of each wrdma buffer in fifo mode
#define TEST_SEG_SIZE 25
//ESP32-S2 cannot do single board test over IOMUX+GPIO matrix
#define TEST_MASTER_GPIO_MATRIX 1
#define SPI_SLOT_TEST_DEFAULT_CONFIG() {\
.spics_io_num = PIN_NUM_CS, \
.flags = 0, \
.mode = 0, \
.command_bits = 8,\
.address_bits = 8,\
.dummy_bits = 8,\
.queue_size = 10,\
}
//context definition for the tcf framework
typedef struct {
WORD_ALIGNED_ATTR uint8_t master_wrdma_buf[TEST_DMA_MAX_SIZE];
WORD_ALIGNED_ATTR uint8_t master_rddma_buf[TEST_DMA_MAX_SIZE];
WORD_ALIGNED_ATTR uint8_t slave_wrdma_buf[TEST_DMA_MAX_SIZE];
WORD_ALIGNED_ATTR uint8_t slave_rddma_buf[TEST_DMA_MAX_SIZE];
SemaphoreHandle_t ev_rdbuf;
SemaphoreHandle_t ev_wrbuf;
spi_slave_hd_data_t tx_data;
spi_slave_hd_data_t rx_data;
} testhd_context_t;
static uint32_t get_hd_flags(void)
{
#if !defined(SLAVE_SUPPORT_QIO)
return 0;
#endif
int flag_id = rand() % 5;
ESP_LOGI("io mode", "%d", flag_id);
switch (flag_id) {
case 1:
return SPI_TRANS_MODE_DIO;
case 2:
return SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_DIOQIO_ADDR;
case 3:
return SPI_TRANS_MODE_QIO;
case 4:
return SPI_TRANS_MODE_QIO | SPI_TRANS_MODE_DIOQIO_ADDR;
default:
return 0;
}
}
static void init_master_hd(spi_device_handle_t* spi, const spitest_param_set_t* config, int freq)
{
spi_bus_config_t bus_cfg = SPI_BUS_TEST_DEFAULT_CONFIG();
bus_cfg.max_transfer_sz = TEST_DMA_MAX_SIZE;
bus_cfg.quadhd_io_num = PIN_NUM_HD;
bus_cfg.quadwp_io_num = PIN_NUM_WP;
#if defined(TEST_MASTER_GPIO_MATRIX) && CONFIG_IDF_TARGET_ESP32S2
bus_cfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS;
#endif
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &bus_cfg, TEST_SPI_HOST));
spi_device_interface_config_t dev_cfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
dev_cfg.flags = SPI_DEVICE_HALFDUPLEX;
dev_cfg.command_bits = 8;
dev_cfg.address_bits = 8;
dev_cfg.dummy_bits = 8;
dev_cfg.clock_speed_hz = freq;
dev_cfg.mode = config->mode;
dev_cfg.input_delay_ns = config->slave_tv_ns;
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &dev_cfg, spi));
}
static void init_slave_hd(int mode, const spi_slave_hd_callback_config_t* callback)
{
spi_bus_config_t bus_cfg = SPI_BUS_TEST_DEFAULT_CONFIG();
bus_cfg.max_transfer_sz = TEST_DMA_MAX_SIZE*30;
bus_cfg.quadwp_io_num = SLAVE_PIN_NUM_WP;
bus_cfg.quadhd_io_num = SLAVE_PIN_NUM_HD;
#ifdef TEST_SLAVE_GPIO_MATRIX
bus_cfg.flags |= SPICOMMON_BUSFLAG_FORCE_GPIO;
#endif
spi_slave_hd_slot_config_t slave_hd_cfg = SPI_SLOT_TEST_DEFAULT_CONFIG();
slave_hd_cfg.mode = mode;
slave_hd_cfg.dma_chan = TEST_SLAVE_HOST;
if (callback) {
slave_hd_cfg.cb_config = *callback;
} else {
slave_hd_cfg.cb_config = (spi_slave_hd_callback_config_t){};
}
TEST_ESP_OK(spi_slave_hd_init(TEST_SLAVE_HOST, &bus_cfg, &slave_hd_cfg));
}
static void test_hd_init(void** arg)
{
TEST_ASSERT(*arg==NULL);
*arg = malloc(sizeof(testhd_context_t));
assert(((int)arg%4)==0);
testhd_context_t* context = (testhd_context_t*)*arg;
TEST_ASSERT(context!=NULL);
context->ev_rdbuf = xSemaphoreCreateBinary();
context->ev_wrbuf = xSemaphoreCreateBinary();
}
static void test_hd_deinit(void* arg)
{
testhd_context_t *context = arg;
vSemaphoreDelete(context->ev_rdbuf);
vSemaphoreDelete(context->ev_wrbuf);
}
esp_err_t wait_wrbuf_sig(testhd_context_t* context, TickType_t wait)
{
BaseType_t r = xSemaphoreTake(context->ev_wrbuf, wait);
if (r==pdTRUE) {
return ESP_OK;
} else {
return ESP_ERR_TIMEOUT;
}
}
esp_err_t wait_rdbuf_sig(testhd_context_t* context, TickType_t wait)
{
BaseType_t r = xSemaphoreTake(context->ev_rdbuf, wait);
if (r==pdTRUE) {
return ESP_OK;
} else {
return ESP_ERR_TIMEOUT;
}
}
static void check_no_rx(testhd_context_t* context)
{
spi_slave_hd_data_t* ret_trans;
esp_err_t ret = spi_slave_hd_get_trans_res(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_RX, &ret_trans, 0);
TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, ret);
}
static void check_no_tx(testhd_context_t* context)
{
spi_slave_hd_data_t* ret_trans;
esp_err_t ret = spi_slave_hd_get_trans_res(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_TX, &ret_trans, 0);
TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, ret);
}
bool wrbuf_cb(void* arg, spi_slave_hd_event_t* ev, BaseType_t* awoken)
{
TEST_ASSERT_EQUAL(SPI_EV_BUF_RX, ev->event);
testhd_context_t* ctx = (testhd_context_t*)arg;
BaseType_t r = xSemaphoreGiveFromISR(ctx->ev_wrbuf, awoken);
TEST_ASSERT_TRUE(r);
return true;
}
bool rdbuf_cb(void* arg, spi_slave_hd_event_t* ev, BaseType_t* awoken)
{
TEST_ASSERT_EQUAL(SPI_EV_BUF_TX, ev->event);
testhd_context_t* ctx = (testhd_context_t*)arg;
BaseType_t r = xSemaphoreGiveFromISR(ctx->ev_rdbuf, awoken);
TEST_ASSERT_TRUE(r);
return true;
}
static void test_hd_start(spi_device_handle_t *spi, int freq, const spitest_param_set_t* cfg, testhd_context_t* ctx)
{
init_master_hd(spi, cfg, freq);
spi_slave_hd_callback_config_t callback = {
.cb_buffer_rx = wrbuf_cb,
.cb_buffer_tx = rdbuf_cb,
.arg = ctx,
};
init_slave_hd(cfg->mode, &callback);
spitest_gpio_output_sel(PIN_NUM_MOSI, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out);
spitest_gpio_output_sel(PIN_NUM_MISO, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiq_out);
spitest_gpio_output_sel(PIN_NUM_CS, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spics_out[0]);
spitest_gpio_output_sel(PIN_NUM_CLK, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spiclk_out);
wait_wrbuf_sig(ctx, 0);
wait_rdbuf_sig(ctx, 0);
check_no_rx(ctx);
check_no_tx(ctx);
srand(9322);
for (int i = 0; i < TEST_DMA_MAX_SIZE; i++) ctx->slave_rddma_buf[i] = rand();
for (int i = 0; i < TEST_DMA_MAX_SIZE; i++) ctx->master_wrdma_buf[i] = rand();
int pos = rand() % TEST_DMA_MAX_SIZE;
int len = rand() % TEST_DMA_MAX_SIZE + 1;
if (pos + len > TEST_DMA_MAX_SIZE) len = TEST_DMA_MAX_SIZE - pos;
ESP_LOGI("rddma_load_len", "%d", len);
ctx->tx_data = (spi_slave_hd_data_t) {
.data = &ctx->slave_rddma_buf[pos],
.len = len,
};
esp_err_t err = spi_slave_hd_queue_trans(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_TX, &ctx->tx_data, portMAX_DELAY);
TEST_ESP_OK(err);
ctx->rx_data = (spi_slave_hd_data_t) {
.data = ctx->slave_wrdma_buf,
.len = TEST_DMA_MAX_SIZE,
};
err = spi_slave_hd_queue_trans(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_RX, &ctx->rx_data, portMAX_DELAY);
TEST_ESP_OK(err);
}
#define REG_REGION_SIZE 4*18
void check_no_signal(testhd_context_t* context)
{
vTaskDelay(1);
TEST_ASSERT(wait_wrbuf_sig(context, 0) == ESP_ERR_TIMEOUT);
TEST_ASSERT(wait_rdbuf_sig(context, 0) == ESP_ERR_TIMEOUT);
check_no_rx(context);
check_no_tx(context);
}
void test_wrdma(testhd_context_t* ctx, const spitest_param_set_t *cfg, spi_device_handle_t spi)
{
int pos = rand() % TEST_DMA_MAX_SIZE;
int len = rand() % TEST_DMA_MAX_SIZE+1;
if (pos+len > TEST_DMA_MAX_SIZE) len = TEST_DMA_MAX_SIZE - pos;
int test_seg_size = len;//TEST_SEG_SIZE;
ESP_LOGW("test_wrdma", "len: %d, seg_size: %d\n", len, test_seg_size);
TEST_ESP_OK(essl_spi_wrdma(spi, &ctx->master_wrdma_buf[pos], len, test_seg_size, get_hd_flags()));
spi_slave_hd_data_t* ret_trans;
esp_err_t ret = spi_slave_hd_get_trans_res(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_RX, &ret_trans, portMAX_DELAY);
TEST_ESP_OK(ret);
TEST_ASSERT_EQUAL(&ctx->rx_data, ret_trans);
TEST_ASSERT_EQUAL(len, ret_trans->trans_len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(&ctx->master_wrdma_buf[pos], ctx->slave_wrdma_buf, len);
ctx->rx_data = (spi_slave_hd_data_t) {
.data = ctx->slave_wrdma_buf,
.len = TEST_DMA_MAX_SIZE,
};
esp_err_t err = spi_slave_hd_queue_trans(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_RX, &ctx->rx_data, portMAX_DELAY);
TEST_ESP_OK(err);
}
void test_rddma(testhd_context_t* ctx, const spitest_param_set_t* cfg, spi_device_handle_t spi)
{
uint8_t* data_expected = ctx->tx_data.data;
int len;
int test_seg_size;
len = ctx->tx_data.len;
test_seg_size = TEST_SEG_SIZE;
ESP_LOGW("test_rddma", "pos: %d, len: %d, slave_tx: %d, seg_size: %d\n", data_expected - ctx->slave_rddma_buf, len, ctx->tx_data.len, test_seg_size);
TEST_ESP_OK(essl_spi_rddma(spi, ctx->master_rddma_buf, len, test_seg_size, get_hd_flags()));
spi_slave_hd_data_t* ret_trans;
esp_err_t ret = spi_slave_hd_get_trans_res(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_TX, &ret_trans, portMAX_DELAY);
TEST_ESP_OK(ret);
TEST_ASSERT_EQUAL(&ctx->tx_data, ret_trans);
spitest_cmp_or_dump(data_expected, ctx->master_rddma_buf, len);
int pos = rand() % TEST_DMA_MAX_SIZE;
len = rand() % TEST_DMA_MAX_SIZE+1;
if (pos + len > TEST_DMA_MAX_SIZE) len = TEST_DMA_MAX_SIZE - pos;
ctx->tx_data = (spi_slave_hd_data_t) {
.data = &ctx->slave_rddma_buf[pos],
.len = len,
};
esp_err_t err = spi_slave_hd_queue_trans(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_TX, &ctx->tx_data, portMAX_DELAY);
TEST_ESP_OK(err);
}
static void test_hd_loop(const void* arg1, void* arg2)
{
const spitest_param_set_t *test_cfg = arg1;
testhd_context_t *context = arg2;
const int *timing_speed_array = test_cfg->freq_list;
ESP_LOGI(MASTER_TAG, "****************** %s ***************", test_cfg->pset_name);
for (int j = 0; ; j++) {
spi_device_handle_t spi;
const int freq = timing_speed_array[j];
if (freq==0) break;
if (test_cfg->freq_limit && freq > test_cfg->freq_limit) break;
ESP_LOGI(MASTER_TAG, "======> %dk", freq / 1000);
test_hd_start(&spi, freq, test_cfg, context);
uint8_t* mem_ptr;
uint8_t slave_mem[REG_REGION_SIZE];
uint8_t recv_buffer[REG_REGION_SIZE];
srand(123);
uint32_t mem[(REG_REGION_SIZE/4)];
for (int i = 0; i < (REG_REGION_SIZE/4); i++) {
mem[i] = rand();
}
mem_ptr = (uint8_t*)mem;
check_no_signal(context);
spi_slave_hd_write_buffer(TEST_SLAVE_HOST, 0, (uint8_t *) mem, SOC_SPI_MAXIMUM_BUFFER_SIZE);
srand(123);
for (int i = 0; i < (REG_REGION_SIZE/4); i++) {
TEST_ASSERT(mem[i] == rand());
}
check_no_signal(context);
test_rddma(context, test_cfg, spi);
for (int i = 0; i < 128; i ++) {
int pos = rand()%REG_REGION_SIZE;
int len = rand()%REG_REGION_SIZE+1;
if (len+pos>REG_REGION_SIZE) len = REG_REGION_SIZE-pos;
memset(recv_buffer, 0xcc, sizeof(recv_buffer));
check_no_signal(context);
test_wrdma(context, test_cfg, spi);
check_no_signal(context);
test_rddma(context, test_cfg, spi);
check_no_signal(context);
TEST_ESP_OK(essl_spi_rdbuf(spi, recv_buffer, pos, len, get_hd_flags()));
wait_rdbuf_sig(context, portMAX_DELAY);
ESP_LOGI("mem", "pos: %d, len: %d", pos, len);
//ESP_LOG_BUFFER_HEX("recv_buffer", recv_buffer, len);
//ESP_LOG_BUFFER_HEX("mem", &mem_ptr[pos], len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(&mem_ptr[pos], recv_buffer, len);
}
check_no_signal(context);
//clear slave buffer
memset(mem, 0xcc, REG_REGION_SIZE);
memcpy(slave_mem, mem, REG_REGION_SIZE);
TEST_ESP_OK(essl_spi_wrbuf(spi, mem_ptr, 0, REG_REGION_SIZE, get_hd_flags()));
wait_wrbuf_sig(context, portMAX_DELAY);
TEST_ESP_OK(essl_spi_rdbuf(spi, recv_buffer, 0, REG_REGION_SIZE, get_hd_flags()));
wait_rdbuf_sig(context, portMAX_DELAY);
TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_mem, recv_buffer, REG_REGION_SIZE);
srand(466);
for (int i = 0; i < 64; i ++) {
ESP_LOGI("temp_i", "^^^^^^^^^^^^^^^^ %d ^^^^^^^^^^", i);
for (int j = 0; j < (REG_REGION_SIZE/4); j++) {
mem[j] = rand();
}
for (int k = 0; k < 2; k++) {
int pos = rand() % REG_REGION_SIZE;
int len = rand() % REG_REGION_SIZE + 1;
if (len + pos > REG_REGION_SIZE) len = REG_REGION_SIZE - pos;
printf("pos: %d, len: %d\n", pos, len);
TEST_ESP_OK(essl_spi_wrbuf(spi, &mem_ptr[pos], pos, len, get_hd_flags()));
wait_wrbuf_sig(context, portMAX_DELAY);
memcpy(&slave_mem[pos], &mem_ptr[pos], len);
}
check_no_signal(context);
test_rddma(context, test_cfg, spi);
check_no_signal(context);
test_wrdma(context, test_cfg, spi);
TEST_ESP_OK(essl_spi_rdbuf(spi, recv_buffer, 0, REG_REGION_SIZE, get_hd_flags()));
wait_rdbuf_sig(context, portMAX_DELAY);
check_no_signal(context);
TEST_ASSERT_EQUAL_HEX8_ARRAY(&slave_mem, recv_buffer, REG_REGION_SIZE);
}
master_free_device_bus(spi);
spi_slave_hd_deinit(TEST_SLAVE_HOST);
}
}
static const ptest_func_t hd_test_func = {
.pre_test = test_hd_init,
.post_test = test_hd_deinit,
.loop = test_hd_loop,
.def_param = spitest_def_param,
};
#define TEST_SPI_HD(name, test_set) \
PARAM_GROUP_DECLARE(name, test_set) \
TEST_SINGLE_BOARD(name, test_set, "[spi][timeout=120]", &hd_test_func)
static int test_freq_hd[] = {
// 100*1000,
// SPI_MASTER_FREQ_10M, //maximum freq MISO stable before next latch edge
// SPI_MASTER_FREQ_20M, //maximum freq MISO stable before next latch edge
SPI_MASTER_FREQ_40M, //maximum freq MISO stable before next latch edge
0,
};
#define TEST_HD_IN_CONTINUOUS_MODE true
static spitest_param_set_t hd_conf[] = {
{ .pset_name = "MODE0",
.freq_list = test_freq_hd,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 0,
},
{ .pset_name = "MODE1",
.freq_list = test_freq_hd,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 1,
},
{ .pset_name = "MODE2",
.freq_list = test_freq_hd,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 2,
},
{ .pset_name = "MODE3",
.freq_list = test_freq_hd,
.dup = FULL_DUPLEX,
.master_iomux = false,
.slave_iomux = false,
.slave_tv_ns = TV_WITH_ESP_SLAVE,
.mode = 3,
},
};
TEST_SPI_HD(HD, hd_conf);
/*
* When the previous transaction of master exceeds the length of slave prepared too long, the
* interrupt of slave will be triggered in side that transaction. In the ISR slave has to prepare
* for the next transaction, while the master is still sending the previous one.
*
* This test checks that the previous trans will not influence the data slave prepared for the next transaction.
*/
TEST_CASE("test spi slave hd continuous mode, master too long", "[spi][spi_slv_hd]")
{
spi_device_handle_t spi;
spitest_param_set_t *cfg = &hd_conf[0];
int freq = 100*1000; // the frequency should be small enough for the slave to prepare new trans
init_master_hd(&spi, cfg, freq);
//no callback needed
init_slave_hd(cfg->mode, NULL);
spitest_gpio_output_sel(PIN_NUM_MOSI, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out);
spitest_gpio_output_sel(PIN_NUM_MISO, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiq_out);
spitest_gpio_output_sel(PIN_NUM_CS, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spics_out[0]);
spitest_gpio_output_sel(PIN_NUM_CLK, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spiclk_out);
const int send_buf_size = 1024;
WORD_ALIGNED_ATTR uint8_t* slave_send_buf = malloc(send_buf_size * 2);
WORD_ALIGNED_ATTR uint8_t* master_send_buf = malloc(send_buf_size * 2);
WORD_ALIGNED_ATTR uint8_t* slave_recv_buf = malloc(send_buf_size * 2);
WORD_ALIGNED_ATTR uint8_t* master_recv_buf = malloc(send_buf_size * 2);
memset(slave_recv_buf, 0xcc, send_buf_size * 2);
memset(master_recv_buf, 0xcc, send_buf_size * 2);
srand (939);
for (int i = 0; i< send_buf_size * 2; i++) {
master_send_buf[i] = rand();
slave_send_buf[i] = rand();
}
//make the first transaction short, so that the second one will be loaded while the master is
//still doing the first transaction.
int trans_len[] = {5, send_buf_size};
spi_slave_hd_data_t slave_trans[4] = {
//recv, the buffer size should be aligned to 4
{
.data = slave_recv_buf,
.len = (trans_len[0] + 3) & (~3),
},
{
.data = slave_recv_buf + send_buf_size,
.len = (trans_len[1] + 3) & (~3),
},
//send
{
.data = slave_send_buf,
.len = trans_len[0],
},
{
.data = slave_send_buf + send_buf_size,
.len = trans_len[1],
},
};
for (int i = 0; i < 2; i ++) {
TEST_ESP_OK(spi_slave_hd_queue_trans(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_RX, &slave_trans[i], portMAX_DELAY));
}
for (int i = 2; i < 4; i ++) {
TEST_ESP_OK(spi_slave_hd_queue_trans(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_TX, &slave_trans[i], portMAX_DELAY));
}
essl_spi_wrdma(spi, master_send_buf, send_buf_size, -1, 0);
essl_spi_wrdma(spi, master_send_buf + send_buf_size, send_buf_size, 5, 0);
essl_spi_rddma(spi, master_recv_buf, send_buf_size, -1, 0);
essl_spi_rddma(spi, master_recv_buf + send_buf_size, send_buf_size, 5, 0);
for (int i = 0; i < 2; i ++) {
spi_slave_hd_data_t *ret_trans;
TEST_ESP_OK(spi_slave_hd_get_trans_res(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_RX, &ret_trans, portMAX_DELAY));
TEST_ASSERT(ret_trans == &slave_trans[i]);
TEST_ASSERT_EQUAL(slave_trans[i].len, ret_trans->trans_len);
}
for (int i = 2; i < 4; i ++) {
spi_slave_hd_data_t *ret_trans;
TEST_ESP_OK(spi_slave_hd_get_trans_res(TEST_SLAVE_HOST, SPI_SLAVE_CHAN_TX, &ret_trans, portMAX_DELAY));
TEST_ASSERT(ret_trans == &slave_trans[i]);
}
spitest_cmp_or_dump(slave_send_buf, master_recv_buf, trans_len[0]);
spitest_cmp_or_dump(slave_send_buf + send_buf_size, master_recv_buf + send_buf_size, trans_len[1]);
spitest_cmp_or_dump(master_send_buf, slave_recv_buf, trans_len[0]);
spitest_cmp_or_dump(master_send_buf + send_buf_size, slave_recv_buf + send_buf_size, trans_len[1]);
free(master_recv_buf);
free(slave_recv_buf);
free(master_send_buf);
free(slave_send_buf);
spi_slave_hd_deinit(TEST_SLAVE_HOST);
master_free_device_bus(spi);
}
#endif //SOC_SPI_SUPPORT_SLAVE_HD_VER2

View File

@ -1,5 +1,6 @@
idf_component_register(SRCS "essl.c"
"essl_sdio.c"
"essl_spi.c"
INCLUDE_DIRS "include"
REQUIRES "sdmmc"
"driver"

View File

@ -0,0 +1,188 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sys/param.h>
#include "esp_log.h"
#include "driver/spi_master.h"
#include "driver/periph_ctrl.h"
#include "essl_spi/esp32s2_defs.h"
#include "essl_spi.h"
static uint16_t get_hd_command(uint16_t cmd_i, uint32_t flags)
{
//have no prefixes
if (cmd_i == CMD_HD_EN_QPI_REG) return cmd_i;
//doesn't support 4-line commands
if(flags & SPI_TRANS_MODE_QIO && flags & SPI_TRANS_MODE_DIOQIO_ADDR &&
(cmd_i == CMD_HD_WR_END_REG || cmd_i == CMD_HD_INT0_REG ||
cmd_i == CMD_HD_INT1_REG || cmd_i == CMD_HD_INT2_REG)) {
//the transaction will be sent in corresponding 1/2/4 bit mode, without address and data.
//the CMD will have no 0xA- prefix
return cmd_i;
}
if (flags & SPI_TRANS_MODE_DIO) {
if (flags & SPI_TRANS_MODE_DIOQIO_ADDR) {
return cmd_i | CMD_HD_DIO_MODE;
} else {
return cmd_i | CMD_HD_DOUT_MODE;
}
} else if (flags & SPI_TRANS_MODE_QIO) {
if (flags & SPI_TRANS_MODE_DIOQIO_ADDR) {
return cmd_i | CMD_HD_QIO_MODE;
} else {
return cmd_i | CMD_HD_QOUT_MODE;
}
}
return cmd_i | CMD_HD_ONEBIT_MODE;
}
static int get_hd_dummy_bits(uint32_t flags)
{
//dummy is always 4 cycles when dual or quad mode is enabled. Otherwise 8 cycles in normal mode.
if (flags & (SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_QIO)) {
return 4;
} else {
return 8;
}
}
esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags)
{
spi_transaction_ext_t t = {
.base = {
.cmd = get_hd_command(CMD_HD_RDBUF_REG, flags),
.addr = addr % 72,
.rxlength = len * 8,
.rx_buffer = out_data,
.flags = flags | SPI_TRANS_VARIABLE_DUMMY,
},
.dummy_bits = get_hd_dummy_bits(flags),
};
return spi_device_transmit(spi, (spi_transaction_t*)&t);
}
esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags)
{
spi_transaction_ext_t t = {
.base = {
.cmd = get_hd_command(CMD_HD_WRBUF_REG, flags),
.addr = addr % 72,
.length = len * 8,
.tx_buffer = data,
.flags = flags | SPI_TRANS_VARIABLE_DUMMY,
},
.dummy_bits = get_hd_dummy_bits(flags),
};
return spi_device_transmit(spi, (spi_transaction_t*)&t);
}
esp_err_t essl_spi_rddma_seg(spi_device_handle_t spi, uint8_t *out_data, int seg_len, uint32_t flags)
{
spi_transaction_ext_t t = {
.base = {
.cmd = get_hd_command(CMD_HD_RDDMA_REG, flags),
.rxlength = seg_len * 8,
.rx_buffer = out_data,
.flags = flags | SPI_TRANS_VARIABLE_DUMMY,
},
.dummy_bits = get_hd_dummy_bits(flags),
};
return spi_device_transmit(spi, (spi_transaction_t*)&t);
}
esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags)
{
spi_transaction_t end_t = {
.cmd = get_hd_command(CMD_HD_INT0_REG, flags),
.flags = flags,
};
return spi_device_transmit(spi, &end_t);
}
esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, int seg_len, uint32_t flags)
{
if (!esp_ptr_dma_capable(out_data) || ((intptr_t)out_data % 4) != 0) {
return ESP_ERR_INVALID_ARG;
}
seg_len = (seg_len > 0)? seg_len : len;
uint8_t* read_ptr = out_data;
esp_err_t err = ESP_OK;
while (len > 0) {
int send_len = MIN(seg_len, len);
err = essl_spi_rddma_seg(spi, read_ptr, send_len, flags);
if (err != ESP_OK) return err;
len -= send_len;
read_ptr += send_len;
}
return essl_spi_rddma_done(spi, flags);
}
esp_err_t essl_spi_wrdma_seg(spi_device_handle_t spi, const uint8_t *data, int seg_len, uint32_t flags)
{
spi_transaction_ext_t t = {
.base = {
.cmd = get_hd_command(CMD_HD_WRDMA_REG, flags),
.length = seg_len * 8,
.tx_buffer = data,
.flags = flags | SPI_TRANS_VARIABLE_DUMMY,
},
.dummy_bits = get_hd_dummy_bits(flags),
};
return spi_device_transmit(spi, (spi_transaction_t*)&t);
}
esp_err_t essl_spi_wrdma_done(spi_device_handle_t spi, uint32_t flags)
{
spi_transaction_t end_t = {
.cmd = get_hd_command(CMD_HD_WR_END_REG, flags),
.flags = flags,
};
return spi_device_transmit(spi, &end_t);
}
esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len, int seg_len, uint32_t flags)
{
if (!esp_ptr_dma_capable(data)) {
return ESP_ERR_INVALID_ARG;
}
seg_len = (seg_len > 0)? seg_len : len;
while (len > 0) {
int send_len = MIN(seg_len, len);
esp_err_t ret = essl_spi_wrdma_seg(spi, data, send_len, flags);
if (ret != ESP_OK) return ret;
len -= send_len;
data += send_len;
}
return essl_spi_wrdma_done(spi, flags);
}
esp_err_t essl_spi_int(spi_device_handle_t spi, int int_n, uint32_t flags)
{
spi_transaction_t end_t = {
.cmd = get_hd_command(CMD_HD_INT0_REG + int_n, flags),
.flags = flags,
};
return spi_device_transmit(spi, &end_t);
}

View File

@ -0,0 +1,165 @@
// Copyright 2010-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_err.h"
#include "esp_serial_slave_link/essl.h"
#ifdef __cplusplus
extern "C"
{
#endif
////////////////////////////////////////////////////////////////////////////////
// Basic commands to communicate with the SPI Slave HD on ESP32-S2
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Read the shared buffer from the slave.
*
* @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
* by the DMA. When a byte is written, the remaining bytes in the same word will also be
* overwritten, even the ``len`` is shorter than a word.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer for read data, strongly suggested to be in the DRAM and align to 4
* @param addr Address of the slave shared buffer
* @param len Length to read
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: on success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags);
/**
* @brief Write the shared buffer of the slave.
*
* @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
* by the DMA. When a byte is written, the remaining bytes in the same word will also be
* overwritten, even the ``len`` is shorter than a word.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param addr Address of the slave shared buffer,
* @param len Length to write
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags);
/**
* @brief Receive long buffer in segments from the slave through its DMA.
*
* @note This function combines several :cpp:func:`essl_spi_rddma_seg` and one
* :cpp:func:`essl_spi_rddma_done` at the end. Used when the slave is working in segment mode.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer to hold the received data, strongly suggested to be in the DRAM and align to 4
* @param len Total length of data to receive.
* @param seg_len Length of each segment, which is not larger than the maximum transaction length
* allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send
* all data in one segment (the ``rddma_done`` will still be sent.)
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, int seg_len, uint32_t flags);
/**
* @brief Read one data segment from the slave through its DMA.
*
* @note To read long buffer, call :cpp:func:`essl_spi_rddma` instead.
*
* @param spi SPI device handle representing the slave
* @param out_data Buffer to hold the received data, strongly suggested to be in the DRAM and align to 4
* @param seg_len Length of this segment
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_rddma_seg(spi_device_handle_t spi, uint8_t *out_data, int seg_len, uint32_t flags);
/**
* @brief Send the ``rddma_done`` command to the slave. Upon receiving this command, the slave will
* stop sending the current buffer even there are data unsent, and maybe prepare the next buffer to
* send.
*
* @note This is required only when the slave is working in segment mode.
*
* @param spi SPI device handle representing the slave
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags);
/**
* @brief Send long buffer in segments to the slave through its DMA.
*
* @note This function combines several :cpp:func:`essl_spi_wrdma_seg` and one
* :cpp:func:`essl_spi_wrdma_done` at the end. Used when the slave is working in segment mode.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param len Total length of data to send.
* @param seg_len Length of each segment, which is not larger than the maximum transaction length
* allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send
* all data in one segment (the ``wrdma_done`` will still be sent.)
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len, int seg_len, uint32_t flags);
/**
* @brief Send one data segment to the slave through its DMA.
*
* @note To send long buffer, call :cpp:func:`essl_spi_wrdma` instead.
*
* @param spi SPI device handle representing the slave
* @param data Buffer for data to send, strongly suggested to be in the DRAM and align to 4
* @param seg_len Length of this segment
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_wrdma_seg(spi_device_handle_t spi, const uint8_t *data, int seg_len, uint32_t flags);
/**
* @brief Send the ``wrdma_done`` command to the slave. Upon receiving this command, the slave will
* stop receiving, process the received data, and maybe prepare the next buffer to receive.
*
* @note This is required only when the slave is working in segment mode.
*
* @param spi SPI device handle representing the slave
* @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
* @return
* - ESP_OK: success
* - or other return value from :cpp:func:`spi_device_transmit`.
*/
esp_err_t essl_spi_wrdma_done(spi_device_handle_t spi, uint32_t flags);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,38 @@
// Copyright 2010-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
// NOTE: From the view of master
#define CMD_HD_WRBUF_REG 0x01
#define CMD_HD_RDBUF_REG 0x02
#define CMD_HD_WRDMA_REG 0x03
#define CMD_HD_RDDMA_REG 0x04
#define CMD_HD_ONEBIT_MODE 0x00
#define CMD_HD_DOUT_MODE 0x10
#define CMD_HD_QOUT_MODE 0x20
#define CMD_HD_DIO_MODE 0x50
#define CMD_HD_QIO_MODE 0xA0
#define CMD_HD_SEG_END_REG 0x05
#define CMD_HD_EN_QPI_REG 0x06
#define CMD_HD_WR_END_REG 0x07
#define CMD_HD_INT0_REG 0x08
#define CMD_HD_INT1_REG 0x09
#define CMD_HD_INT2_REG 0x0A
#define CMD_HD_EX_QPI_REG 0xDD
#define SPI_SLAVE_HD_BUFFER_SIZE 72

View File

@ -40,10 +40,11 @@ if(CONFIG_IDF_TARGET_ESP32)
"src/hal/sdio_slave_hal.c")
elseif(CONFIG_IDF_TARGET_ESP32S2)
target_sources(${COMPONENT_LIB} PRIVATE "src/hal/spi_flash_hal_gpspi.c")
target_sources(${COMPONENT_LIB} PRIVATE "src/hal/spi_slave_hd_hal.c")
endif()
# Since there can be chip-specific HAL headers which can include the common
# HAL header via include_next, process the build scripts here first so that
# Since there can be chip-specific HAL headers which can include the common
# HAL header via include_next, process the build scripts here first so that
# include directories appear first in the compile command.
add_subdirectory(src/${target})
target_include_directories(${COMPONENT_LIB} PUBLIC include)

View File

@ -1,10 +1,11 @@
SOC_NAME := $(IDF_TARGET)
COMPONENT_SRCDIRS := src src/hal
COMPONENT_ADD_INCLUDEDIRS :=
COMPONENT_ADD_INCLUDEDIRS :=
COMPONENT_OBJEXCLUDE += src/hal/spi_slave_hd_hal.o
# Since there can be chip-specific HAL headers which can include the common
# HAL header via include_next, process the build scripts here first so that
# Since there can be chip-specific HAL headers which can include the common
# HAL header via include_next, process the build scripts here first so that
# include directories appear first in the compile command.
-include $(COMPONENT_PATH)/src/$(SOC_NAME)/component.mk
COMPONENT_ADD_INCLUDEDIRS += include

View File

@ -0,0 +1,212 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in soc/include/hal/readme.md
******************************************************************************/
/*
* The HAL layer for SPI Slave HD mode, currently only segment mode is supported
*
* Usage:
* - Firstly, initialize the slave with `slave_hd_hal_init`
*
* - Event handling:
* - (Optional) Call ``spi_slave_hd_hal_enable_event_intr`` to enable the used interrupts
* - (Basic) Call ``spi_slave_hd_hal_check_clear_event`` to check whether an event happen, and also
* clear its interrupt. For events: SPI_EV_BUF_TX, SPI_EV_BUF_RX, SPI_EV_BUF_RX, SPI_EV_CMD9,
* SPI_EV_CMDA.
* - (Advanced) Call ``spi_slave_hd_hal_check_disable_event`` to disable the interrupt of an event,
* so that the task can call ``spi_slave_hd_hal_invoke_event_intr`` later to manually invoke the
* ISR. For SPI_EV_SEND, SPI_EV_RECV.
*
* - TXDMA:
* - To send data through DMA, call `spi_slave_hd_hal_txdma`
* - When the operation is done, SPI_EV_SEND will be triggered.
*
* - RXDMA:
* - To receive data through DMA, call `spi_slave_hd_hal_rxdma`
* - When the operation is done, SPI_EV_RECV will be triggered.
* - Call ``spi_slave_hd_hal_rxdma_get_len`` to get the received length
*
* - Shared buffer:
* - Call ``spi_slave_hd_hal_write_buffer`` to write the shared register buffer. When the buffer is
* read by the master (regardless of the read address), SPI_EV_BUF_TX will be triggered
* - Call ``spi_slave_hd_hal_read_buffer`` to read the shared register buffer. When the buffer is
* written by the master (regardless of the written address), SPI_EV_BUF_RX will be triggered.
*/
#pragma once
#include <esp_types.h>
#include "esp_err.h"
#include "hal/spi_ll.h"
#include "hal/spi_types.h"
/// Configuration of the HAL
typedef struct {
int host_id; ///< Host ID of the spi peripheral
int spics_io_num; ///< CS GPIO pin for this device
uint8_t mode; ///< SPI mode (0-3)
int command_bits; ///< command field bits, multiples of 8 and at least 8.
int address_bits; ///< address field bits, multiples of 8 and at least 8.
int dummy_bits; ///< dummy field bits, multiples of 8 and at least 8.
struct {
uint32_t tx_lsbfirst : 1;///< Whether TX data should be sent with LSB first.
uint32_t rx_lsbfirst : 1;///< Whether RX data should be read with LSB first.
};
int dma_chan; ///< The dma channel used.
} spi_slave_hd_hal_config_t;
/// Context of the HAL, initialized by :cpp:func:`slave_hd_hal_init`.
typedef struct {
spi_dev_t* dev; ///< Beginning address of the peripheral registers.
lldesc_t *dmadesc_tx; /**< Array of DMA descriptor used by the TX DMA.
* The amount should be larger than dmadesc_n. The driver should ensure that
* the data to be sent is shorter than the descriptors can hold.
*/
lldesc_t *dmadesc_rx; /**< Array of DMA descriptor used by the RX DMA.
* The amount should be larger than dmadesc_n. The driver should ensure that
* the data to be sent is shorter than the descriptors can hold.
*/
/* Internal status used by the HAL implementation, initialized as 0. */
uint32_t intr_not_triggered;
} spi_slave_hd_hal_context_t;
/**
* @brief Initialize the hardware and part of the context
*
* @param hal Context of the HAL layer
* @param config Configuration of the HAL
*/
void slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_config_t *config);
/**
* @brief Check and clear signal of one event
*
* @param hal Context of the HAL layer
* @param ev Event to check
* @return true if event triggered, otherwise false
*/
bool spi_slave_hd_hal_check_clear_event(spi_slave_hd_hal_context_t* hal, spi_event_t ev);
/**
* @brief Check and clear the interrupt of one event.
*
* @note The event source will be kept, so that the interrupt can be invoked by
* :cpp:func:`spi_slave_hd_hal_invoke_event_intr`. If event not triggered, its interrupt source
* will not be disabled either.
*
* @param hal Context of the HAL layer
* @param ev Event to check and disable
* @return true if event triggered, otherwise false
*/
bool spi_slave_hd_hal_check_disable_event(spi_slave_hd_hal_context_t* hal, spi_event_t ev);
/**
* @brief Enable to invole the ISR of corresponding event.
*
* @note The function, compared with :cpp:func:`spi_slave_hd_hal_enable_event_intr`, contains a
* workaround to force trigger the interrupt, even if the interrupt source cannot be initialized
* correctly.
*
* @param hal Context of the HAL layer
* @param ev Event (reason) to invoke the ISR
*/
void spi_slave_hd_hal_invoke_event_intr(spi_slave_hd_hal_context_t* hal, spi_event_t ev);
/**
* @brief Enable the interrupt source of corresponding event.
*
* @param hal Context of the HAL layer
* @param ev Event whose corresponding interrupt source should be enabled.
*/
void spi_slave_hd_hal_enable_event_intr(spi_slave_hd_hal_context_t* hal, spi_event_t ev);
////////////////////////////////////////////////////////////////////////////////
// RX DMA
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Start the RX DMA operation to the specified buffer.
*
* @param hal Context of the HAL layer
* @param[out] out_buf Buffer to receive the data
* @param len Maximul length to receive
*/
void spi_slave_hd_hal_rxdma(spi_slave_hd_hal_context_t *hal, uint8_t *out_buf, size_t len);
/**
* @brief Get the length of total received data
*
* @param hal Context of the HAL layer
* @return The received length
*/
int spi_slave_hd_hal_rxdma_get_len(spi_slave_hd_hal_context_t *hal);
////////////////////////////////////////////////////////////////////////////////
// TX DMA
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Start the TX DMA operation with the specified buffer
*
* @param hal Context of the HAL layer
* @param data Buffer of data to send
* @param len Size of the buffer, also the maximum length to send
*/
void spi_slave_hd_hal_txdma(spi_slave_hd_hal_context_t *hal, uint8_t *data, size_t len);
////////////////////////////////////////////////////////////////////////////////
// Shared buffer
////////////////////////////////////////////////////////////////////////////////
/**
* @brief Read from the shared register buffer
*
* @param hal Context of the HAL layer
* @param addr Address of the shared regsiter to read
* @param out_data Buffer to store the read data
* @param len Length to read from the shared buffer
*/
void spi_slave_hd_hal_read_buffer(spi_slave_hd_hal_context_t *hal, int addr, uint8_t *out_data, size_t len);
/**
* @brief Write the shared register buffer
*
* @param hal Context of the HAL layer
* @param addr Address of the shared register to write
* @param data Buffer of the data to write
* @param len Length to write into the shared buffer
*/
void spi_slave_hd_hal_write_buffer(spi_slave_hd_hal_context_t *hal, int addr, uint8_t *data, size_t len);
/**
* @brief Get the length of previous transaction.
*
* @param hal Context of the HAL layer
* @return The length of previous transaction
*/
int spi_slave_hd_hal_get_rxlen(spi_slave_hd_hal_context_t *hal);
/**
* @brief Get the address of last transaction
*
* @param hal Context of the HAL layer
* @return The address of last transaction
*/
int spi_slave_hd_hal_get_last_addr(spi_slave_hd_hal_context_t *hal);

View File

@ -15,7 +15,9 @@
#pragma once
#include "soc/spi_caps.h"
#include "esp_attr.h"
#include "sdkconfig.h"
#include <esp_bit_defs.h>
/**
* @brief Enum with the three SPI peripherals that are software-accessible in it
@ -27,6 +29,19 @@ typedef enum {
SPI3_HOST=2, ///< SPI3
} spi_host_device_t;
/// SPI Events
typedef enum {
SPI_EV_BUF_TX = BIT(0), ///< The buffer has sent data to master, Slave HD only
SPI_EV_BUF_RX = BIT(1), ///< The buffer has received data from master, Slave HD only
SPI_EV_SEND = BIT(2), ///< Has sent data to master through RDDMA, Slave HD only
SPI_EV_RECV = BIT(3), ///< Has received data from master through WRDMA, Slave HD only
SPI_EV_CMD9 = BIT(4), ///< Received CMD9 from master, Slave HD only
SPI_EV_CMDA = BIT(5), ///< Received CMDA from master, Slave HD only
SPI_EV_TRANS = BIT(6), ///< A transaction has done
} spi_event_t;
FLAG_ATTR(spi_event_t)
/** @cond */ //Doxy command to hide preprocessor definitions from docs */
//alias for different chips

View File

@ -33,13 +33,23 @@
* The caller should ensure there is enough size to hold the array, by calling
* ``lldesc_get_required_num``.
*
* @param out_desc_array Output of a descriptor array, the head should be fed to the DMA.
* @param[out] out_desc_array Output of a descriptor array, the head should be fed to the DMA.
* @param buffer Buffer for the descriptors to point to.
* @param size Size (or length for TX) of the buffer
* @param isrx The RX DMA may require the buffer to be word-aligned, set to true for a RX link, otherwise false.
*/
void lldesc_setup_link(lldesc_t *out_desc_array, const void *buffer, int size, bool isrx);
/**
* @brief Get the received length of a linked list, until end of the link or eof.
*
* @param head The head of the linked list.
* @param[out] out_next Output of the next descriptor of the EOF descriptor. Return NULL if there's no
* EOF. Can be set to NULL if next descriptor is not needed.
* @return The accumulation of the `len` field of all descriptors until EOF or the end of the link.
*/
int lldesc_get_received_len(lldesc_t* head, lldesc_t** out_next);
/**
* Get the number of descriptors required for a given buffer size.
*

View File

@ -45,7 +45,8 @@
#define SOC_SPI_SLAVE_SUPPORT_SEG_TRANS 1
#define SOC_SPI_SUPPORT_CD_SIG 1
#define SOC_SPI_SUPPORT_CONTINUOUS_TRANS 1
/// The SPI Slave half duplex mode has been updated greatly in ESP32-S2
#define SOC_SPI_SUPPORT_SLAVE_HD_VER2 1
// Peripheral supports DIO, DOUT, QIO, or QOUT
// VSPI (SPI3) only support 1-bit mode

View File

@ -28,6 +28,7 @@
#include "esp_types.h"
#include "soc/spi_periph.h"
#include "esp32s2/rom/lldesc.h"
#include "esp_attr.h"
#ifdef __cplusplus
extern "C" {
@ -64,6 +65,34 @@ typedef enum {
SPI_LL_INT_TYPE_SEG = 1, ///< Wait for DMA signals
} spi_ll_slave_intr_type;
/// Type definition of all supported interrupts
typedef enum {
SPI_LL_INTR_TRANS_DONE = BIT(0), ///< A transaction has done
SPI_LL_INTR_IN_SUC_EOF = BIT(1), ///< DMA in_suc_eof triggered
SPI_LL_INTR_OUT_EOF = BIT(2), ///< DMA out_eof triggered
SPI_LL_INTR_OUT_TOTAL_EOF = BIT(3), ///< DMA out_total_eof triggered
SPI_LL_INTR_IN_FULL = BIT(4), ///< DMA in_full error happened
SPI_LL_INTR_OUT_EMPTY = BIT(5), ///< DMA out_empty error happened
SPI_LL_INTR_RDBUF = BIT(6), ///< Has received RDBUF command. Only available in slave HD.
SPI_LL_INTR_WRBUF = BIT(7), ///< Has received WRBUF command. Only available in slave HD.
SPI_LL_INTR_RDDMA = BIT(8), ///< Has received RDDMA command. Only available in slave HD.
SPI_LL_INTR_WRDMA = BIT(9), ///< Has received WRDMA command. Only available in slave HD.
SPI_LL_INTR_WR_DONE = BIT(10), ///< Has received WR_DONE command. Only available in slave HD.
SPI_LL_INTR_CMD8 = BIT(11), ///< Has received CMD8 command. Only available in slave HD.
SPI_LL_INTR_CMD9 = BIT(12), ///< Has received CMD9 command. Only available in slave HD.
SPI_LL_INTR_CMDA = BIT(13), ///< Has received CMDA command. Only available in slave HD.
SPI_LL_INTR_SEG_DONE = BIT(14),
} spi_ll_intr_t;
FLAG_ATTR(spi_ll_intr_t)
///< Flags for conditions under which the transaction length should be recorded
typedef enum {
SPI_LL_TRANS_LEN_COND_WRBUF = BIT(0), ///< WRBUF length will be recorded
SPI_LL_TRANS_LEN_COND_RDBUF = BIT(1), ///< RDBUF length will be recorded
SPI_LL_TRANS_LEN_COND_WRDMA = BIT(2), ///< WRDMA length will be recorded
SPI_LL_TRANS_LEN_COND_RDDMA = BIT(3), ///< RDDMA length will be recorded
} spi_ll_trans_len_cond_t;
FLAG_ATTR(spi_ll_trans_len_cond_t)
/*------------------------------------------------------------------------------
* Control
@ -125,6 +154,76 @@ static inline void spi_ll_slave_init(spi_dev_t *hw)
hw->dma_int_ena.val = 0;
}
static inline void spi_ll_slave_hd_init(spi_dev_t* hw)
{
hw->clock.val = 0;
hw->user.val = 0;
hw->ctrl.val = 0;
hw->user.sio = 0;
//hw->user.tx_start_bit = 7;
hw->slave.soft_reset = 1;
hw->slave.soft_reset = 0;
//Reset DMA
hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
hw->dma_out_link.start = 0;
hw->dma_in_link.start = 0;
hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
if (hw == &GPSPI2) {
hw->dma_conf.out_data_burst_en = 1;
} else {
hw->dma_conf.out_data_burst_en = 0;
}
hw->dma_conf.outdscr_burst_en = 1;
hw->dma_conf.indscr_burst_en = 1;
hw->dma_conf.rx_eof_en = 0;
hw->dma_conf.out_eof_mode = 1;
hw->dma_conf.out_auto_wrback = 1;
hw->user.doutdin = 0; //we only support full duplex
hw->slave.slave_mode = 1;
}
/**
* Check whether user-defined transaction is done.
*
* @param hw Beginning address of the peripheral registers.
*
* @return true if transaction is done, otherwise false.
*/
static inline bool spi_ll_usr_is_done(spi_dev_t *hw)
{
return hw->slave.trans_done;
}
/**
* Trigger start of user-defined transaction.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_user_start(spi_dev_t *hw)
{
hw->cmd.usr = 1;
}
/**
* Get current running command bit-mask. (Preview)
*
* @param hw Beginning address of the peripheral registers.
*
* @return Bitmask of running command, see ``SPI_CMD_REG``. 0 if no in-flight command.
*/
static inline uint32_t spi_ll_get_running_cmd(spi_dev_t *hw)
{
return hw->cmd.val;
}
/*------------------------------------------------------------------------------
* DMA
*----------------------------------------------------------------------------*/
/**
* Reset TX and RX DMAs.
*
@ -168,6 +267,62 @@ static inline void spi_ll_txdma_start(spi_dev_t *hw, lldesc_t *addr)
hw->dma_out_link.start = 1;
}
static inline void spi_ll_rxdma_reset(spi_dev_t* hw)
{
hw->dma_conf.in_rst = 1;
hw->dma_conf.in_rst = 0;
hw->dma_conf.infifo_full_clr = 1;
hw->dma_conf.infifo_full_clr = 0;
}
static inline void spi_ll_txdma_reset(spi_dev_t* hw)
{
hw->dma_conf.out_rst = 1;
hw->dma_conf.out_rst = 0;
hw->dma_conf.outfifo_empty_clr = 1;
hw->dma_conf.outfifo_empty_clr = 0;
}
static inline void spi_ll_rxdma_restart(spi_dev_t* hw)
{
hw->dma_in_link.restart = 1;
}
static inline void spi_ll_txdma_restart(spi_dev_t* hw)
{
hw->dma_out_link.restart = 1;
}
static inline void spi_ll_rxdma_disable(spi_dev_t* hw)
{
hw->dma_in_link.dma_rx_ena = 0;
}
static inline void spi_ll_txdma_disable(spi_dev_t* hw)
{
hw->dma_out_link.dma_tx_ena = 0;
hw->dma_out_link.stop = 1;
}
static inline void spi_ll_rxdma_clr_err(spi_dev_t* hw)
{
hw->dma_conf.infifo_full_clr = 1;
hw->dma_conf.infifo_full_clr = 0;
}
static inline void spi_ll_txdma_clr_err(spi_dev_t* hw)
{
hw->dma_int_clr.outfifo_empty_err= 1;
}
static inline bool spi_ll_txdma_get_empty_err(spi_dev_t* hw)
{
return hw->dma_int_raw.outfifo_empty_err;
}
/*------------------------------------------------------------------------------
* Buffer
*----------------------------------------------------------------------------*/
/**
* Write to SPI buffer.
*
@ -205,99 +360,43 @@ static inline void spi_ll_read_buffer(spi_dev_t *hw, uint8_t *buffer_to_rcv, siz
}
}
/**
* Check whether user-defined transaction is done.
*
* @param hw Beginning address of the peripheral registers.
*
* @return true if transaction is done, otherwise false.
*/
static inline bool spi_ll_usr_is_done(spi_dev_t *hw)
static inline void spi_ll_read_buffer_byte(spi_dev_t *hw, int byte_addr, uint8_t *out_data, int len)
{
return hw->slave.trans_done;
while (len>0) {
uint32_t word = hw->data_buf[byte_addr/4];
int offset = byte_addr % 4;
int copy_len = 4 - offset;
if (copy_len > len) copy_len = len;
memcpy(out_data, ((uint8_t*)&word)+offset, copy_len);
byte_addr += copy_len;
out_data += copy_len;
len -= copy_len;
}
}
/**
* Trigger start of user-defined transaction.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_user_start(spi_dev_t *hw)
static inline void spi_ll_write_buffer_byte(spi_dev_t *hw, int byte_addr, uint8_t *data, int len)
{
hw->cmd.usr = 1;
}
assert( byte_addr + len <= 72);
assert(len > 0);
assert(byte_addr >= 0);
/**
* Get current running command bit-mask. (Preview)
*
* @param hw Beginning address of the peripheral registers.
*
* @return Bitmask of running command, see ``SPI_CMD_REG``. 0 if no in-flight command.
*/
static inline uint32_t spi_ll_get_running_cmd(spi_dev_t *hw)
{
return hw->cmd.val;
}
while (len > 0) {
uint32_t word;
int offset = byte_addr % 4;
/**
* Disable the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_disable_int(spi_dev_t *hw)
{
hw->slave.int_trans_done_en = 0;
}
int copy_len = 4 - offset;
if (copy_len > len) copy_len = len;
/**
* Clear the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_clear_int_stat(spi_dev_t *hw)
{
hw->slave.trans_done = 0;
hw->dma_int_clr.val = UINT32_MAX;
}
//read-modify-write
if (copy_len != 4) word = hw->data_buf[byte_addr / 4];
/**
* Set the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_set_int_stat(spi_dev_t *hw)
{
hw->slave.trans_done = 1;
}
/**
* Enable the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_enable_int(spi_dev_t *hw)
{
hw->slave.int_trans_done_en = 1;
}
/**
* Set different interrupt types for the slave.
*
* @param hw Beginning address of the peripheral registers.
* @param int_type Interrupt type
*/
static inline void spi_ll_slave_set_int_type(spi_dev_t *hw, spi_ll_slave_intr_type int_type)
{
switch (int_type) {
case SPI_LL_INT_TYPE_SEG:
hw->dma_int_ena.in_suc_eof = 1;
hw->dma_int_ena.out_total_eof = 1;
hw->slave.int_trans_done_en = 0;
break;
default:
hw->dma_int_ena.in_suc_eof = 0;
hw->dma_int_ena.out_total_eof = 0;
hw->slave.int_trans_done_en = 1;
memcpy(((uint8_t *)&word) + offset, data, copy_len);
hw->data_buf[byte_addr / 4] = word;
data += copy_len;
byte_addr += copy_len;
len -= copy_len;
}
}
@ -460,6 +559,12 @@ static inline void spi_ll_master_set_io_mode(spi_dev_t *hw, spi_ll_io_mode_t io_
}
}
static inline void spi_ll_slave_set_seg_mode(spi_dev_t* hw, bool seg_trans)
{
hw->dma_conf.dma_seg_trans_en = seg_trans;
hw->dma_conf.rx_eof_en = seg_trans;
}
/**
* Select one of the CS to use in current transaction.
*
@ -859,7 +964,150 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw)
return hw->slv_rd_byte.data_bytelen * 8;
}
/*------------------------------------------------------------------------------
* Interrupts
*----------------------------------------------------------------------------*/
//helper macros to generate code for each interrupts
#define FOR_EACH_ITEM(op, list) do { list(op) } while(0)
#define INTR_LIST(item) \
item(SPI_LL_INTR_TRANS_DONE, slave.int_trans_done_en, slave.trans_done, slave.trans_done=0) \
item(SPI_LL_INTR_RDBUF, slave.int_rd_buf_done_en, slv_rdbuf_dlen.rd_buf_done, slv_rdbuf_dlen.rd_buf_done=0) \
item(SPI_LL_INTR_WRBUF, slave.int_wr_buf_done_en, slv_wrbuf_dlen.wr_buf_done, slv_wrbuf_dlen.wr_buf_done=0) \
item(SPI_LL_INTR_RDDMA, slave.int_rd_dma_done_en, slv_rd_byte.rd_dma_done, slv_rd_byte.rd_dma_done=0) \
item(SPI_LL_INTR_WRDMA, slave.int_wr_dma_done_en, slave1.wr_dma_done, slave1.wr_dma_done=0) \
item(SPI_LL_INTR_IN_SUC_EOF, dma_int_ena.in_suc_eof, dma_int_raw.in_suc_eof, dma_int_clr.in_suc_eof=1) \
item(SPI_LL_INTR_OUT_EOF, dma_int_ena.out_eof, dma_int_raw.out_eof, dma_int_clr.out_eof=1) \
item(SPI_LL_INTR_OUT_TOTAL_EOF, dma_int_ena.out_total_eof, dma_int_raw.out_total_eof, dma_int_clr.out_total_eof=1) \
item(SPI_LL_INTR_SEG_DONE, slave.int_dma_seg_trans_en, hold.dma_seg_trans_done, hold.dma_seg_trans_done=0) \
item(SPI_LL_INTR_IN_FULL, dma_int_ena.infifo_full_err, dma_int_raw.infifo_full_err, dma_int_clr.infifo_full_err=1) \
item(SPI_LL_INTR_OUT_EMPTY, dma_int_ena.outfifo_empty_err, dma_int_raw.outfifo_empty_err, dma_int_clr.outfifo_empty_err=1) \
item(SPI_LL_INTR_WR_DONE, dma_int_ena.cmd7, dma_int_raw.cmd7, dma_int_clr.cmd7=1) \
item(SPI_LL_INTR_CMD8, dma_int_ena.cmd8, dma_int_raw.cmd8, dma_int_clr.cmd8=1) \
item(SPI_LL_INTR_CMD9, dma_int_ena.cmd9, dma_int_raw.cmd9, dma_int_clr.cmd9=1) \
item(SPI_LL_INTR_CMDA, dma_int_ena.cmda, dma_int_raw.cmda, dma_int_clr.cmda=1)
static inline void spi_ll_enable_intr(spi_dev_t* hw, spi_ll_intr_t intr_mask)
{
#define ENA_INTR(intr_bit, en_reg, ...) if (intr_mask & (intr_bit)) hw->en_reg = 1;
FOR_EACH_ITEM(ENA_INTR, INTR_LIST);
#undef ENA_INTR
}
static inline void spi_ll_disable_intr(spi_dev_t* hw, spi_ll_intr_t intr_mask)
{
#define DIS_INTR(intr_bit, en_reg, ...) if (intr_mask & (intr_bit)) hw->en_reg = 0;
FOR_EACH_ITEM(DIS_INTR, INTR_LIST);
#undef DIS_INTR
}
static inline void spi_ll_set_intr(spi_dev_t* hw, spi_ll_intr_t intr_mask)
{
#define SET_INTR(intr_bit, _, st_reg, ...) if (intr_mask & (intr_bit)) hw->st_reg = 1;
FOR_EACH_ITEM(SET_INTR, INTR_LIST);
#undef SET_INTR
}
static inline void spi_ll_clear_intr(spi_dev_t* hw, spi_ll_intr_t intr_mask)
{
#define CLR_INTR(intr_bit, _, __, clr_op) if (intr_mask & (intr_bit)) hw->clr_op;
FOR_EACH_ITEM(CLR_INTR, INTR_LIST);
#undef CLR_INTR
}
static inline bool spi_ll_get_intr(spi_dev_t* hw, spi_ll_intr_t intr_mask)
{
#define GET_INTR(intr_bit, _, st_reg, ...) if (intr_mask & (intr_bit) && hw->st_reg) return true;
FOR_EACH_ITEM(GET_INTR, INTR_LIST);
return false;
#undef GET_INTR
}
#undef FOR_EACH_ITEM
#undef INTR_LIST
/**
* Disable the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_disable_int(spi_dev_t *hw)
{
hw->slave.int_trans_done_en = 0;
}
/**
* Clear the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_clear_int_stat(spi_dev_t *hw)
{
hw->slave.trans_done = 0;
hw->dma_int_clr.val = UINT32_MAX;
}
/**
* Set the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_set_int_stat(spi_dev_t *hw)
{
hw->slave.trans_done = 1;
}
/**
* Enable the trans_done interrupt.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_enable_int(spi_dev_t *hw)
{
hw->slave.int_trans_done_en = 1;
}
/**
* Set different interrupt types for the slave.
*
* @param hw Beginning address of the peripheral registers.
* @param int_type Interrupt type
*/
static inline void spi_ll_slave_set_int_type(spi_dev_t *hw, spi_ll_slave_intr_type int_type)
{
switch (int_type) {
case SPI_LL_INT_TYPE_SEG:
hw->dma_int_ena.in_suc_eof = 1;
hw->dma_int_ena.out_total_eof = 1;
hw->slave.int_trans_done_en = 0;
break;
default:
hw->dma_int_ena.in_suc_eof = 0;
hw->dma_int_ena.out_total_eof = 0;
hw->slave.int_trans_done_en = 1;
}
}
/*------------------------------------------------------------------------------
* Slave HD
*----------------------------------------------------------------------------*/
static inline void spi_ll_slave_hd_set_len_cond(spi_dev_t* hw, spi_ll_trans_len_cond_t cond_mask)
{
hw->slv_rd_byte.rdbuf_bytelen_en = (cond_mask & SPI_LL_TRANS_LEN_COND_RDBUF) ? 1 : 0;
hw->slv_rd_byte.wrbuf_bytelen_en = (cond_mask & SPI_LL_TRANS_LEN_COND_WRBUF) ? 1 : 0;
hw->slv_rd_byte.rddma_bytelen_en = (cond_mask & SPI_LL_TRANS_LEN_COND_RDDMA) ? 1 : 0;
hw->slv_rd_byte.wrdma_bytelen_en = (cond_mask & SPI_LL_TRANS_LEN_COND_WRDMA) ? 1 : 0;
}
static inline int spi_ll_slave_get_rx_byte_len(spi_dev_t* hw)
{
return hw->slv_rd_byte.data_bytelen;
}
static inline uint32_t spi_ll_slave_hd_get_last_addr(spi_dev_t* hw)
{
return hw->slave1.last_addr;
}
#undef SPI_LL_RST_MASK
#undef SPI_LL_UNUSED_INT_MASK

View File

@ -0,0 +1,182 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The HAL layer for SPI Slave HD
#include <string.h>
#include "esp_types.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "sdkconfig.h"
#include "soc/spi_periph.h"
#include "soc/lldesc.h"
#include "hal/spi_slave_hd_hal.h"
void slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_config_t *config)
{
memset(hal, 0, sizeof(spi_slave_hd_hal_context_t));
spi_dev_t* hw = SPI_LL_GET_HW(config->host_id);
hal->dev = hw;
//Configure slave
spi_ll_slave_hd_init(hw);
spi_ll_set_addr_bitlen(hw, config->address_bits);
spi_ll_set_command_bitlen(hw, config->command_bits);
spi_ll_set_dummy(hw, config->dummy_bits);
spi_ll_set_rx_lsbfirst(hw, config->rx_lsbfirst);
spi_ll_set_tx_lsbfirst(hw, config->tx_lsbfirst);
spi_ll_slave_set_mode(hw, config->mode, (config->dma_chan != 0));
spi_ll_disable_intr(hw, UINT32_MAX);
spi_ll_clear_intr(hw, UINT32_MAX);
spi_ll_set_intr(hw, SPI_LL_INTR_WR_DONE | SPI_LL_INTR_CMD8);
bool workaround_required = false;
if (!spi_ll_get_intr(hw, SPI_LL_INTR_WR_DONE)) {
hal->intr_not_triggered |= SPI_EV_RECV;
workaround_required = true;
}
if (!spi_ll_get_intr(hw, SPI_LL_INTR_CMD8)) {
hal->intr_not_triggered |= SPI_EV_SEND;
workaround_required = true;
}
if (workaround_required) {
//Workaround if the previous interrupts are not writable
spi_ll_set_intr(hw, SPI_LL_INTR_TRANS_DONE);
}
spi_ll_slave_hd_set_len_cond(hw, SPI_LL_TRANS_LEN_COND_WRBUF |
SPI_LL_TRANS_LEN_COND_WRDMA |
SPI_LL_TRANS_LEN_COND_RDBUF |
SPI_LL_TRANS_LEN_COND_RDDMA);
spi_ll_slave_set_seg_mode(hw, true);
}
void spi_slave_hd_hal_rxdma(spi_slave_hd_hal_context_t *hal, uint8_t *out_buf, size_t len)
{
lldesc_setup_link(hal->dmadesc_rx, out_buf, len, true);
spi_ll_rxdma_reset(hal->dev);
spi_ll_clear_intr(hal->dev, SPI_LL_INTR_WR_DONE);
spi_ll_rxdma_start(hal->dev, &hal->dmadesc_rx[0]);
}
void spi_slave_hd_hal_txdma(spi_slave_hd_hal_context_t *hal, uint8_t *data, size_t len)
{
lldesc_setup_link(hal->dmadesc_tx, data, len, false);
spi_ll_txdma_reset(hal->dev);
spi_ll_clear_intr(hal->dev, SPI_LL_INTR_CMD8);
spi_ll_txdma_start(hal->dev, &hal->dmadesc_tx[0]);
}
static spi_ll_intr_t get_event_intr(spi_event_t ev)
{
spi_ll_intr_t intr = 0;
if (ev & SPI_EV_BUF_TX) intr |= SPI_LL_INTR_RDBUF;
if (ev & SPI_EV_BUF_RX) intr |= SPI_LL_INTR_WRBUF;
if (ev & SPI_EV_SEND) intr |= SPI_LL_INTR_CMD8;
if (ev & SPI_EV_RECV) intr |= SPI_LL_INTR_WR_DONE;
if (ev & SPI_EV_CMD9) intr |= SPI_LL_INTR_CMD9;
if (ev & SPI_EV_CMDA) intr |= SPI_LL_INTR_CMDA;
if (ev & SPI_EV_TRANS) intr |= SPI_LL_INTR_TRANS_DONE;
return intr;
}
bool spi_slave_hd_hal_check_clear_event(spi_slave_hd_hal_context_t* hal, spi_event_t ev)
{
spi_ll_intr_t intr = get_event_intr(ev);
if (spi_ll_get_intr(hal->dev, intr)) {
spi_ll_clear_intr(hal->dev, intr);
return true;
}
return false;
}
bool spi_slave_hd_hal_check_disable_event(spi_slave_hd_hal_context_t* hal, spi_event_t ev)
{
//The trans_done interrupt is used for the workaround when some interrupt is not writable
spi_ll_intr_t intr = get_event_intr(ev);
// Workaround for these interrupts not writable
uint32_t missing_intr = hal->intr_not_triggered & ev;
if (missing_intr) {
if ((missing_intr & SPI_EV_RECV) && spi_ll_get_intr(hal->dev, SPI_LL_INTR_WR_DONE)) {
hal->intr_not_triggered &= ~SPI_EV_RECV;
}
if ((missing_intr & SPI_EV_SEND) && spi_ll_get_intr(hal->dev, SPI_LL_INTR_CMD8)) {
hal->intr_not_triggered &= ~SPI_EV_SEND;
}
if (spi_ll_get_intr(hal->dev, SPI_LL_INTR_TRANS_DONE)) {
spi_ll_disable_intr(hal->dev, SPI_LL_INTR_TRANS_DONE);
}
}
if (spi_ll_get_intr(hal->dev, intr)) {
spi_ll_disable_intr(hal->dev, intr);
return true;
}
return false;
}
void spi_slave_hd_hal_enable_event_intr(spi_slave_hd_hal_context_t* hal, spi_event_t ev)
{
spi_ll_intr_t intr = get_event_intr(ev);
spi_ll_enable_intr(hal->dev, intr);
}
void spi_slave_hd_hal_invoke_event_intr(spi_slave_hd_hal_context_t* hal, spi_event_t ev)
{
spi_ll_intr_t intr = get_event_intr(ev);
// Workaround for these interrupts not writable
if (hal->intr_not_triggered & ev & (SPI_EV_RECV | SPI_EV_SEND)) {
intr |= SPI_LL_INTR_TRANS_DONE;
}
spi_ll_enable_intr(hal->dev, intr);
}
void spi_slave_hd_hal_read_buffer(spi_slave_hd_hal_context_t *hal, int addr, uint8_t *out_data, size_t len)
{
spi_ll_read_buffer_byte(hal->dev, addr, out_data, len);
}
void spi_slave_hd_hal_write_buffer(spi_slave_hd_hal_context_t *hal, int addr, uint8_t *data, size_t len)
{
spi_ll_write_buffer_byte(hal->dev, addr, data, len);
}
int spi_slave_hd_hal_get_last_addr(spi_slave_hd_hal_context_t *hal)
{
return spi_ll_slave_hd_get_last_addr(hal->dev);
}
int spi_slave_hd_hal_get_rxlen(spi_slave_hd_hal_context_t *hal)
{
//this is by -byte
return spi_ll_slave_get_rx_byte_len(hal->dev);
}
int spi_slave_hd_hal_rxdma_get_len(spi_slave_hd_hal_context_t *hal)
{
lldesc_t* desc = &hal->dmadesc_rx[0];
return lldesc_get_received_len(desc, NULL);
}

View File

@ -28,3 +28,19 @@ void lldesc_setup_link(lldesc_t *dmadesc, const void *data, int len, bool isrx)
dmadesc[n - 1].eof = 1; //Mark last DMA desc as end of stream.
dmadesc[n - 1].qe.stqe_next = NULL;
}
int lldesc_get_received_len(lldesc_t* head, lldesc_t** out_next)
{
lldesc_t* desc = head;
int len = 0;
while(desc) {
len += desc->length;
bool eof = desc->eof;
desc = STAILQ_NEXT(desc, qe);
if (eof) break;
}
if (out_next) {
*out_next = desc;
}
return len;
}

View File

@ -100,6 +100,7 @@ INPUT = \
$(IDF_PATH)/components/driver/include/driver/spi_common.h \
$(IDF_PATH)/components/driver/include/driver/spi_master.h \
$(IDF_PATH)/components/driver/include/driver/spi_slave.h \
$(IDF_PATH)/components/driver/include/driver/spi_slave_hd.h \
$(IDF_PATH)/components/driver/$(IDF_TARGET)/include/driver/adc.h \
$(IDF_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
$(IDF_PATH)/components/driver/esp32s2/include/driver/temp_sensor.h \
@ -157,6 +158,7 @@ INPUT = \
## ESP Serial Slave Link
$(IDF_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h \
$(IDF_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio.h \
$(IDF_PATH)/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h \
## ESP Certificate Bundle
$(IDF_PATH)/components/mbedtls/esp_crt_bundle/include/esp_crt_bundle.h \
##

View File

@ -139,9 +139,7 @@ BT_DOCS = ['api-guides/blufi.rst',
SDMMC_DOCS = ['api-reference/peripherals/sdmmc_host.rst',
'api-reference/peripherals/sd_pullup_requirements.rst']
SDIO_SLAVE_DOCS = ['api-reference/peripherals/sdio_slave.rst',
'api-reference/peripherals/esp_slave_protocol.rst',
'api-reference/protocols/esp_serial_slave_link.rst']
SDIO_SLAVE_DOCS = ['api-reference/peripherals/sdio_slave.rst']
MCPWM_DOCS = ['api-reference/peripherals/mcpwm.rst']
@ -168,6 +166,7 @@ ESP32S2_DOCS = ['esp32s2.rst',
'api-guides/usb-console.rst',
'api-reference/peripherals/hmac.rst',
'api-reference/peripherals/ds.rst',
'api-reference/peripherals/spi_slave_hd.rst',
'api-reference/peripherals/temp_sensor.rst'
'']

View File

@ -26,6 +26,7 @@ Peripherals API
SPI Master <spi_master>
SPI Slave <spi_slave>
:esp32: Secure Element <secure_element>
:esp32s2: SPI Slave Half Duplex <spi_slave_hd>
:esp32s2: Temp sensor <temp_sensor>
Timer <timer>
Touch Sensor <touch_pad>

View File

@ -133,10 +133,6 @@ Func 1. Please refer to :ref:`esp_slave_protocol_layer`. There is also a compone
for ESP32 master to communicate with ESP32 SDIO slave, see example :example:`peripherals/sdio`
when programming your host.
.. toctree::
:hidden:
esp_slave_protocol
.. _interrupts:

View File

@ -0,0 +1,114 @@
SPI Slave Half Duplex
=====================
Introduction
------------
The half duplex (HD) mode is a special mode provided by ESP SPI Slave peripheral. Under this mode, the hardware provides more services than the full duplex (FD) mode (the mode for general purpose SPI transactions, see :doc:`spi_slave`). These services reduce the CPU load and the response time of SPI Slave, but the communication format is determined by the hardware. The communication format is always half duplex, so comes the name of Half Duplex Mode.
There are several different types of transactions, determined by the *command* phase of the transaction. Each transaction may consist of the following phases: command, address, dummy, data. The command phase is mandatory, while the other fields may be determined by the command field. During the command, address, dummy phases, the bus is always controlled by the master, while the direction of the data phase depends on the command. The data phase can be either an in phase, for the master to write data to the slave; or an out phase, for the master to read data from the slave.
About the details of how master should communicate with the SPI Slave, see :doc:`/api-reference/protocols/esp_spi_slave_protocol`.
By these different transactions, the slave provide these services to the master:
- A DMA channel for the master to write a great amount of data to the slave.
- A DMA channel for the master to read a great amount of data from the slave.
- Several general purpose registers, shard between the master and the slave.
- Several general purpose interrupts, for the master to interrupt the SW of slave.
Terminology
-----------
- Transaction
- Channel
- Sending
- Receiving
- Data Descriptor
Driver Feature
--------------
- Transaction read/write by master in segments
- Queues for data to send and received
Driver usage
------------
Slave initialization
^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`spi_slave_hd_init` to initialize the SPI bus as well as the peripheral and the driver. The SPI slave will exclusively use the SPI peripheral, pins of the bus before it's deinitialized. Most configurations of the slave should be done as soon as the slave is being initialized.
The :cpp:type:`spi_bus_config_t` specifies how the bus should be initialized, while :cpp:type:`spi_slave_hd_slot_config_t` specifies how the SPI Slave driver should work.
Deinitialization (optional)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`spi_slave_hd_deinit` to uninstall the driver. The resources, including the pins, SPI peripheral, internal memory used by the driver, interrupt sources, will be released by the deinit function.
Send/Receive Data by DMA Channels
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To send data to the master through the sending DMA channel, the application should properly wrap the data to send by a :cpp:type:`spi_slave_hd_data_t` descriptor structure before calling :cpp:func:`spi_slave_hd_queue_trans` with the data descriptor, and the channel argument of :cpp:enumerator:`SPI_SLAVE_CHAN_TX`. The pointers to descriptors are stored in the queue, and the data will be send to the master upon master's RDDMA command in the same order they are put into the queue by :cpp:func:`spi_slave_hd_queue_trans`.
The application should check the result of data sending by calling :cpp:func:`spi_slave_hd_get_trans_res` with the channel set as :cpp:enumerator:`SPI_SLAVE_CHAN_TX`. This function will block until the transaction with command RDDMA from master successfully completes (or timeout). The ``out_trans`` argument of the function will output the pointer of the data descriptor which is just finished.
Receiving data from the master through the receiving DMA channel is quite similar. The application calls :cpp:func:`spi_slave_hd_queue_trans` with proper data descriptor and the channel argument of :cpp:enumerator:`SPI_SLAVE_CHAN_RX`. And the application calls the :cpp:func:`spi_slave_hd_get_trans_res` later to get the descriptor to the receiving buffer, before it handles the data in the receiving buffer.
.. note::
This driver itself doesn't have internal buffer for the data to send, or just received. The application should provide data descriptors for the data buffer to send to master, or to receive data from the master.
The application will have to properly keep the data descriptor as well as the buffer it points to, after the descriptor is successfully sent into the driver internal queue by :cpp:func:`spi_slave_hd_queue_trans`, and before returned by :cpp:func:`spi_slave_hd_get_trans_res`. During this period, the hardware as well as the driver may read or write to the buffer and the descriptor when required at any time.
Please note that the buffer doesn't have to be fully sent or filled before it's terminated. For example, in the segment transaction mode, the master has to send CMD7 to terminate a WRDMA transaction, or send CMD8 to terminate a RDDMA transaction (in segments), no matter the send (receive) buffer is used up (full) or not.
.. _spi_slave_hd_data_arguments:
Using Data Arguments
^^^^^^^^^^^^^^^^^^^^
Sometimes you may have initiator (sending data descriptor) and closure (handling returned descriptors) functions in different places. When you get the returned data descriptor in the closure, you may need some extra information when handle the finished data descriptor. For example, you may want to know which round it is for the returned descriptor, when you send the same piece of data for several times.
Set the ``arg`` member in the data descriptor to an variable indicating the transaction (by force casting), or point it to a a structure which wraps all the information you may need when handling the sending/receiving data. Then you can get what you need in your closure.
.. _spi_slave_hd_callbacks:
Using callbacks
^^^^^^^^^^^^^^^
.. note::
These callbacks are called in the ISR, so that they are fast enough. However, you may need to be very careful to write the code in the ISR. The callback should return as soon as possible. No delay or blocking operations are allowed.
The :cpp:type:`spi_slave_hd_intr_config_t` member in the :cpp:type:`spi_slave_hd_slot_config_t` configuration structure passed when initialize the SPI Slave HD driver, allows you having callbacks for each events you may concern.
The corresponding interrupt for each callbacks that is not *NULL* will enabled, so that the callbacks can be called immediately when the events happen. You don't need to provide callbacks for the unconcerned events.
The ``arg`` member in the configuration structure can help you pass some context to the callback, or indicate which SPI Slave instance when you are using the same callbacks for several SPI Slave peripherals. Set the ``arg`` member to an variable indicating the SPI Slave instance (by force casting), or point it to a context structure. All the callbacks will be called with this ``arg`` argument you set when the callbacks are initialized.
There are two other arguments: the ``event`` and the ``awoken``. The ``event`` passes the information of the current event to the callback. The :cpp:type:`spi_slave_hd_event_t` type contains the information of the event, for example, event type, the data descriptor just finished (The :ref:`data argument <spi_slave_hd_data_arguments>` will be very useful in this case!). The ``awoken`` argument is an output one, telling the ISR there are tasks are awoken after this callback, and the ISR should call `portYIELD_FROM_ISR()` to do task scheduling. Just pass the ``awoken`` argument to all FreeRTOS APIs which may unblock tasks, and the awoken will be returned to the ISR.
Writing/Reading Shared Registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`spi_slave_hd_write_buffer` to write the shared buffer, and :cpp:func:`spi_slave_hd_read_buffer` to read the shared buffer.
.. note::
On {IDF_TARGET_NAME}, the shared registers are read/written in words by the application, but read/written in bytes by the master. There's no guarantee four continuous bytes read from the master are from the same word written by the slave's application. It's also possible that if the slave reads a word while the master is writing bytes of the word, the slave may get one word with half of them just written by the master, and the other half hasn't been written into.
The master can confirm that the word is not in transition by reading the word twice and comparing the values.
For the slave, it will be more difficult to ensure the word is not in transition because the process of master writing four bytes can be very long (32 SPI clocks). You can put some CRC in the last (largest address) byte of a word so that when the byte is written, the word is sure to be all written.
Due to the conflicts there may be among read/write from SW (worse if there are multi cores) and master, it is suggested that a word is only used in one direction (only written by master or only written by the slave).
Receiving General Purpose Interrupts From the Master
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the master sends CMD 0x08, 0x09 or 0x0A, the slave corresponding will be triggered. Currently the CMD8 is permanently used to indicate the termination of RDDMA segments. To receiving general purpose interrupts, register callbacks for CMD 0x09 and 0x0A when the slave is initialized, see :ref:`spi_slave_hd_callbacks`.
API reference
-------------
.. include-build-file:: inc/spi_slave_hd.inc

View File

@ -5,6 +5,30 @@ This document describes the process of initialization of an ESP SDIO Slave devic
The ESP SDIO Slave protocol was created to implement the communication between SDIO host and slave, because the SDIO specification only shows how to access the custom region of a card (by sending CMD52 and CMD53 to Functions 1-7) without any details regarding the underlying hardware implementation.
.. _esp_sdio_slave_caps:
SDIO Slave Capabilities of Espressif chips
------------------------------------------
The services provided by SDIO Slave peripherals of Espressif chips are different. See the table below:
+-----------------------------------------------------------+-------+----------+
| | ESP32 | ESP32-S2 |
+===========================================================+=======+==========+
| SDIO slave | Y | N |
+-----------------------------------------------------------+-------+----------+
| :ref:`Tohost intr <esp_sdio_slave_interrupts>` | 8 | |
+-----------------------------------------------------------+-------+----------+
| :ref:`Frhost intr <esp_sdio_slave_interrupts>` | 8 | |
+-----------------------------------------------------------+-------+----------+
| :ref:`TX DMA <esp_sdio_slave_send_fifo>` | Y | |
+-----------------------------------------------------------+-------+----------+
| :ref:`RX DMA <esp_sdio_slave_rcv_fifo>` | Y | |
+-----------------------------------------------------------+-------+----------+
| :ref:`Shared registers <esp_sdio_slave_shared_registers>` | 56\* | |
+-----------------------------------------------------------+-------+----------+
- \* Not including the interrupt registers
.. _esp_slave_init:
@ -90,6 +114,7 @@ The :doc:`ESP Serial Slave Link </api-reference/protocols/esp_serial_slave_link>
.. _{IDF_TARGET_NAME} Technical Reference Manual: {IDF_TARGET_TRM_EN_URL}
.. _esp_sdio_slave_shared_registers:
Slave register table
^^^^^^^^^^^^^^^^^^^^
@ -137,16 +162,18 @@ The slave will respond with data that has a length equal to the length field of
1. Send CMD53 in block mode, block count=2 (1024 bytes) to address 0x1F3F9=0x1F800-**1031**.
2. Then send CMD53 in byte mode, byte count=8 (or 7 if your controller supports that) to address 0x1F7F9=0x1F800-**7**.
.. _esp_sdio_slave_interrupts:
Interrupts
^^^^^^^^^^
SDIO interrupts are "level sensitive". For host interrupts, the slave sends an interrupt by pulling the DAT1 line down at a proper time. The host detects when the interrupt line is pulled down and reads the INT_ST register to determine the source of the interrupt. After that, the host can clear the interrupt bits by writing the INT_CLR register and process the interrupt. The host can also mask unneeded sources by clearing the bits in the INT_ENA register corresponding to the sources. If all the sources are cleared (or masked), the DAT1 line goes inactive.
:cpp:type:`sdio_slave_hostint_t` (:doc:`sdio_slave`) shows the bit definition corresponding to host interrupt sources.
On ESP32, the corresponding host_int bits are: bit 0 to bit 7.
For slave interrupts, the host sends a transfer to write the SLAVE_INT register. Once a bit is set to 1, the slave hardware and the driver will detect it and inform the application.
.. _esp_sdio_slave_rcv_fifo:
Receiving FIFO
^^^^^^^^^^^^^^
@ -158,6 +185,7 @@ To write to the slave's receiving FIFO, the host should complete the following s
3. **Write to the FIFO address with CMD53**. Note that the *requested length* should not exceed the length calculated at Step 2, and the FIFO address is related to *requested length*.
4. **Calculate used buffers**. Note that a partially used buffer at the tail is counted as used.
.. _esp_sdio_slave_send_fifo:
Sending FIFO
^^^^^^^^^^^^

View File

@ -12,7 +12,16 @@ bus drivers.
After an `esp_serial_slave_link` device is initialized properly, the application can use it to communicate with the ESP
slave devices conveniently.
For more details about ESP32 SDIO slave protocol, see document :doc:`/api-reference/peripherals/esp_slave_protocol`.
Espressif Device protocols
--------------------------
For more details about Espressif device protocols, see the following documents.
.. toctree::
:maxdepth: 1
esp_sdio_slave_protocol
esp_spi_slave_protocol
Terminology
-----------
@ -51,7 +60,7 @@ Services provided by ESP slave
There are some common services provided by the Espressif slaves:
1. Tohost Interrupts: The slave can inform the master about certain events by the interrupt line.
1. Tohost Interrupts: The slave can inform the master about certain events by the interrupt line. (optional)
2. Frhost Interrupts: The master can inform the slave about certain events.
@ -71,12 +80,20 @@ There are some common services provided by the Espressif slaves:
5. Shared registers: the master can read some part of the registers on the slave, and also write
these registers to let the slave read.
The services provided by the slave depends on the slave's model. See
:ref:`esp_sdio_slave_caps` and :ref:`esp_spi_slave_caps` for more details.
Initialization of ESP SDIO Slave Link
-------------------------------------
Initialization of ESP Serial Slave Link
---------------------------------------
The ESP SDIO slave link (ESSL SDIO) devices relies on the sdmmc component. The ESSL device should
be initialized as below:
.. _essl_sdio_slave_init:
ESP SDIO Slave
^^^^^^^^^^^^^^
The ESP SDIO slave link (ESSL SDIO) devices relies on the sdmmc component. It includes the usage
of communicating with ESP SDIO Slave device via SDSPI feature. The ESSL device should be
initialized as below:
1. Initialize a sdmmc card (see :doc:` Document of SDMMC driver </api-reference/storage/sdmmc>`)
structure.
@ -91,14 +108,23 @@ be initialized as below:
5. Call :cpp:func:`essl_wait_for_ready` to wait for the slave to be ready.
ESP SPI Slave
^^^^^^^^^^^^^
.. note::
If you are communicating with the ESP SDIO Slave device through SPI interface, you should use
the :ref:`SDIO interface <essl_sdio_slave_init>` instead.
Hasn't been supported yet.
APIs
----
After the initialization process above is performed, you can call the APIs below to make use of
the services provided by the slave:
Interrupts
^^^^^^^^^^
Tohost Interrupts (optional)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. Call :cpp:func:`essl_get_intr_ena` to know which events will trigger the interrupts to the master.
@ -109,7 +135,10 @@ Interrupts
4. When interrupt is triggered, call :cpp:func:`essl_get_intr` to know which events are active,
and call :cpp:func:`essl_clear_intr` to clear them.
5. Call :cpp:func:`essl_send_slave_intr` to trigger general purpose interrupt of the slave.
Frhost Interrupts
^^^^^^^^^^^^^^^^^
1. Call :cpp:func:`essl_send_slave_intr` to trigger general purpose interrupt of the slave.
TX FIFO
^^^^^^^
@ -152,3 +181,4 @@ API Reference
.. include-build-file:: inc/essl.inc
.. include-build-file:: inc/essl_sdio.inc
.. include-build-file:: inc/essl_spi.inc

View File

@ -0,0 +1,170 @@
ESP SPI Slave HD (Half Duplex) Mode Protocol
============================================
.. note::
This protocol is only for ESP32-S2. The driver for other chip versions hasn't be developed
yet.
.. _esp_spi_slave_caps:
SPI Slave Capabilities of Espressif chips
-----------------------------------------
+--------------------+-------+----------+
| | ESP32 | ESP32-S2 |
+====================+=======+==========+
| SPI Slave HD | N | Y (v2) |
+--------------------+-------+----------+
| Tohost intr | | N |
+--------------------+-------+----------+
| Frhost intr | | 2 \* |
+--------------------+-------+----------+
| TX DMA | | Y |
+--------------------+-------+----------+
| RX DMA | | Y |
+--------------------+-------+----------+
| Shared registers | | 72 |
+--------------------+-------+----------+
Introduction
------------
In the half duplex mode, the master has to use the protocol defined by the slave to communicate
with the slave. Each transaction may consists of the following phases (list by the order they
should exist):
- Command: 8-bit, master to slave
This phase determines the rest phases of the transactions. See :ref:`spi_slave_hd_supported_cmds`.
- Address: 8-bit, master to slave, optional
For some commands (WRBUF, RDBUF), this phase specifies the address of shared buffer to write
to/read from. For other commands with this phase, they are meaningless, but still have to
exist in the transaction.
- Dummy: 8-bit, floating, optional
This phase is the turn around time between the master and the slave on the bus, and also
provides enough time for the slave to prepare the data to send to master.
- Data: variable length, the direction is also determined by the command.
This may be a data OUT phase, in which the direction is slave to master, or a data IN phase,
in which the direction is master to slave.
The *direction* means which side (master or slave) controls the MOSI, MISO, WP and HD pins.
Data IO Modes
-------------
In some IO modes, more data wires can be use to send the data. As a result, the SPI clock cycles
required for the same amount of data will be less than in 1-bit mode. For example, in QIO mode,
address and data (IN and OUT) should be sent on all 4 data wires (MOSI, MISO, WP, and HD). Here's
the modes supported by ESP32-S2 SPI slave and the wire number used in corresponding modes.
+-------+------------+------------+--------------+---------+
| Mode | command WN | address WN | dummy cycles | data WN |
+=======+============+============+==============+=========+
| 1-bit | 1 | 1 | 1 | 1 |
+-------+------------+------------+--------------+---------+
| DOUT | 1 | 1 | 4 | 2 |
+-------+------------+------------+--------------+---------+
| DIO | 1 | 2 | 4 | 2 |
+-------+------------+------------+--------------+---------+
| QOUT | 1 | 1 | 4 | 4 |
+-------+------------+------------+--------------+---------+
| QIO | 1 | 4 | 4 | 4 |
+-------+------------+------------+--------------+---------+
| QPI | 4 | 4 | 4 | 4 |
+-------+------------+------------+--------------+---------+
Normally, which mode is used is determined is determined by the command sent by the master (See
:ref:`spi_slave_hd_supported_cmds`), except from the QPI mode.
QPI Mode
^^^^^^^^
The QPI mode is a special state of the SPI Slave. The master can send ENQPI command to put the
slave into the QPI mode state. In the QPI mode, the command is also sent in 4-bit, thus it's not
compatible with the normal modes. The master should only send QPI commands when the slave is in
the QPI mode. To exit form the QPI mode, master can send EXQPI command.
.. _spi_slave_hd_supported_cmds:
Supported Commands
------------------
.. note::
The command name are in a master-oriented direction. For example, WRBUF means master writes
the buffer of slave.
+----------+---------------------+---------+----------+----------------------------------------------------------+
| Name | Description | Command | Address | Data |
+==========+=====================+=========+==========+==========================================================+
| WRBUF | Write buffer | 0x01 | Buf addr | master to slave, no longer than buffer size |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| RDBUF | Read buffer | 0x02 | Buf addr | slave to master, no longer than buffer size |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| WRDMA | Write DMA | 0x03 | 8 bits | master to slave, no longer than length provided by slave |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| RDDMA | Read DMA | 0x04 | 8 bits | slave to master, no longer than length provided by slave |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| SEG_DONE | Segments done | 0x05 | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| ENQPI | Enter QPI mode | 0x06 | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| WR_DONE | Write segments done | 0x07 | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| CMD8 | Interrupt | 0x08 | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| CMD9 | Interrupt | 0x09 | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| CMDA | Interrupt | 0x0A | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
| EXQPI | Exit QPI mode | 0xDD | - | - |
+----------+---------------------+---------+----------+----------------------------------------------------------+
Moreover, WRBUF, RDBUF, WRDMA, RDDMA commands have their 2-bit and 4-bit version. To do
transactions in 2-bit or 4-bit mode, send the original command ORed by the corresponding command
mask below. For example, command 0xA1 means WRBUF in QIO mode.
+-------+------+
| Mode | Mask |
+=======+======+
| 1-bit | 0x00 |
+-------+------+
| DOUT | 0x10 |
+-------+------+
| DIO | 0x50 |
+-------+------+
| QOUT | 0x20 |
+-------+------+
| QIO | 0xA0 |
+-------+------+
| QPI | 0xA0 |
+-------+------+
Segment Transaction Mode
------------------------
Segment transaction mode is the only mode supported by the SPI Slave HD driver for now. In this
mode, for a transaction the slave load onto the DMA, the master is allowed to read or write in
segments. This way the master doesn't have to prepare large buffer as the size of data provided
by the slave. After the master finish reading/writing a buffer, it has to send corresponding
termination command to the slave as a synchronization signal. The slave driver will update new
data (if exist) onto the DMA upon seeing the termination command.
The termination command is WR_DONE (0x07) for the WRDMA, and CMD8 (0x08) for the RDDMA.
Here's an example for the flow the master read data from the slave DMA:
1. The slave loads 4092 bytes of data onto the RDDMA
2. The master do seven RDDMA transactions, each of them are 512 bytes long, and reads the first
3584 bytes from the slave
3. The master do the last RDDMA transaction of 512 bytes (equal, longer or shorter than the total
length loaded by the slave are all allowed). The first 508 bytes are valid data from the
slave, while the last 4 bytes are meaningless bytes.
4. The master sends CMD8 to the slave
5. The slave loads another 4092 bytes of data onto the RDDMA
6. The master can start new reading transactions after it sends the CMD8

View File

@ -16,7 +16,7 @@ Application Protocols
mDNS <mdns>
Modbus <modbus>
Websocket Client <esp_websocket_client>
:esp32: ESP Serial Slave Link <esp_serial_slave_link>
ESP Serial Slave Link <esp_serial_slave_link>
Certificate Bundle <esp_crt_bundle>
Code examples for this API section are provided in the :example:`protocols` directory of ESP-IDF examples.

View File

@ -1 +0,0 @@
.. include:: ../../../en/api-reference/peripherals/esp_slave_protocol.rst

View File

@ -8,7 +8,7 @@
ADC <adc>
DAC <dac>
GPIO (including RTC low power I/O) <gpio>
GPIO (包括 RTC 低功耗 I/O) <gpio>
:esp32s2: HMAC <hmac>
:esp32s2: Digital Signature <ds>
I2C <i2c>
@ -24,11 +24,11 @@
SPI Master <spi_master>
SPI Slave <spi_slave>
:esp32: Secure Element <secure_element>
:esp32s2: SPI Slave 半双工 (half duplex) <spi_slave_hd>
:esp32s2: Temp sensor <temp_sensor>
Timer <timer>
Touch Sensor <touch_pad>
TWAI <twai>
UART <uart>
本部分的 API 示例代码存放在 ESP-IDF 示例项目的 :example:`peripherals` 目录下。
本部分的 API 示例代码存放在 ESP-IDF 示例项目的 :example:`peripherals` 目录下。

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/peripherals/spi_slave_hd.rst

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/protocols/esp_sdio_slave_protocol.rst

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/protocols/esp_spi_slave_protocol.rst

View File

@ -16,7 +16,7 @@
ESP-MQTT <mqtt>
Modbus slave <modbus>
Local Control <esp_local_ctrl>
:esp32: ESP Serial Slave Link <esp_serial_slave_link>
ESP Serial Slave Link <esp_serial_slave_link>
Certificate Bundle <esp_crt_bundle>
此 API 部分的示例代码在 ESP-IDF 示例工程的 :example:`protocols` 目录下提供。

View File

@ -259,6 +259,7 @@ class PublicHeaderChecker:
if item.startswith("-D"):
include_dir_flags.append(item.replace('\\','')) # removes escaped quotes, eg: -DMBEDTLS_CONFIG_FILE=\\\"mbedtls/esp_config.h\\\"
include_dir_flags.append("-I" + os.path.join(project_dir, "build", "config"))
include_dir_flags.append("-DCI_HEADER_CHECK")
sdkconfig_h = os.path.join(project_dir, "build", "config", "sdkconfig.h")
# prepares a main_c file for easier sdkconfig checks and avoid compilers warning when compiling headers directly
with open(sdkconfig_h, "a") as f: