mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(spi_master): support dma transfer with psram directly
This commit is contained in:
@@ -27,6 +27,6 @@ idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS ${public_include}
|
||||
REQUIRES esp_pm
|
||||
PRIV_REQUIRES esp_timer esp_mm esp_driver_gpio
|
||||
PRIV_REQUIRES esp_timer esp_mm esp_driver_gpio spi_flash esp_psram #For CONFIG_SPIRAM_SPEED
|
||||
LDFRAGMENTS "linker.lf"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ menu "ESP-Driver:SPI Configurations"
|
||||
help
|
||||
Normally only the ISR of SPI slave is placed in the IRAM, so that it
|
||||
can work without the flash when interrupt is triggered.
|
||||
For other functions, there's some possibility that the flash cache
|
||||
For other functions, there is some possibility that the flash cache
|
||||
miss when running inside and out of SPI functions, which may increase
|
||||
the interval of SPI transactions.
|
||||
Enable this to put ``queue_trans``, ``get_trans_result`` and
|
||||
|
||||
@@ -103,6 +103,7 @@ typedef struct {
|
||||
*/
|
||||
} spi_device_interface_config_t;
|
||||
|
||||
// Input flags
|
||||
#define SPI_TRANS_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode
|
||||
#define SPI_TRANS_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode
|
||||
#define SPI_TRANS_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer.
|
||||
@@ -116,6 +117,11 @@ typedef struct {
|
||||
#define SPI_TRANS_MODE_OCT (1<<10) ///< Transmit/receive data in 8-bit mode
|
||||
#define SPI_TRANS_MULTILINE_ADDR SPI_TRANS_MODE_DIOQIO_ADDR ///< The data lines used at address phase is the same as data phase (otherwise, only one data line is used at address phase)
|
||||
#define SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL (1<<11) ///< By default driver will automatically re-alloc dma buffer if it doesn't meet hardware alignment or dma_capable requirements, this flag is for you to disable this feature, you will need to take care of the alignment otherwise driver will return you error ESP_ERR_INVALID_ARG
|
||||
#define SPI_TRANS_DMA_USE_PSRAM (1<<12) ///< Use PSRAM for DMA buffer directly, has speed limit, but no temp buffer and save memory
|
||||
|
||||
// Output flags
|
||||
#define SPI_TRANS_DMA_RX_FAIL (1<<30) ///< RX transaction data lose flag, indicate DMA RX overflow
|
||||
#define SPI_TRANS_DMA_TX_FAIL (1<<31) ///< TX transaction data lose flag, indicate DMA TX underflow
|
||||
|
||||
/**
|
||||
* This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.
|
||||
|
||||
@@ -116,6 +116,7 @@ We have two bits to control the interrupt:
|
||||
#include "esp_private/spi_common_internal.h"
|
||||
#include "esp_private/spi_master_internal.h"
|
||||
#include "esp_private/esp_clk_tree_common.h"
|
||||
#include "esp_private/cache_utils.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#include "esp_log.h"
|
||||
@@ -131,10 +132,6 @@ We have two bits to control the interrupt:
|
||||
#include "hal/spi_hal.h"
|
||||
#include "hal/spi_ll.h"
|
||||
#include "hal/hal_utils.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
#include "esp_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SPI_MASTER_ISR_IN_IRAM
|
||||
#define SPI_MASTER_ISR_ATTR IRAM_ATTR
|
||||
@@ -162,7 +159,25 @@ We have two bits to control the interrupt:
|
||||
|
||||
#define SPI_PERIPH_SRC_FREQ_MAX (80*1000*1000) //peripheral hardware limitation for clock source into peripheral
|
||||
|
||||
static const char *SPI_TAG = "spi_master";
|
||||
/**
|
||||
* The approx time for dma setup and pop data into peripheral
|
||||
* This time is theoretically inverse proportion to the PSRAM speed(bandwidth), and direct proportion to the SPI speed, but hard to accurately calculated
|
||||
* Below is an engineering value based on experience test result, e.g. delay 5us for 20MHz PSRAM speed, 1us for 80M
|
||||
* Then the formula is: Delay_time = K * (SPI_SPEED / PSRAM_SPEED) + B
|
||||
* delay
|
||||
* ▲
|
||||
* │ x
|
||||
* │ x
|
||||
* │ x
|
||||
* │ x x
|
||||
* ─┼─────────────────►
|
||||
* │ psram speed
|
||||
*/
|
||||
#define K_EDMA_SETUP_RATIO 1 / 50000
|
||||
#define B_EDMA_SETUP_TIME_US 1
|
||||
#define SPI_EDMA_SETUP_TIME_US(spi_speed) ((spi_speed) * K_EDMA_SETUP_RATIO / CONFIG_SPIRAM_SPEED + B_EDMA_SETUP_TIME_US)
|
||||
|
||||
ESP_LOG_ATTR_TAG_DRAM(SPI_TAG, "spi_master");
|
||||
#define SPI_CHECK(a, str, ret_val, ...) ESP_RETURN_ON_FALSE_ISR(a, ret_val, SPI_TAG, str, ##__VA_ARGS__)
|
||||
|
||||
typedef struct spi_device_t spi_device_t;
|
||||
@@ -835,6 +850,13 @@ static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_
|
||||
if (dev->cfg.pre_cb) {
|
||||
dev->cfg.pre_cb(trans);
|
||||
}
|
||||
#if CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE
|
||||
spi_hal_clear_intr_mask(hal, SPI_LL_INTR_IN_FULL | SPI_LL_INTR_OUT_EMPTY);
|
||||
if (esp_ptr_dma_ext_capable(hal_trans.send_buffer)) {
|
||||
// ! Delay here is required for EDMA to pass data from PSRAM to GPSPI
|
||||
esp_rom_delay_us(SPI_EDMA_SETUP_TIME_US(hal_dev->timing_conf.real_freq));
|
||||
}
|
||||
#endif
|
||||
//Kick off transfer
|
||||
spi_hal_user_start(hal);
|
||||
}
|
||||
@@ -932,6 +954,26 @@ static void SPI_MASTER_ISR_ATTR spi_post_sct_trans(spi_host_t *host)
|
||||
}
|
||||
#endif //#if SOC_SPI_SCT_SUPPORTED
|
||||
|
||||
static void SPI_MASTER_ISR_ATTR spi_trans_dma_error_check(spi_host_t *host)
|
||||
{
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM //error checks only for psram dma
|
||||
if (!host->sct_mode_enabled) {
|
||||
if (esp_ptr_external_ram(host->cur_trans_buf.buffer_to_rcv) && spi_hal_get_intr_mask(&host->hal, SPI_LL_INTR_IN_FULL)) {
|
||||
host->cur_trans_buf.trans->flags |= SPI_TRANS_DMA_RX_FAIL;
|
||||
ESP_DRAM_LOGE(SPI_TAG, "DMA RX overflow detected");
|
||||
} else {
|
||||
host->cur_trans_buf.trans->flags &= ~SPI_TRANS_DMA_RX_FAIL;
|
||||
}
|
||||
if (esp_ptr_external_ram(host->cur_trans_buf.buffer_to_send) && spi_hal_get_intr_mask(&host->hal, SPI_LL_INTR_OUT_EMPTY)) {
|
||||
host->cur_trans_buf.trans->flags |= SPI_TRANS_DMA_TX_FAIL;
|
||||
ESP_DRAM_LOGE(SPI_TAG, "DMA TX underflow detected");
|
||||
} else {
|
||||
host->cur_trans_buf.trans->flags &= ~SPI_TRANS_DMA_TX_FAIL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is run in interrupt context.
|
||||
static void SPI_MASTER_ISR_ATTR spi_intr(void *arg)
|
||||
{
|
||||
@@ -981,6 +1023,7 @@ static void SPI_MASTER_ISR_ATTR spi_intr(void *arg)
|
||||
(void)ret;
|
||||
}
|
||||
#endif
|
||||
spi_trans_dma_error_check(host);
|
||||
}
|
||||
|
||||
#if SOC_SPI_SCT_SUPPORTED
|
||||
@@ -1132,7 +1175,9 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl
|
||||
SPI_CHECK(trans_desc->length <= SPI_LL_CPU_MAX_BIT_LEN, "txdata transfer > hardware max supported len", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->rxlength <= SPI_LL_CPU_MAX_BIT_LEN, "rxdata transfer > hardware max supported len", ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
|
||||
if (esp_ptr_external_ram(trans_desc->tx_buffer) || esp_ptr_external_ram(trans_desc->rx_buffer)){
|
||||
SPI_CHECK(spi_flash_cache_enabled(), "Using PSRAM must when cache is enabled", ESP_ERR_INVALID_STATE);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -1154,6 +1199,45 @@ static SPI_MASTER_ISR_ATTR void uninstall_priv_desc(spi_trans_priv_t* trans_buf)
|
||||
}
|
||||
}
|
||||
|
||||
static SPI_MASTER_ISR_ATTR esp_err_t setup_dma_priv_buffer(uint32_t *buffer, uint32_t len, uint32_t alignment, bool is_tx, uint32_t flags, uint32_t **ret_buffer)
|
||||
{
|
||||
bool unaligned = ((((uint32_t)buffer) | len) & (alignment - 1));
|
||||
#if !SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
if (!((flags & SPI_TRANS_DMA_USE_PSRAM) && esp_ptr_dma_ext_capable(buffer))) {
|
||||
// tx don't need align on addr or length on those chips
|
||||
unaligned = is_tx ? false : (((uint32_t)buffer) & (alignment - 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SOC_PSRAM_DMA_CAPABLE
|
||||
if ((flags & SPI_TRANS_DMA_USE_PSRAM) && esp_ptr_dma_ext_capable(buffer)) {
|
||||
// dma psram don't do additional copy, check only
|
||||
ESP_RETURN_ON_FALSE_ISR(!unaligned, ESP_ERR_INVALID_ARG, SPI_TAG, "%s buffer addr and length should align to %ld Byte to use PSRAM, use heap_caps_aligned_calloc() may helpful", is_tx ? "TX" : "RX", alignment);
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_cache_msync((void *)buffer, len, is_tx ? ESP_CACHE_MSYNC_FLAG_DIR_C2M : ESP_CACHE_MSYNC_FLAG_DIR_M2C), SPI_TAG, "sync failed for %s buffer", is_tx ? "TX" : "RX");
|
||||
*ret_buffer = buffer;
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
if ((!esp_ptr_dma_capable(buffer) || unaligned)) {
|
||||
ESP_RETURN_ON_FALSE_ISR(!(flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL), ESP_ERR_INVALID_ARG, SPI_TAG, "Set flag SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL but %s addr&len not align to %d, or not dma_capable", is_tx ? "TX" : "RX", alignment);
|
||||
//if buf in the desc not DMA-capable, or not bytes aligned to alignment, malloc a new one
|
||||
ESP_EARLY_LOGD(SPI_TAG, "Allocate %s buffer for DMA", is_tx ? "TX" : "RX");
|
||||
len = (len + alignment - 1) & (~(alignment - 1)); // up align alignment
|
||||
uint32_t *temp = heap_caps_aligned_alloc(alignment, len, MALLOC_CAP_DMA);
|
||||
ESP_RETURN_ON_FALSE_ISR(temp != NULL, ESP_ERR_NO_MEM, SPI_TAG, "Failed to allocate priv %s buffer", is_tx ? "TX" : "RX");
|
||||
|
||||
if (is_tx) {
|
||||
memcpy(temp, buffer, len);
|
||||
}
|
||||
buffer = temp;
|
||||
}
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_cache_msync((void *)buffer, len, is_tx ? ESP_CACHE_MSYNC_FLAG_DIR_C2M : ESP_CACHE_MSYNC_FLAG_DIR_M2C), SPI_TAG, "sync failed for %s buffer", is_tx ? "TX" : "RX");
|
||||
#endif
|
||||
*ret_buffer = buffer;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_host_t *host, spi_trans_priv_t* priv_desc)
|
||||
{
|
||||
spi_transaction_t *trans_desc = priv_desc->trans;
|
||||
@@ -1170,70 +1254,36 @@ static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_host_t *host, spi_trans
|
||||
}
|
||||
|
||||
// tx memory assign
|
||||
const uint32_t *send_ptr;
|
||||
uint32_t *send_ptr;
|
||||
if (trans_desc->flags & SPI_TRANS_USE_TXDATA) {
|
||||
send_ptr = (uint32_t *)&trans_desc->tx_data[0];
|
||||
} else {
|
||||
//if not use TXDATA neither tx_buffer, tx data assigned to NULL
|
||||
send_ptr = trans_desc->tx_buffer ;
|
||||
send_ptr = (uint32_t *)trans_desc->tx_buffer ;
|
||||
}
|
||||
|
||||
uint32_t tx_byte_len = (trans_desc->length + 7) / 8;
|
||||
uint32_t rx_byte_len = (trans_desc->rxlength + 7) / 8;
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
bool tx_unaligned = ((((uint32_t)send_ptr) | tx_byte_len) & (alignment - 1));
|
||||
bool rx_unaligned = ((((uint32_t)rcv_ptr) | rx_byte_len) & (alignment - 1));
|
||||
#else
|
||||
bool tx_unaligned = false; //tx don't need align on addr or length, for other chips
|
||||
bool rx_unaligned = (((uint32_t)rcv_ptr) & (alignment - 1));
|
||||
#endif
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
if (send_ptr && bus_attr->dma_enabled) {
|
||||
if ((!esp_ptr_dma_capable(send_ptr) || tx_unaligned)) {
|
||||
ESP_RETURN_ON_FALSE(!(trans_desc->flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL), ESP_ERR_INVALID_ARG, SPI_TAG, "Set flag SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL but TX buffer addr&len not align to %d byte, or not dma_capable", alignment);
|
||||
//if txbuf in the desc not DMA-capable, or not bytes aligned to alignment, malloc a new one
|
||||
ESP_EARLY_LOGD(SPI_TAG, "Allocate TX buffer for DMA");
|
||||
tx_byte_len = (tx_byte_len + alignment - 1) & (~(alignment - 1)); // up align alignment
|
||||
uint32_t *temp = heap_caps_aligned_alloc(alignment, tx_byte_len, MALLOC_CAP_DMA);
|
||||
if (temp == NULL) {
|
||||
goto clean_up;
|
||||
}
|
||||
|
||||
memcpy(temp, send_ptr, (trans_desc->length + 7) / 8);
|
||||
send_ptr = temp;
|
||||
ret = setup_dma_priv_buffer(send_ptr, (trans_desc->length + 7) / 8, alignment, true, trans_desc->flags, &send_ptr);
|
||||
if (ret != ESP_OK) {
|
||||
goto clean_up;
|
||||
}
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
esp_err_t ret = esp_cache_msync((void *)send_ptr, tx_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
assert(ret == ESP_OK);
|
||||
(void)ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (rcv_ptr && bus_attr->dma_enabled) {
|
||||
if ((!esp_ptr_dma_capable(rcv_ptr) || rx_unaligned)) {
|
||||
ESP_RETURN_ON_FALSE(!(trans_desc->flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL), ESP_ERR_INVALID_ARG, SPI_TAG, "Set flag SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL but RX buffer addr&len not align to %d byte, or not dma_capable", alignment);
|
||||
//if rxbuf in the desc not DMA-capable, or not aligned to alignment, malloc a new one
|
||||
ESP_EARLY_LOGD(SPI_TAG, "Allocate RX buffer for DMA");
|
||||
rx_byte_len = (rx_byte_len + alignment - 1) & (~(alignment - 1)); // up align alignment
|
||||
rcv_ptr = heap_caps_aligned_alloc(alignment, rx_byte_len, MALLOC_CAP_DMA);
|
||||
if (rcv_ptr == NULL) {
|
||||
goto clean_up;
|
||||
}
|
||||
ret = setup_dma_priv_buffer(rcv_ptr, (trans_desc->rxlength + 7) / 8, alignment, false, trans_desc->flags, &rcv_ptr);
|
||||
if (ret != ESP_OK) {
|
||||
goto clean_up;
|
||||
}
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
// do invalid here to hold on cache status to avoid hardware auto write back during dma transaction
|
||||
esp_err_t ret = esp_cache_msync((void *)rcv_ptr, rx_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
|
||||
assert(ret == ESP_OK);
|
||||
(void)ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
priv_desc->buffer_to_send = send_ptr;
|
||||
priv_desc->buffer_to_rcv = rcv_ptr;
|
||||
return ESP_OK;
|
||||
|
||||
clean_up:
|
||||
uninstall_priv_desc(priv_desc);
|
||||
return ESP_ERR_NO_MEM;
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t SPI_MASTER_ATTR spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, uint32_t ticks_to_wait)
|
||||
@@ -1312,7 +1362,7 @@ esp_err_t SPI_MASTER_ATTR spi_device_get_trans_result(spi_device_handle_t handle
|
||||
}
|
||||
(*trans_desc) = trans_buf.trans;
|
||||
|
||||
return ESP_OK;
|
||||
return (trans_buf.trans->flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL)) ? ESP_ERR_INVALID_STATE : ESP_OK;
|
||||
}
|
||||
|
||||
//Porcelain to do one blocking transmission.
|
||||
@@ -1461,6 +1511,8 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle,
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
spi_trans_dma_error_check(host);
|
||||
uint32_t trans_flags = host->cur_trans_buf.trans->flags; // save the flags before bus_lock release
|
||||
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE //invalidate here to let user access rx data in post_cb if possible
|
||||
const spi_bus_attr_t *bus_attr = host->bus_attr;
|
||||
@@ -1474,7 +1526,6 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGV(SPI_TAG, "polling trans done");
|
||||
//deal with the in-flight transaction
|
||||
spi_post_trans(host);
|
||||
@@ -1490,7 +1541,7 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle,
|
||||
spi_bus_lock_acquire_end(handle->dev_lock);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
return (trans_flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL)) ? ESP_ERR_INVALID_STATE : ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc)
|
||||
|
||||
@@ -16,6 +16,6 @@ endif()
|
||||
# the component can be registered as WHOLE_ARCHIVE
|
||||
idf_component_register(
|
||||
SRCS ${srcs}
|
||||
PRIV_REQUIRES esp_driver_spi spi_flash esp_timer esp_driver_gpio
|
||||
PRIV_REQUIRES esp_driver_spi spi_flash esp_timer esp_driver_gpio esp_mm
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "esp_clk_tree.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_cache.h"
|
||||
#include "test_utils.h"
|
||||
#include "test_spi_utils.h"
|
||||
#include "spi_performance.h"
|
||||
@@ -775,24 +776,27 @@ TEST_CASE("SPI Master DMA test, TX and RX in different regions", "[spi]")
|
||||
//connect MOSI to two devices breaks the output, fix it.
|
||||
spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out);
|
||||
|
||||
#define TEST_REGION_SIZE 2
|
||||
#define TEST_REGION_SIZE 3
|
||||
static spi_transaction_t trans[TEST_REGION_SIZE];
|
||||
int x;
|
||||
memset(trans, 0, sizeof(trans));
|
||||
|
||||
trans[0].length = 320 * 8,
|
||||
trans[0].tx_buffer = data_malloc + 2;
|
||||
trans[0].length = 320 * 8;
|
||||
trans[0].tx_buffer = data_malloc + 2;
|
||||
trans[0].rx_buffer = data_dram;
|
||||
|
||||
trans[1].length = 4 * 8,
|
||||
trans[1].flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;
|
||||
trans[1].length = 4 * 8;
|
||||
trans[1].flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;
|
||||
uint32_t *ptr = (uint32_t *)trans[1].rx_data;
|
||||
*ptr = 0x54545454;
|
||||
ptr = (uint32_t *)trans[1].tx_data;
|
||||
*ptr = 0xbc124960;
|
||||
|
||||
trans[2].length = 64 * 8;
|
||||
trans[2].tx_buffer = data_drom;
|
||||
trans[2].rx_buffer = data_malloc;
|
||||
|
||||
//Queue all transactions.
|
||||
for (x = 0; x < TEST_REGION_SIZE; x++) {
|
||||
for (int x = 0; x < TEST_REGION_SIZE; x++) {
|
||||
ESP_LOGI(TAG, "transmitting %d...", x);
|
||||
ret = spi_device_transmit(spi, &trans[x]);
|
||||
TEST_ASSERT(ret == ESP_OK);
|
||||
@@ -1967,3 +1971,95 @@ TEST_CASE("test_spi_master_auto_sleep_retention", "[spi]")
|
||||
}
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
#endif //SOC_LIGHT_SLEEP_SUPPORTED
|
||||
|
||||
#if CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE
|
||||
#define TEST_EDMA_PSRAM_TRANS_NUM 5
|
||||
#define TEST_EDMA_TRANS_LEN 20480 //Use PSRAM need a 16/32/64 aligned buffer len
|
||||
#define TEST_EDMA_BUFFER_SZ (TEST_EDMA_PSRAM_TRANS_NUM * TEST_EDMA_TRANS_LEN)
|
||||
|
||||
void test_spi_psram_trans(spi_device_handle_t dev_handle, void *tx, void *rx)
|
||||
{
|
||||
spi_transaction_t trans_cfg = {
|
||||
.length = TEST_EDMA_TRANS_LEN * 8,
|
||||
.flags = SPI_TRANS_DMA_USE_PSRAM,
|
||||
};
|
||||
|
||||
for (uint8_t cnt = 0; cnt < TEST_EDMA_PSRAM_TRANS_NUM; cnt ++) {
|
||||
trans_cfg.tx_buffer = tx + TEST_EDMA_TRANS_LEN * cnt;
|
||||
trans_cfg.rx_buffer = rx + TEST_EDMA_TRANS_LEN * cnt;
|
||||
|
||||
// To use psram, hardware will pass data through MSPI and GDMA to GPSPI, which need some time
|
||||
// GPSPI bandwidth(speed * line_num) should always no more than PSRAM bandwidth
|
||||
trans_cfg.override_freq_hz = (CONFIG_SPIRAM_SPEED / 4) * 1000 * 1000;
|
||||
printf("%d TX %p RX %p len %d @%ld kHz\n", cnt, trans_cfg.tx_buffer, trans_cfg.rx_buffer, TEST_EDMA_TRANS_LEN, trans_cfg.override_freq_hz / 1000);
|
||||
TEST_ESP_OK(spi_device_transmit(dev_handle, &trans_cfg));
|
||||
TEST_ASSERT(!(trans_cfg.flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL)));
|
||||
spitest_cmp_or_dump(trans_cfg.tx_buffer, trans_cfg.rx_buffer, TEST_EDMA_TRANS_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SPI_Master: PSRAM buffer transaction via EDMA", "[spi]")
|
||||
{
|
||||
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
|
||||
buscfg.miso_io_num = buscfg.mosi_io_num; // set spi "self-loopback"
|
||||
buscfg.max_transfer_sz = TEST_EDMA_BUFFER_SZ;
|
||||
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
|
||||
spi_device_handle_t dev_handle;
|
||||
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
|
||||
devcfg.clock_speed_hz = 80 * 1000 * 1000; // Test error case on highest freq first
|
||||
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev_handle));
|
||||
int real_freq_khz;
|
||||
spi_device_get_actual_freq(dev_handle, &real_freq_khz);
|
||||
|
||||
uint32_t cache_width = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
|
||||
uint8_t *internal_1 = heap_caps_aligned_calloc(cache_width, 1, TEST_EDMA_BUFFER_SZ, MALLOC_CAP_INTERNAL);
|
||||
uint8_t *external_1 = heap_caps_aligned_calloc(cache_width, 1, TEST_EDMA_BUFFER_SZ, MALLOC_CAP_SPIRAM);
|
||||
uint8_t *external_2 = heap_caps_aligned_calloc(cache_width, 1, TEST_EDMA_BUFFER_SZ, MALLOC_CAP_SPIRAM);
|
||||
test_fill_random_to_buffers_dualboard(1001, internal_1, external_2, TEST_EDMA_BUFFER_SZ);
|
||||
|
||||
printf("Test error case: High freq @%d kHz\n", real_freq_khz);
|
||||
spi_transaction_t trans_cfg = {
|
||||
.length = TEST_EDMA_TRANS_LEN * 8,
|
||||
.tx_buffer = external_2,
|
||||
.rx_buffer = external_1,
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
trans_cfg.flags = i ? SPI_TRANS_DMA_USE_PSRAM : 0;
|
||||
uint32_t before = esp_get_free_heap_size();
|
||||
spi_device_polling_start(dev_handle, &trans_cfg, portMAX_DELAY);
|
||||
uint32_t after = esp_get_free_heap_size();
|
||||
printf("\n==== %s ====\n", i ? "EDMA" : "Auto Malloc");
|
||||
printf("before: %ld, after: %ld, diff: %ld\n", before, after, after - before);
|
||||
spi_device_polling_end(dev_handle, portMAX_DELAY);
|
||||
printf("RX fail: %d, TX fail: %d\n", !!(trans_cfg.flags & SPI_TRANS_DMA_RX_FAIL), !!(trans_cfg.flags & SPI_TRANS_DMA_TX_FAIL));
|
||||
TEST_ASSERT(i ? (before - after) < TEST_EDMA_TRANS_LEN : (before - after) > 2 * TEST_EDMA_TRANS_LEN);
|
||||
TEST_ASSERT((!!i) == !!(trans_cfg.flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL)));
|
||||
}
|
||||
|
||||
printf("\nTest unaligned tx psram buffer\n");
|
||||
trans_cfg.tx_buffer ++;
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, spi_device_transmit(dev_handle, &trans_cfg));
|
||||
|
||||
printf("\nTest trans: internal -> psram\n");
|
||||
memset(external_1, 0, TEST_EDMA_BUFFER_SZ);
|
||||
TEST_ESP_OK(esp_cache_msync((void *)external_1, TEST_EDMA_BUFFER_SZ, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
test_spi_psram_trans(dev_handle, internal_1, external_1);
|
||||
|
||||
printf("\nTest trans: psram -> psram\n");
|
||||
memset(external_2, 0, TEST_EDMA_BUFFER_SZ);
|
||||
TEST_ESP_OK(esp_cache_msync((void *)external_2, TEST_EDMA_BUFFER_SZ, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
|
||||
test_spi_psram_trans(dev_handle, external_1, external_2);
|
||||
|
||||
printf("\nTest trans: psram -> internal\n");
|
||||
memset(internal_1, 0, TEST_EDMA_BUFFER_SZ);
|
||||
test_spi_psram_trans(dev_handle, external_2, internal_1);
|
||||
|
||||
free(internal_1);
|
||||
free(external_1);
|
||||
free(external_2);
|
||||
spi_bus_remove_device(dev_handle);
|
||||
spi_bus_free(TEST_SPI_HOST);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
Reference in New Issue
Block a user