From 52a8f6cdd5d1962805919b99a034ae55b1c78301 Mon Sep 17 00:00:00 2001 From: Armando Date: Mon, 19 Jul 2021 11:01:13 +0800 Subject: [PATCH] essl: add essl spi support to communicate with spi slave hd mode --- components/driver/spi_master.c | 7 +- components/esp_serial_slave_link/essl.c | 9 +- components/esp_serial_slave_link/essl_spi.c | 278 +++++++++++++++++- .../include/esp_serial_slave_link/essl.h | 109 ++++--- .../include/esp_serial_slave_link/essl_spi.h | 165 +++++++++-- 5 files changed, 487 insertions(+), 81 deletions(-) diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index 9d4ee65bd7..11ffafd569 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -921,13 +921,14 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_start(spi_device_handle_t handl { esp_err_t ret; SPI_CHECK(ticks_to_wait == portMAX_DELAY, "currently timeout is not available for polling transactions", ESP_ERR_INVALID_ARG); - - spi_host_t *host = handle->host; ret = check_trans_valid(handle, trans_desc); if (ret!=ESP_OK) return ret; - SPI_CHECK(!spi_bus_device_is_polling(handle), "Cannot send polling transaction while the previous polling transaction is not terminated.", ESP_ERR_INVALID_STATE ); + /* If device_acquiring_lock is set to handle, it means that the user has already + * acquired the bus thanks to the function `spi_device_acquire_bus()`. + * In that case, we don't need to take the lock again. */ + spi_host_t *host = handle->host; if (host->device_acquiring_lock != handle) { ret = spi_bus_lock_acquire_start(handle->dev_lock, ticks_to_wait); } else { diff --git a/components/esp_serial_slave_link/essl.c b/components/esp_serial_slave_link/essl.c index 7a91bca6c8..cfbe459f82 100644 --- a/components/esp_serial_slave_link/essl.c +++ b/components/esp_serial_slave_link/essl.c @@ -62,10 +62,7 @@ esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms) esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms) { - if (handle == NULL) { - return ESP_ERR_INVALID_ARG; - } - if (start == NULL || length == 0) { + if (handle == NULL || start == NULL || length == 0) { return ESP_ERR_INVALID_ARG; } if (handle->send_packet == NULL) { @@ -87,9 +84,9 @@ esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t lengt } else if (err != ESP_ERR_NOT_FOUND) { return err; } // else ESP_ERR_NOT_FOUND - //the slave has no enough memory, retry + //the slave is not ready, retry } while (remain_wait_ms > 0); - return ESP_OK; + return err; } esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms) diff --git a/components/esp_serial_slave_link/essl_spi.c b/components/esp_serial_slave_link/essl_spi.c index 03ad5120ee..a9565ac4a6 100644 --- a/components/esp_serial_slave_link/essl_spi.c +++ b/components/esp_serial_slave_link/essl_spi.c @@ -17,8 +17,46 @@ #include "esp_log.h" #include "driver/spi_master.h" #include "driver/periph_ctrl.h" -#include "essl_spi/esp32s2_defs.h" +#include "essl_internal.h" #include "essl_spi.h" +#include "essl_spi/esp32s2_defs.h" + +#define ESSL_SPI_CHECK(cond, warn, ret) do{if(!(cond)){ESP_LOGE(TAG, warn); return ret;}} while(0) + +/** + * Initialise device function list of SPI by this macro. + */ +#define ESSL_SPI_DEFAULT_DEV_FUNC() (essl_dev_t) {\ + .get_tx_buffer_num = essl_spi_get_tx_buffer_num,\ + .update_tx_buffer_num = essl_spi_update_tx_buffer_num,\ + .get_rx_data_size = essl_spi_get_rx_data_size,\ + .update_rx_data_size = essl_spi_update_rx_data_size,\ + .send_packet = essl_spi_send_packet,\ + .get_packet = essl_spi_get_packet,\ + .write_reg = essl_spi_write_reg,\ + .read_reg = essl_spi_read_reg,\ +} + +static const char TAG[] = "essl_spi"; + +typedef struct { + spi_device_handle_t spi; // Pointer to SPI device handle. + /* Master TX, Slave RX */ + struct { + size_t sent_buf_num; // Number of TX buffers that has been sent out by the master. + size_t slave_rx_buf_num; // Number of RX buffers laoded by the slave. + uint16_t tx_buffer_size; /* Buffer size for Master TX / Slave RX direction. + * Data with length within this size will still be regarded as one buffer. + * E.g. 10 bytes data costs 2 buffers if the size is 8 bytes per buffer. */ + uint8_t tx_sync_reg; // The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. + } master_out; + /* Master RX, Slave TX */ + struct { + size_t received_bytes; // Number of the RX bytes that has been received by the Master. + size_t slave_tx_bytes; // Number of the TX bytes that has been loaded by the Slave + uint8_t rx_sync_reg; // The pre-negotiated register ID for Master-RX-SLAVE-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. + } master_in; +} essl_spi_context_t; static uint16_t get_hd_command(uint16_t cmd_i, uint32_t flags) @@ -153,12 +191,12 @@ esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, in seg_len = (seg_len > 0)? seg_len : len; uint8_t* read_ptr = out_data; - esp_err_t err = ESP_OK; + esp_err_t ret = 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; + ret = essl_spi_rddma_seg(spi, read_ptr, send_len, flags); + if (ret != ESP_OK) return ret; len -= send_len; read_ptr += send_len; @@ -217,3 +255,235 @@ esp_err_t essl_spi_int(spi_device_handle_t spi, int int_n, uint32_t flags) }; return spi_device_transmit(spi, &end_t); } + +//------------------------------------ APPEND MODE ----------------------------------// +static uint32_t essl_spi_get_rx_data_size(void *arg); +static esp_err_t essl_spi_update_rx_data_size(void *arg, uint32_t wait_ms); +static uint32_t essl_spi_get_tx_buffer_num(void *arg); +static esp_err_t essl_spi_update_tx_buffer_num(void *arg, uint32_t wait_ms); + +esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config) +{ + ESSL_SPI_CHECK(init_config->spi, "Check SPI initialization first", ESP_ERR_INVALID_STATE); + ESSL_SPI_CHECK(init_config->tx_sync_reg <= (SOC_SPI_MAXIMUM_BUFFER_SIZE - 1) * 4, "tx_sync_reg ID larger than interal register length", ESP_ERR_INVALID_ARG); + ESSL_SPI_CHECK(init_config->rx_sync_reg <= (SOC_SPI_MAXIMUM_BUFFER_SIZE - 1) * 4, "rx_sync_reg ID larger than interal register length", ESP_ERR_INVALID_ARG); + ESSL_SPI_CHECK(init_config->tx_sync_reg != init_config->rx_sync_reg, "Should use different word of registers for synchronization", ESP_ERR_INVALID_ARG); + + essl_spi_context_t *context = calloc(1, sizeof(essl_spi_context_t)); + essl_dev_t *dev = calloc(1, sizeof(essl_dev_t)); + if (!context || !dev) { + free(context); + free(dev); + return ESP_ERR_NO_MEM; + } + + *context = (essl_spi_context_t) { + .spi = *init_config->spi, + .master_out.tx_buffer_size = init_config->tx_buf_size, + .master_out.tx_sync_reg = init_config->tx_sync_reg, + .master_in.rx_sync_reg = init_config->rx_sync_reg + }; + + *dev = ESSL_SPI_DEFAULT_DEV_FUNC(); + dev->args = context; + + *out_handle = dev; + + return ESP_OK; +} + +esp_err_t essl_spi_deinit_dev(essl_handle_t handle) +{ + ESSL_SPI_CHECK(handle, "ESSL SPI is not in use", ESP_ERR_INVALID_STATE); + free(handle->args); + free(handle); + return ESP_OK; +} + +void essl_spi_reset_cnt(void *arg) +{ + essl_spi_context_t *ctx = arg; + if (ctx) { + ctx->master_out.sent_buf_num = 0; + ctx->master_in.received_bytes = 0; + } +} + +//------------------------------------ RX ----------------------------------// +esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms) +{ + essl_spi_context_t *ctx = arg; + ESSL_SPI_CHECK(arg, "Check ESSL SPI initialization first", ESP_ERR_INVALID_STATE); + uint8_t reserved_1_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_out.tx_sync_reg : ctx->master_in.rx_sync_reg; + uint8_t reserved_1_tail = reserved_1_head + 3; + uint8_t reserved_2_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_in.rx_sync_reg : ctx->master_out.tx_sync_reg; + uint8_t reserved_2_tail = reserved_2_head + 3; + ESSL_SPI_CHECK(addr < reserved_1_head || (addr > reserved_1_tail && addr < reserved_2_head) || addr > reserved_2_tail, "Invalid address", ESP_ERR_INVALID_ARG); + + return essl_spi_rdbuf(ctx->spi, out_value, addr, sizeof(uint8_t), 0); +} + +static uint32_t essl_spi_get_rx_data_size(void *arg) +{ + essl_spi_context_t *ctx = arg; + ESP_LOGV(TAG, "slave tx buffer: %d bytes, master has read: %d bytes", ctx->master_in.slave_tx_bytes, ctx->master_in.received_bytes); + return ctx->master_in.slave_tx_bytes - ctx->master_in.received_bytes; +} + +static esp_err_t essl_spi_update_rx_data_size(void *arg, uint32_t wait_ms) +{ + essl_spi_context_t *ctx = arg; + uint32_t updated_size; + uint32_t previous_size; + esp_err_t ret; + + ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&previous_size, ctx->master_in.rx_sync_reg, sizeof(uint32_t), 0); + if (ret != ESP_OK) { + return ret; + } + + /** + * Read until the last 2 reading result are same. Reason: + * SPI transaction is carried on per 1 Byte. So when Master is reading the shared register, if the + * register value is changed by Slave at this time, Master may get wrong data. + */ + while (1) { + ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&updated_size, ctx->master_in.rx_sync_reg, sizeof(uint32_t), 0); + if (ret != ESP_OK) { + return ret; + } + if (updated_size == previous_size) { + ctx->master_in.slave_tx_bytes = updated_size; + ESP_LOGV(TAG, "updated: slave prepared tx buffer is: %d bytes", updated_size); + return ret; + } + previous_size = updated_size; + } +} + +esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms) +{ + ESSL_SPI_CHECK(arg, "Check ESSL SPI initialization first", ESP_ERR_INVALID_STATE); + if (!esp_ptr_dma_capable(out_data) || ((intptr_t)out_data % 4) != 0) { + return ESP_ERR_INVALID_ARG; + } + + essl_spi_context_t *ctx = arg; + esp_err_t ret; + + if (essl_spi_get_rx_data_size(arg) < size) { + /** + * For realistic situation, usually there will be a large overhead (Slave will load large amount of data), + * so here we only update the Slave's TX size when the last-updated size is smaller than what Master requires. + */ + ret = essl_spi_update_rx_data_size(arg, wait_ms); + if (ret != ESP_OK) { + return ret; + } + + //Slave still did not load enough size of buffer + if (essl_spi_get_rx_data_size(arg) < size) { + ESP_LOGV(TAG, "slave buffer: %d is not enough, %d is required", ctx->master_in.slave_tx_bytes, ctx->master_in.received_bytes + size); + return ESP_ERR_NOT_FOUND; + } + } + + ESP_LOGV(TAG, "get_packet: size to read is: %d", size); + ret = essl_spi_rddma_seg(ctx->spi, out_data, size, 0); + if (ret != ESP_OK) { + return ret; + } + ctx->master_in.received_bytes += size; + + return ESP_OK; +} + +//------------------------------------ TX ----------------------------------// +esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms) +{ + essl_spi_context_t *ctx = arg; + ESSL_SPI_CHECK(arg, "Check ESSL SPI initialization first", ESP_ERR_INVALID_STATE); + uint8_t reserved_1_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_out.tx_sync_reg : ctx->master_in.rx_sync_reg; + uint8_t reserved_1_tail = reserved_1_head + 3; + uint8_t reserved_2_head = ctx->master_out.tx_sync_reg < ctx->master_in.rx_sync_reg ? ctx->master_in.rx_sync_reg : ctx->master_out.tx_sync_reg; + uint8_t reserved_2_tail = reserved_2_head + 3; + ESSL_SPI_CHECK(addr < reserved_1_head || (addr > reserved_1_tail && addr < reserved_2_head) || addr > reserved_2_tail, "Invalid address", ESP_ERR_INVALID_ARG); + ESSL_SPI_CHECK(out_value == NULL, "This feature is not supported", ESP_ERR_NOT_SUPPORTED); + + return essl_spi_wrbuf(ctx->spi, &value, addr, sizeof(uint8_t), 0); +} + +static uint32_t essl_spi_get_tx_buffer_num(void *arg) +{ + essl_spi_context_t *ctx = arg; + ESP_LOGV(TAG, "slave rx buffer: %d, master has sent: %d", ctx->master_out.slave_rx_buf_num, ctx->master_out.sent_buf_num); + return ctx->master_out.slave_rx_buf_num - ctx->master_out.sent_buf_num; +} + +static esp_err_t essl_spi_update_tx_buffer_num(void *arg, uint32_t wait_ms) +{ + essl_spi_context_t *ctx = arg; + uint32_t updated_num; + uint32_t previous_size; + esp_err_t ret; + + ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&previous_size, ctx->master_out.tx_sync_reg, sizeof(uint32_t), 0); + if (ret != ESP_OK) { + return ret; + } + + /** + * Read until the last 2 reading result are same. Reason: + * SPI transaction is carried on per 1 Byte. So when Master is reading the shared register, if the + * register value is changed by Slave at this time, Master may get wrong data. + */ + while (1) { + ret = essl_spi_rdbuf_polling(ctx->spi, (uint8_t *)&updated_num, ctx->master_out.tx_sync_reg, sizeof(uint32_t), 0); + if (ret != ESP_OK) { + return ret; + } + if (updated_num == previous_size) { + ctx->master_out.slave_rx_buf_num = updated_num; + ESP_LOGV(TAG, "updated: slave prepared rx buffer: %d", updated_num); + return ret; + } + previous_size = updated_num; + } +} + +esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms) +{ + ESSL_SPI_CHECK(arg, "Check ESSL SPI initialization first", ESP_ERR_INVALID_STATE); + if (!esp_ptr_dma_capable(data)) { + return ESP_ERR_INVALID_ARG; + } + + essl_spi_context_t *ctx = arg; + esp_err_t ret; + uint32_t buf_num_to_use = (size + ctx->master_out.tx_buffer_size - 1) / ctx->master_out.tx_buffer_size; + + if (essl_spi_get_tx_buffer_num(arg) < buf_num_to_use) { + /** + * For realistic situation, usually there will be a large overhead (Slave will load enough number of RX buffers), + * so here we only update the Slave's RX buffer number when the last-updated number is smaller than what Master requires. + */ + ret = essl_spi_update_tx_buffer_num(arg, wait_ms); + if (ret != ESP_OK) { + return ret; + } + //Slave still did not load a sufficient amount of buffers + if (essl_spi_get_tx_buffer_num(arg) < buf_num_to_use) { + ESP_LOGV(TAG, "slave buffer: %d is not enough, %d is required", ctx->master_out.slave_rx_buf_num, ctx->master_out.sent_buf_num + buf_num_to_use); + return ESP_ERR_NOT_FOUND; + } + } + + ESP_LOGV(TAG, "send_packet: size to write is: %d", size); + ret = essl_spi_wrdma_seg(ctx->spi, data, size, 0); + if (ret != ESP_OK) { + return ret; + } + ctx->master_out.sent_buf_num += buf_num_to_use; + + return essl_spi_wrdma_done(ctx->spi, 0); +} diff --git a/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h b/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h index 82e91778df..03c68c8981 100644 --- a/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h +++ b/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h @@ -31,53 +31,64 @@ typedef struct essl_dev_t* essl_handle_t; * * @param handle Handle of a ``essl`` device. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. - * @return ESP_OK if success, or other value returned from lower layer `init`. + * @return + * - ESP_OK: If success + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. + * - Other value returned from lower layer `init`. */ esp_err_t essl_init(essl_handle_t handle, uint32_t wait_ms); -/** Wait for interrupt of a ESP32 slave device. +/** Wait for interrupt of an ESSL slave device. * * @param handle Handle of a ``essl`` device. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK if success + * - ESP_OK: If success + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. * - One of the error codes from SDMMC host controller */ esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms); /** Get buffer num for the host to send data to the slave. The buffers are size of ``buffer_size``. * - * @param handle Handle of a ``essl`` device. - * @param out_tx_num Output of buffer num that host can send data to ESP32 slave. + * @param handle Handle of an ESSL device. + * @param out_tx_num Output of buffer num that host can send data to ESSL slave. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success - * - One of the error codes from SDMMC host controller + * - ESP_OK: Success + * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode + * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_get_tx_buffer_num(essl_handle_t handle, uint32_t *out_tx_num, uint32_t wait_ms); -/** Get amount of data the ESP32 slave preparing to send to host. +/** Get the size, in bytes, of the data that the ESSL slave is ready to send * - * @param handle Handle of a ``essl`` device. - * @param out_rx_size Output of data size to read from slave. + * @param handle Handle of an ESSL device. + * @param out_rx_size Output of data size to read from slave, in bytes * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success - * - One of the error codes from SDMMC host controller + * - ESP_OK: Success + * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode + * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_get_rx_data_size(essl_handle_t handle, uint32_t *out_rx_size, uint32_t wait_ms); /** Reset the counters of this component. Usually you don't need to do this unless you know the slave is reset. * - * @param handle Handle of a ``essl`` device. + * @param handle Handle of an ESSL device. + * + * @return + * - ESP_OK: Success + * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode + * - ESP_ERR_INVALID_ARG: Invalid argument, handle is not init. */ esp_err_t essl_reset_cnt(essl_handle_t handle); -/** Send a packet to the ESP32 slave. The slave receive the packet into buffers whose size is ``buffer_size`` (configured during initialization). +/** Send a packet to the ESSL Slave. The Slave receives the packet into buffers whose size is ``buffer_size`` (configured during initialization). * * @param handle Handle of a ``essl`` device. * @param start Start address of the packet to send @@ -86,12 +97,15 @@ esp_err_t essl_reset_cnt(essl_handle_t handle); * * @return * - ESP_OK Success - * - ESP_ERR_TIMEOUT No buffer to use, or error ftrom SDMMC host controller - * - One of the error codes from SDMMC host controller + * - ESP_ERR_INVALID_ARG: Invalid argument, handle is not init or other argument is not valid. + * - ESP_ERR_TIMEOUT: No buffer to use, or error ftrom SDMMC host controller. + * - ESP_ERR_NOT_FOUND: Slave is not ready for receiving. + * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode + * - One of the error codes from SDMMC/SPI host controller. */ esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms); -/** Get a packet from ESP32 slave. +/** Get a packet from ESSL slave. * * @param handle Handle of a ``essl`` device. * @param[out] out_data Data output address @@ -100,16 +114,19 @@ esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t lengt * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success, all the data are read from the slave. - * - ESP_ERR_NOT_FINISHED Read success, while there're data remaining. - * - One of the error codes from SDMMC host controller + * - ESP_OK Success: All the data has been read from the slave. + * - ESP_ERR_INVALID_ARG: Invalid argument, The handle is not initialized or the other arguments are invalid. + * - ESP_ERR_NOT_FINISHED: Read was successful, but there is still data remaining. + * - ESP_ERR_NOT_FOUND: Slave is not ready to send data. + * - ESP_ERR_NOT_SUPPORTED: This API is not supported in this mode + * - One of the error codes from SDMMC/SPI host controller. */ esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms); -/** Write general purpose R/W registers (8-bit) of ESP32 slave. +/** Write general purpose R/W registers (8-bit) of ESSL slave. * - * @param handle Handle of a ``essl`` device. - * @param addr Address of register to write. Valid address: 0-59. + * @param handle Handle of an ESSL device. + * @param addr Address of register to write. For SDIO, valid address: 0-59. For SPI, see ``essl_spi.h`` * @param value Value to write to the register. * @param value_o Output of the returned written value. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. @@ -118,22 +135,20 @@ esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, siz * * @return * - ESP_OK Success - * - ESP_ERR_INVALID_ARG Address not valid. - * - One of the error codes from SDMMC host controller + * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_write_reg(essl_handle_t handle, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms); -/** Read general purpose R/W registers (8-bit) of ESP32 slave. +/** Read general purpose R/W registers (8-bit) of ESSL slave. * * @param handle Handle of a ``essl`` device. - * @param add Address of register to read. Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read). + * @param add Address of register to read. For SDIO, Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read). For SPI, see ``essl_spi.h`` * @param value_o Output value read from the register. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return * - ESP_OK Success - * - ESP_ERR_INVALID_ARG Address not valid. - * - One of the error codes from SDMMC host controller + * - One of the error codes from SDMMC/SPI host controller */ esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uint32_t wait_ms); @@ -143,25 +158,26 @@ esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uin * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_ERR_NOT_SUPPORTED Currently our driver doesnot support SDIO with SPI interface. - * - ESP_OK If interrupt triggered. - * - ESP_ERR_TIMEOUT No interrupts before timeout. + * - ESP_OK: If interrupt is triggered. + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. + * - ESP_ERR_TIMEOUT: No interrupts before timeout. */ esp_err_t essl_wait_int(essl_handle_t handle, uint32_t wait_ms); -/** Clear interrupt bits of ESP32 slave. All the bits set in the mask will be cleared, while other bits will stay the same. +/** Clear interrupt bits of ESSL slave. All the bits set in the mask will be cleared, while other bits will stay the same. * * @param handle Handle of a ``essl`` device. * @param intr_mask Mask of interrupt bits to clear. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success - * - One of the error codes from SDMMC host controller + * - ESP_OK: Success + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. + * - One of the error codes from SDMMC host controller */ esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms); -/** Get interrupt bits of ESP32 slave. +/** Get interrupt bits of ESSL slave. * * @param handle Handle of a ``essl`` device. * @param intr_raw Output of the raw interrupt bits. Set to NULL if only masked bits are read. @@ -169,25 +185,27 @@ esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wai * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success - * - ESP_INVALID_ARG if both ``intr_raw`` and ``intr_st`` are NULL. - * - One of the error codes from SDMMC host controller + * - ESP_OK: Success + * - ESP_INVALID_ARG: If both ``intr_raw`` and ``intr_st`` are NULL. + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. + * - One of the error codes from SDMMC host controller */ esp_err_t essl_get_intr(essl_handle_t handle, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms); -/** Set interrupt enable bits of ESP32 slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set. +/** Set interrupt enable bits of ESSL slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set. * * @param handle Handle of a ``essl`` device. * @param ena_mask Mask of the interrupt bits to enable. * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success - * - One of the error codes from SDMMC host controller + * - ESP_OK: Success + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. + * - One of the error codes from SDMMC host controller */ esp_err_t essl_set_intr_ena(essl_handle_t handle, uint32_t ena_mask, uint32_t wait_ms); -/** Get interrupt enable bits of ESP32 slave. +/** Get interrupt enable bits of ESSL slave. * * @param handle Handle of a ``essl`` device. * @param ena_mask_o Output of interrupt bit enable mask. @@ -206,7 +224,8 @@ esp_err_t essl_get_intr_ena(essl_handle_t handle, uint32_t *ena_mask_o, uint32_t * @param wait_ms Millisecond to wait before timeout, will not wait at all if set to 0-9. * * @return - * - ESP_OK Success - * - One of the error codes from SDMMC host controller + * - ESP_OK: Success + * - ESP_ERR_NOT_SUPPORTED: Current device does not support this function. + * - One of the error codes from SDMMC host controller */ esp_err_t essl_send_slave_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms); diff --git a/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h b/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h index a6c3c40c04..22721d17ec 100644 --- a/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h +++ b/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h @@ -23,22 +23,141 @@ extern "C" { #endif +/// Configuration of ESSL SPI device +typedef struct { + spi_device_handle_t *spi; ///< Pointer to SPI device handle. + uint32_t tx_buf_size; ///< The pre-negotiated Master TX buffer size used by both the host and the slave. + uint8_t tx_sync_reg; ///< The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. + uint8_t rx_sync_reg; ///< The pre-negotiated register ID for Master-RX-Slave-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization. +} essl_spi_config_t; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// APIs for DMA Append Mode +// This mode has a better performance for continuous Half Duplex SPI transactions. +// +// * You can use the ``essl_spi_init_dev`` and ``essl_spi_deinit_dev`` together with APIs in ``essl.h`` to communicate +// with ESP SPI Slaves in Half Duplex DMA Append Mode. See example for SPI SLAVE HALFDUPLEX APPEND MODE. +// * You can also use the following APIs to create your own logic. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * @brief Initialize the ESSL SPI device function list and get its handle + * + * @param[out] out_handle Output of the handle + * @param init_config Configuration for the ESSL SPI device + * @return + * - ESP_OK: On success + * - ESP_ERR_NO_MEM: Memory exhausted + * - ESP_ERR_INVALID_STATE: SPI driver is not initialized + * - ESP_ERR_INVALID_ARG: Wrong register ID + */ +esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config); + +/** + * @brief Deinitialize the ESSL SPI device and free the memory used by the device + * + * @param handle Handle of the ESSL SPI device + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_STATE: ESSL SPI is not in use + */ +esp_err_t essl_spi_deinit_dev(essl_handle_t handle); + +/** + * @brief Read from the shared registers + * + * @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `rx_sync_reg` in `essl_spi_config_t`) + * + * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) + * @param addr Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1). + * @param[out] out_value Read buffer for the shared registers. + * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. + * - ESP_ERR_INVALID_ARG: The address argument is not valid. See note 1. + * - or other return value from :cpp:func:`spi_device_transmit`. + */ +esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms); + +/** + * @brief Get a packet from Slave + * + * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) + * @param[out] out_data Output data address + * @param size The size of the output data. + * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). + * @return + * - ESP_OK: On Success + * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. + * - ESP_ERR_INVALID_ARG: The output data address is neither DMA capable nor 4 byte-aligned + * - ESP_ERR_INVALID_SIZE: Master requires ``size`` bytes of data but Slave did not load enough bytes. + */ +esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms); + +/** + * @brief Write to the shared registers + * + * @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `tx_sync_reg` in `essl_spi_config_t`) + * @note Feature of checking the actual written value (``out_value``) is not supported. + * + * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) + * @param addr Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1) + * @param value Buffer for data to send, should be align to 4. + * @param[out] out_value Not supported, should be set to NULL. + * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. + * - ESP_ERR_INVALID_ARG: The address argument is not valid. See note 1. + * - ESP_ERR_NOT_SUPPORTED: Should set ``out_value`` to NULL. See note 2. + * - or other return value from :cpp:func:`spi_device_transmit`. + * + */ +esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms); + +/** + * @brief Send a packet to Slave + * + * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) + * @param data Address of the data to send + * @param size Size of the data to send. + * @param wait_ms Time to wait before timeout (reserved for future use, user should set this to 0). + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized. + * - ESP_ERR_INVALID_ARG: The data address is not DMA capable + * - ESP_ERR_INVALID_SIZE: Master will send ``size`` bytes of data but Slave did not load enough RX buffer + */ +esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms); + +/** + * @brief Reset the counter in Master context + * + * @note Shall only be called if the slave has reset its counter. Else, Slave and Master would be desynchronized + * + * @param arg Context of the component. (Member ``arg`` from ``essl_handle_t``) + */ +void essl_spi_reset_cnt(void *arg); + //////////////////////////////////////////////////////////////////////////////// // Basic commands to communicate with the SPI Slave HD on ESP32-S2 //////////////////////////////////////////////////////////////////////////////// - /** * @brief Read the shared buffer from the slave in ISR way * + * @note The slave's HW doesn't guarantee the data in one SPI transaction is consistent. It sends data in unit of byte. + * In other words, if the slave SW attempts to update the shared register when a rdbuf SPI transaction is in-flight, + * the data got by the master will be the combination of bytes of different writes of slave SW. + * * @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. + * @param spi SPI device handle representing the slave + * @param[out] out_data Buffer for read data, strongly suggested to be in the DRAM and aligned 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`. @@ -52,11 +171,11 @@ esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, i * 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. + * @param spi SPI device handle representing the slave + * @param[out] out_data Buffer for read data, strongly suggested to be in the DRAM and aligned 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`. @@ -71,7 +190,7 @@ esp_err_t essl_spi_rdbuf_polling(spi_device_handle_t spi, uint8_t *out_data, int * 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 data Buffer for data to send, strongly suggested to be in the DRAM * @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. @@ -89,7 +208,7 @@ esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr, * 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 data Buffer for data to send, strongly suggested to be in the DRAM * @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. @@ -105,10 +224,10 @@ esp_err_t essl_spi_wrbuf_polling(spi_device_handle_t spi, const uint8_t *data, i * @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 + * @param spi SPI device handle representing the slave + * @param[out] out_data Buffer to hold the received data, strongly suggested to be in the DRAM and aligned 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. @@ -123,10 +242,10 @@ esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, in * * @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. + * @param spi SPI device handle representing the slave + * @param[out] out_data Buffer to hold the received data. strongly suggested to be in the DRAM and aligned 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`. @@ -155,7 +274,7 @@ esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags); * :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 data Buffer for data to send, strongly suggested to be in the DRAM * @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 @@ -173,7 +292,7 @@ esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len, * @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 data Buffer for data to send, strongly suggested to be in the DRAM * @param seg_len Length of this segment * @param flags `SPI_TRANS_*` flags to control the transaction mode of the transaction to send. * @return