From 0f4f16033b24f3602d45f612faf73dd922083d4f Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 29 Jul 2026 14:45:16 +0200 Subject: [PATCH 1/2] fix(ulp/lp_spi): fix driver bugs and add bus deinit API The LP SPI driver read and wrote the W0..W15 data buffer registers a whole 32-bit word at a time, which overran the caller's buffer for transfers whose length was not a multiple of four bytes and corrupted the received data. Read and write the data buffer byte-granularly so sub-word transfers no longer alias adjacent bytes. The master transfer also programmed the shared bit-length register from tx_length alone, truncating receive-longer-than-transmit transactions, and always enabled MOSI even on read-only transfers, clocking out stale buffer contents. Size each hardware transaction by max(tx_length, rx_length) and gate MOSI/MISO on the corresponding buffer. The slave path reused the master's single-shot flow, so it re-triggered reg_update after preload (clocking out the previous transaction's data) and offered no way for the caller to publish readiness before the master started the clock. Split the slave transfer into an arm step that preloads the buffer and starts the user phase, and a wait step that blocks on TRANS_DONE and drains only the bytes the master actually clocked in, tracked in software since reg_usr is not a reliable busy indicator in slave mode. Set the LP IO direction for the SPI pads, add lp_core_lp_spi_bus_deinit() to release the LP GPIO pins, and reset the LP SPI peripheral at bus initialization so a stale configuration from a previous run cannot leak into the next. --- components/ulp/lp_core/include/lp_core_spi.h | 18 +- .../lp_core/lp_core/include/ulp_lp_core_spi.h | 36 +- components/ulp/lp_core/lp_core/lp_core_spi.c | 419 +++++++++++------- components/ulp/lp_core/lp_core_spi.c | 44 +- 4 files changed, 359 insertions(+), 158 deletions(-) diff --git a/components/ulp/lp_core/include/lp_core_spi.h b/components/ulp/lp_core/include/lp_core_spi.h index f2f0ea9f036..a9d836d4bf5 100644 --- a/components/ulp/lp_core/include/lp_core_spi.h +++ b/components/ulp/lp_core/include/lp_core_spi.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -104,6 +104,22 @@ esp_err_t lp_core_lp_spi_bus_add_device(lp_spi_host_t host_id, const lp_spi_devi */ esp_err_t lp_core_lp_spi_slave_initialize(lp_spi_host_t host_id, const lp_spi_slave_config_t *slave_config); +/** + * @brief Deinitialize the LP SPI bus. + * + * Performs a module-level hardware reset of the LP SPI peripheral (all + * registers return to power-on defaults) and deinitializes the LP GPIO + * pins that were configured for SPI signals. + * + * @param host_id LP SPI host ID (currently unused, only one host exists) + * @param bus_config Pointer to the bus configuration that was used during + * initialization, so that the same GPIO pins can be + * deinitialized. May be NULL to skip GPIO deinit. + * + * @return ESP_OK on success + */ +esp_err_t lp_core_lp_spi_bus_deinit(lp_spi_host_t host_id, const lp_spi_bus_config_t *bus_config); + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h index 95b97e13200..e614440fb65 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -60,6 +60,40 @@ esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32 */ esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait); +/** + * @brief Preload the LP SPI slave's TX data and arm the peripheral, then return. + * + * Loads ``trans_desc->tx_buffer`` into the LP-SPI W0..W15 data buffer, + * programs the bit length, and starts the slave user phase. The call + * does not block on the master's SCK; the peripheral is left armed and + * will sample/drive the bus as soon as the master starts clocking. + * + * Pair with ``lp_core_lp_spi_slave_wait()`` to block on completion and + * drain the RX buffer. Calling ``lp_core_lp_spi_slave_arm()`` again while + * a previous arm has not been waited on returns ``ESP_ERR_INVALID_STATE``. + * + * @param trans_desc LP SPI transaction configuration descriptor. + * + * @return esp_err_t ESP_OK when successful + * ESP_ERR_INVALID_ARG if the configuration is invalid + * ESP_ERR_INVALID_STATE if a previous transaction is still in progress + */ +esp_err_t lp_core_lp_spi_slave_arm(lp_spi_transaction_t *trans_desc); + +/** + * @brief Wait for a previously-armed LP SPI slave transaction to complete. + * + * Must be paired with ``lp_core_lp_spi_slave_arm()`` using the same ``trans_desc``. + * + * @param trans_desc LP SPI transaction configuration descriptor. + * @param ticks_to_wait Operation timeout in CPU cycles. Set to -1 to wait forever. + * + * @return esp_err_t ESP_OK when successful + * ESP_ERR_INVALID_ARG if the configuration is invalid + * ESP_ERR_TIMEOUT when the operation times out + */ +esp_err_t lp_core_lp_spi_slave_wait(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait); + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/lp_core/lp_core_spi.c b/components/ulp/lp_core/lp_core/lp_core_spi.c index 9ff1f8a2734..b768977f801 100644 --- a/components/ulp/lp_core/lp_core/lp_core_spi.c +++ b/components/ulp/lp_core/lp_core/lp_core_spi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,7 @@ #if SOC_LP_SPI_SUPPORTED #include +#include #include #include "esp_err.h" #include "ulp_lp_core_spi.h" @@ -17,6 +18,73 @@ /* Use the register structure to access LP_SPI module registers */ lp_spi_dev_t *lp_spi_dev = &LP_SPI; +/* Tracks an outstanding lp_core_lp_spi_slave_arm() that has not yet been + * paired with a slave_wait(). The LP_SPI_CMD.reg_usr bit is not a reliable + * "busy" indicator in slave mode (the slave holds it set while merely armed + * and waiting for the master's SCK), so we serialise arm/wait in software. + */ +static volatile bool s_slave_armed = false; + +/* LP SPI data buffer is W0..W15 (16 x 32-bit = 64 B). Per TRM, transfers + * beyond 64 B repeatedly fetch from W15[31:24], so byte 63 is replayed for + * every byte past 64. Skipping W15 (cap at 60 B / W0..W14) avoids that + * aliasing region entirely; longer transfers are split into back-to-back + * 60 B hardware transactions. + */ +#define LP_SPI_MAX_DATA_REG_NUM ((SOC_LP_SPI_MAXIMUM_BUFFER_SIZE / 4) - 1) /* 15 */ +#define LP_SPI_CHUNK_BYTES (LP_SPI_MAX_DATA_REG_NUM * 4) /* 60 */ + +/* Write ``len`` bytes into the LP SPI data buffer registers from W0. + * Sub-word safe (no read past ``src``). ``len`` must be <= LP_SPI_CHUNK_BYTES. + */ +static inline void lp_spi_write_buffer_bytes(const uint8_t *src, size_t len) +{ + size_t reg_idx = 0; + size_t remaining = len; + while (remaining >= 4) { + uint32_t word; + memcpy(&word, src, 4); + lp_spi_dev->data_buf[reg_idx].reg_buf = word; + reg_idx++; + src += 4; + remaining -= 4; + } + if (remaining > 0) { + uint32_t word = 0; + memcpy(&word, src, remaining); + lp_spi_dev->data_buf[reg_idx].reg_buf = word; + } +} + +/* Read ``len`` bytes from the LP SPI data buffer registers into ``dst``, + * starting at W0. Sub-word safe (no write past ``dst``). + */ +static inline void lp_spi_read_buffer_bytes(uint8_t *dst, size_t len) +{ + size_t reg_idx = 0; + size_t remaining = len; + while (remaining >= 4) { + uint32_t word = lp_spi_dev->data_buf[reg_idx].reg_buf; + memcpy(dst, &word, 4); + reg_idx++; + dst += 4; + remaining -= 4; + } + if (remaining > 0) { + uint32_t word = lp_spi_dev->data_buf[reg_idx].reg_buf; + memcpy(dst, &word, remaining); + } +} + +/* Reset the RX and TX AFIFOs */ +static inline void lp_spi_reset_fifos(void) +{ + lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 1; + lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 0; + lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 1; + lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 0; +} + static inline esp_err_t lp_core_spi_wait_for_interrupt(int32_t ticks_to_wait) { uint32_t to = 0; @@ -45,29 +113,40 @@ esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32 { esp_err_t ret = ESP_OK; - /* Argument sanity check - * Note: The Tx buffer is mandatory for this API. + /* Require at least one of tx_buffer/rx_buffer; length must be 0 when its + * buffer is NULL. */ - if (trans_desc == NULL || trans_desc->tx_buffer == NULL || trans_desc->tx_length == 0) { + if (trans_desc == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (trans_desc->tx_buffer != NULL && trans_desc->tx_length == 0) { + return ESP_ERR_INVALID_ARG; + } + if (trans_desc->rx_buffer != NULL && trans_desc->rx_length == 0) { return ESP_ERR_INVALID_ARG; } - /* Reset the Tx and Rx FIFOs */ - lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 1; - lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 0; - lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 1; - lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 0; - - /* Clear any previous interrupts. - * Note: LP SPI does not have any DMA access but the interrupt bit lives in the DMA interrupt register. + /* The peripheral has a single shared bit-length register + * (LP_SPI_MS_DLEN.reg_ms_data_bitlen), so we program it for + * max(tx_length, rx_length) bytes to avoid truncating the longer side. */ - lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + uint32_t tx_total = trans_desc->tx_buffer ? trans_desc->tx_length : 0; + uint32_t rx_total = trans_desc->rx_buffer ? trans_desc->rx_length : 0; + uint32_t bus_total = tx_total > rx_total ? tx_total : rx_total; + if (bus_total == 0) { + return ESP_ERR_INVALID_ARG; + } /* Make sure that we do not have any ongoing transactions */ if (lp_spi_dev->spi_cmd.reg_usr) { return ESP_ERR_INVALID_STATE; } + /* Clear any previous interrupts. + * Note: LP SPI does not have any DMA access but the interrupt bit lives in the DMA interrupt register. + */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + /* Configure dummy bits */ lp_spi_dev->spi_user.reg_usr_dummy = trans_desc->dummy_bits ? 1 : 0; if (trans_desc->dummy_bits) { @@ -88,154 +167,38 @@ esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32 lp_spi_dev->spi_addr.reg_usr_addr_value = lp_spi_dev->spi_ctrl.reg_wr_bit_order ? __builtin_bswap32(trans_desc->address) : trans_desc->address << (32 - trans_desc->address_bits); } - /* Set data lines */ - lp_spi_dev->spi_user.reg_usr_mosi = 1; - lp_spi_dev->spi_user.reg_usr_miso = trans_desc->rx_buffer ? 1 : 0; + /* MOSI gated by tx_buffer to avoid clocking stale W0..W15 on read-only. */ + lp_spi_dev->spi_user.reg_usr_mosi = trans_desc->tx_buffer != NULL ? 1 : 0; + lp_spi_dev->spi_user.reg_usr_miso = trans_desc->rx_buffer != NULL ? 1 : 0; - /* Configure the transaction bit length */ - int tx_bitlen = trans_desc->tx_length * 8; - lp_spi_dev->spi_ms_dlen.reg_ms_data_bitlen = tx_bitlen - 1; - - /* Prepare the data to be transmitted */ - uint32_t tx_idx = 0; - uint32_t rx_idx = 0; - - /* The TRM suggests that the data is sent from and received in the LP_SPI_W0_REG ~ LP_SPI_W15_REG registers. - * The following rules apply: - * 1. The first 64 bytes are sent from/received in LP_SPI_W0_REG ~ LP_SPI_W15_REG - * 2. Bytes 64 - 255 are repeatedly sent from or received in LP_SPI_W15_REG[31:24] - * 3. Subsequent blocks of 256 bytes of data continue to follow the above rules - * - * This driver, however, avoids using the LP_SPI_W15_REG altogether. In other words, - * this driver sends or receives data in chunks of 60 bytes (LP_SPI_W0_REG ~ LP_SPI_W14_REG) - * and does not handle the repeated use of the high-byte of LP_SPI_W15_REG. This design approach - * has been chosen to simplify the data handling logic. + /* Drive the bus one hardware transaction at a time. Each iteration: + * 1. clip ``chunk`` to the remaining bytes, capped at LP_SPI_CHUNK_BYTES; + * 2. preload ``tx_chunk`` TX bytes into W0.. (only if the caller still + * has TX bytes left for this chunk -- TX may end before RX); + * 3. program the shared bit-length register for ``chunk * 8`` SCKs; + * 4. AFIFO reset + apply config + kick (ordering as per the TRM). + * 5. block on TRANS_DONE. + * 6. drain ``rx_chunk`` RX bytes from W0.. (only if the caller still + * wants RX bytes for this chunk -- RX may end before TX). */ - uint8_t max_data_reg_num = (SOC_LP_SPI_MAXIMUM_BUFFER_SIZE / 4) - 1; // 15 - uint8_t max_data_chunk_size = max_data_reg_num * 4; // 60 - while (tx_idx < trans_desc->tx_length) { - /* Store 4 bytes of data in the data buffer registers serially. */ - lp_spi_dev->data_buf[(tx_idx / 4) & max_data_reg_num].reg_buf = *(uint32_t *)(trans_desc->tx_buffer + tx_idx); - tx_idx += 4; + uint32_t bus_done = 0; + while (bus_done < bus_total) { + uint32_t chunk = bus_total - bus_done; + if (chunk > LP_SPI_CHUNK_BYTES) { + chunk = LP_SPI_CHUNK_BYTES; + } - /* Begin transmission of the data if we have pushed all the data or if we have reached the maximum data chunk size */ - if ((tx_idx >= trans_desc->tx_length) || (tx_idx % max_data_chunk_size) == 0) { - /* Apply the configuration */ - lp_spi_dev->spi_cmd.reg_update = 1; - while (lp_spi_dev->spi_cmd.reg_update) { - ; + if (trans_desc->tx_buffer != NULL && bus_done < tx_total) { + uint32_t tx_chunk = tx_total - bus_done; + if (tx_chunk > chunk) { + tx_chunk = chunk; } - - /* Start the transaction */ - lp_spi_dev->spi_cmd.reg_usr = 1; - - /* Wait for the transaction to complete */ - ret = lp_core_spi_wait_for_interrupt(ticks_to_wait); - if (ret != ESP_OK) { - return ret; - } - - /* Clear the transaction done interrupt */ - lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; - - /* Fetch the received data if an Rx buffer is provided */ - if (trans_desc->rx_buffer != NULL) { - while (rx_idx < tx_idx) { - *(uint32_t *)(trans_desc->rx_buffer + rx_idx) = lp_spi_dev->data_buf[(rx_idx / 4) & max_data_reg_num].reg_buf; - rx_idx += 4; - // This loop would exit even if we haven't received all the data. - } - } - } - } - - return ret; -} - -esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait) -{ - esp_err_t ret = ESP_OK; - - /* Argument sanity check - * Note: The Rx buffer is mandatory for this API. - */ - if (trans_desc == NULL || trans_desc->rx_buffer == NULL || trans_desc->rx_length == 0) { - return ESP_ERR_INVALID_ARG; - } - - /* Reset the Tx and Rx FIFOs */ - lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 1; - lp_spi_dev->spi_dma_conf.reg_rx_afifo_rst = 0; - lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 1; - lp_spi_dev->spi_dma_conf.reg_buf_afifo_rst = 0; - - /* Clear any previous interrupts. - * Note: LP SPI does not have any DMA access but the interrupt bit lives in the DMA interrupt register. - */ - lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; - - /* Set data lines */ - lp_spi_dev->spi_user.reg_usr_mosi = 1; - lp_spi_dev->spi_user.reg_usr_miso = 1; - - /* Configure the transaction bit length */ - int rx_bitlen = trans_desc->rx_length * 8; - lp_spi_dev->spi_ms_dlen.reg_ms_data_bitlen = rx_bitlen - 1; - - /* Prepare the data to be received */ - uint32_t rx_idx = 0; - uint32_t rcvd_bitlen = 0; - uint32_t rcvd_length_in_bytes = 0; - - /* The LP SPI slave receives data in the LP_SPI_W0_REG ~ LP_SPI_W15_REG registers. - * The following rules apply: - * 1. The first 64 bytes are received in LP_SPI_W0_REG ~ LP_SPI_W15_REG - * 2. The next 64 bytes are overwritten in LP_SPI_W0_REG ~ LP_SPI_W15_REG - * - * Since the peripheral has no protection against overwriting the data, we restrict the - * driver to receive up to 64 bytes of data at a time. - */ - uint32_t length_in_bytes = trans_desc->rx_length; - if (trans_desc->rx_length > SOC_LP_SPI_MAXIMUM_BUFFER_SIZE) { - /* Truncate the length to the maximum buffer size */ - length_in_bytes = SOC_LP_SPI_MAXIMUM_BUFFER_SIZE; - } - - while (rx_idx < length_in_bytes) { - /* Wait for the transmission to complete */ - ret = lp_core_spi_wait_for_interrupt(ticks_to_wait); - if (ret != ESP_OK) { - return ret; + lp_spi_write_buffer_bytes((const uint8_t *)trans_desc->tx_buffer + bus_done, tx_chunk); } - /* Fetch the received bit length */ - rcvd_bitlen = lp_spi_dev->spi_slave1.reg_slv_data_bitlen > (trans_desc->rx_length * 8) ? (trans_desc->rx_length * 8) : lp_spi_dev->spi_slave1.reg_slv_data_bitlen; - rcvd_length_in_bytes = (rcvd_bitlen + 7) / 8; + lp_spi_dev->spi_ms_dlen.reg_ms_data_bitlen = chunk * 8 - 1; - /* Read the received data */ - while (rx_idx < rcvd_length_in_bytes) { - *(uint32_t *)(trans_desc->rx_buffer + rx_idx) = lp_spi_dev->data_buf[(rx_idx / 4)].reg_buf; - rx_idx += 4; - } - - /* Clear the transaction done interrupt */ - lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; - } - - /* Prepare data for transmission if a Tx buffer is provided */ - if (trans_desc->tx_buffer != NULL) { - uint32_t tx_idx = 0; - uint32_t length_in_bytes = trans_desc->tx_length; - if (length_in_bytes > SOC_LP_SPI_MAXIMUM_BUFFER_SIZE) { - /* Truncate the length to the maximum buffer size */ - length_in_bytes = SOC_LP_SPI_MAXIMUM_BUFFER_SIZE; - } - - while (tx_idx < length_in_bytes) { - /* Store 4 bytes of data in the data buffer registers serially. */ - lp_spi_dev->data_buf[(tx_idx / 4)].reg_buf = *(uint32_t *)(trans_desc->tx_buffer + tx_idx); - tx_idx += 4; - } + lp_spi_reset_fifos(); /* Apply the configuration */ lp_spi_dev->spi_cmd.reg_update = 1; @@ -252,11 +215,159 @@ esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_ return ret; } + if (trans_desc->rx_buffer != NULL && bus_done < rx_total) { + uint32_t rx_chunk = rx_total - bus_done; + if (rx_chunk > chunk) { + rx_chunk = chunk; + } + lp_spi_read_buffer_bytes((uint8_t *)trans_desc->rx_buffer + bus_done, rx_chunk); + } + /* Clear the transaction done interrupt */ lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + + bus_done += chunk; } return ret; } +/* Arm = preload TX + start user phase, return immediately. Pair with + * lp_core_lp_spi_slave_wait(). Splitting arm/wait lets the caller signal + * the master only after the slave is actually listening for SCK. + */ +esp_err_t lp_core_lp_spi_slave_arm(lp_spi_transaction_t *trans_desc) +{ + /* Require at least one of tx_buffer/rx_buffer; length must be 0 when its + * buffer is NULL. + */ + if (trans_desc == NULL || + (trans_desc->rx_buffer == NULL && trans_desc->tx_buffer == NULL)) { + return ESP_ERR_INVALID_ARG; + } + if (trans_desc->rx_buffer != NULL && trans_desc->rx_length == 0) { + return ESP_ERR_INVALID_ARG; + } + if (trans_desc->tx_buffer != NULL && trans_desc->tx_length == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* Refuse to re-arm while a previous arm has not been waited on, + * otherwise the preload below would clobber its W0..W15 mid-transfer. + */ + if (s_slave_armed) { + return ESP_ERR_INVALID_STATE; + } + + /* Clear stale TRANS_DONE so the paired wait sees only this arm. */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + + /* Slave direction is reversed vs. master: MOSI carries master->slave + * (caller RX), MISO carries slave->master (caller TX). + */ + lp_spi_dev->spi_user.reg_usr_mosi = trans_desc->rx_buffer != NULL ? 1 : 0; + lp_spi_dev->spi_user.reg_usr_miso = trans_desc->tx_buffer != NULL ? 1 : 0; + + /* Same single shared bit-length register as master + * (LP_SPI_MS_DLEN.reg_ms_data_bitlen). The slave runs a single hardware + * shot capped at LP_SPI_CHUNK_BYTES (60 B, W0..W14, W15 reserved per + * TRM); longer transfers must be split by the caller into successive + * arm/wait pairs. + */ + uint32_t rx_total = trans_desc->rx_buffer ? trans_desc->rx_length : 0; + uint32_t tx_total = trans_desc->tx_buffer ? trans_desc->tx_length : 0; + uint32_t arm_bytes = rx_total > tx_total ? rx_total : tx_total; + if (arm_bytes > LP_SPI_CHUNK_BYTES) { + arm_bytes = LP_SPI_CHUNK_BYTES; + } + lp_spi_dev->spi_ms_dlen.reg_ms_data_bitlen = arm_bytes * 8 - 1; + + /* Preload TX into W0.. for the slave to drive on MISO when the master + * starts clocking. + */ + if (trans_desc->tx_buffer != NULL) { + uint32_t tx_preload = tx_total > LP_SPI_CHUNK_BYTES + ? LP_SPI_CHUNK_BYTES + : tx_total; + lp_spi_write_buffer_bytes((const uint8_t *)trans_desc->tx_buffer, tx_preload); + } + + /* Reset AFIFOs after preload, before start. */ + lp_spi_reset_fifos(); + + /* Skip apply_config() in slave mode: reg_update is master-only and + * re-triggering it here was observed to clock out the previous + * transaction's data. + */ + lp_spi_dev->spi_cmd.reg_usr = 1; + + s_slave_armed = true; + return ESP_OK; +} + +/* Block on TRANS_DONE from the matching arm, then drain whatever the master + * actually clocked into W0..W15. Pair with lp_core_lp_spi_slave_arm(). + */ +esp_err_t lp_core_lp_spi_slave_wait(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait) +{ + if (trans_desc == NULL) { + return ESP_ERR_INVALID_ARG; + } + /* Reject ``wait()`` without a preceding ``arm()`` -- otherwise we would + * block on whatever stale TRANS_DONE happens to be latched. + */ + if (!s_slave_armed) { + return ESP_ERR_INVALID_STATE; + } + + /* Block until TRANS_DONE or timeout (ticks_to_wait is in LP CPU cycles). */ + esp_err_t ret = lp_core_spi_wait_for_interrupt(ticks_to_wait); + if (ret != ESP_OK) { + /* Clear the armed latch on the timeout path too so the caller can + * recover by issuing a fresh ``arm()``; otherwise the next + * ``arm()`` would return ESP_ERR_INVALID_STATE forever. + * ``lp_core_spi_wait_for_interrupt()`` already cleared + * TRANS_DONE on its timeout exit, so no extra latch clear here. + */ + s_slave_armed = false; + return ret; + } + + s_slave_armed = false; + + /* Clear the latch so the next arm starts from a clean state. */ + lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1; + + /* The master, not the slave, drives SCK, so the actually-received length + * is decided by the master and only known after TRANS_DONE. Query the + * hardware bit counter (LP_SPI_SLAVE1.reg_slv_data_bitlen), clamp it + * against the caller's rx_length, round up to whole bytes, then drain + * that many bytes from W0.. into rx_buffer. + */ + if (trans_desc->rx_buffer != NULL) { + uint32_t rx_total = trans_desc->rx_length; + uint32_t slave_bitlen = lp_spi_dev->spi_slave1.reg_slv_data_bitlen; + uint32_t req_bitlen = rx_total * 8; + uint32_t valid_bitlen = slave_bitlen > req_bitlen ? req_bitlen : slave_bitlen; + uint32_t valid_bytes = (valid_bitlen + 7) / 8; + if (valid_bytes > rx_total) { + valid_bytes = rx_total; + } + if (valid_bytes > 0) { + lp_spi_read_buffer_bytes((uint8_t *)trans_desc->rx_buffer, valid_bytes); + } + } + + return ESP_OK; +} + +esp_err_t lp_core_lp_spi_slave_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait) +{ + esp_err_t ret = lp_core_lp_spi_slave_arm(trans_desc); + if (ret != ESP_OK) { + return ret; + } + return lp_core_lp_spi_slave_wait(trans_desc, ticks_to_wait); +} + #endif /* SOC_LP_SPI_SUPPORTED */ diff --git a/components/ulp/lp_core/lp_core_spi.c b/components/ulp/lp_core/lp_core_spi.c index 0acf8eed6f1..2aea61531d5 100644 --- a/components/ulp/lp_core/lp_core_spi.c +++ b/components/ulp/lp_core/lp_core_spi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,7 +34,10 @@ static esp_err_t lp_spi_config_io(gpio_num_t pin, rtc_gpio_mode_t direction, uin /* Initialize LP_IO */ ESP_RETURN_ON_ERROR(rtc_gpio_init(pin), LP_SPI_TAG, "LP IO Init failed for GPIO %d", pin); - /* Connect this LP_IO to the LP SPI pad-out and pad-in indices on the LP IO Matrix. */ + /* Set LP_IO direction */ + ESP_RETURN_ON_ERROR(rtc_gpio_set_direction(pin, direction), LP_SPI_TAG, "LP IO Set direction failed for %d", pin); + + /* Connect the LP SPI signals to the LP_IO Matrix */ ESP_RETURN_ON_ERROR(lp_gpio_connect_out_signal(pin, out_pad_idx, 0, 0), LP_SPI_TAG, "LP IO Matrix connect out signal failed for %d", pin); ESP_RETURN_ON_ERROR(lp_gpio_connect_in_signal(pin, in_pad_idx, 0), LP_SPI_TAG, "LP IO Matrix connect in signal failed for %d", pin); @@ -91,6 +94,20 @@ static void lp_spi_enable_clock_gate(void) } } +static void lp_spi_module_reset(void) +{ + /* Module-level reset of the LP SPI peripheral: all registers return to + * their power-on defaults. + */ + lpperi_dev_t *lp_peri_dev = &LPPERI; + lp_peri_dev->reset_en.rst_en_lp_spi = 1; + /* Read-back fence: ensure the reset assertion propagates through the + * bus before de-asserting. + */ + (void)lp_peri_dev->reset_en.rst_en_lp_spi; + lp_peri_dev->reset_en.rst_en_lp_spi = 0; +} + static esp_err_t lp_spi_clock_init(const lp_spi_device_config_t *dev_config) { esp_err_t ret = ESP_OK; @@ -249,6 +266,9 @@ esp_err_t lp_core_lp_spi_bus_initialize(lp_spi_host_t host_id, const lp_spi_bus_ return ESP_ERR_INVALID_ARG; } + /* Reset the LP SPI peripheral to a known state */ + lp_spi_module_reset(); + /* Connect the LP SPI peripheral to a "bus", i.e. a set of * GPIO pins defined in the bus_config structure. */ @@ -301,3 +321,23 @@ esp_err_t lp_core_lp_spi_slave_initialize(lp_spi_host_t host_id, const lp_spi_sl return ret; } + +esp_err_t lp_core_lp_spi_bus_deinit(lp_spi_host_t host_id, const lp_spi_bus_config_t *bus_config) +{ + (void)host_id; + + /* Disconnect and deinit LP GPIO pins that were used for SPI signals */ + if (bus_config != NULL) { + if (bus_config->miso_io_num != -1) { + rtc_gpio_deinit(bus_config->miso_io_num); + } + if (bus_config->mosi_io_num != -1) { + rtc_gpio_deinit(bus_config->mosi_io_num); + } + if (bus_config->sclk_io_num != -1) { + rtc_gpio_deinit(bus_config->sclk_io_num); + } + } + + return ESP_OK; +} From d93b5289ab1b8ffe7347871341e3a35637b70a16 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 29 Jul 2026 14:46:32 +0200 Subject: [PATCH 2/2] test(ulp/lp_spi): improve test stability and add cleanup Reset the LP SPI peripheral and GPIOs before and after every SPI case so each test starts from a known-clean baseline, and gate the master on an explicit slave-armed handshake so the master cannot clock the bus while the slave is still in its arm prologue. Verify received data against the expected pattern with bounded waits instead of merely logging it. Add a dedicated LP SPI multi-device pytest for esp32p4 that runs the retagged lp_core_spi cases, and tag the LP I2C case accordingly. --- .../main/lp_core/test_main_spi_slave.c | 41 ++- .../main/test_lp_core_i2c.c | 2 +- .../main/test_lp_core_spi.c | 273 ++++++++++++++---- .../pytest_lp_core_basic.py | 18 +- 4 files changed, 271 insertions(+), 63 deletions(-) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c index 81b208295e5..18ceb2b38bc 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_spi_slave.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,18 +12,49 @@ volatile lp_core_test_command_reply_t spi_test_cmd_reply = LP_CORE_COMMAND_NOK; volatile uint8_t spi_slave_tx_buf[100] = {0}; volatile uint8_t spi_slave_rx_buf[100] = {0}; volatile uint32_t spi_rx_len = 0; +volatile uint32_t spi_slave_tx_len = 0; + +/* Set by the LP slave once the hardware is armed (W0..W15 preloaded, + * reg_usr written). The HP slave-side test polls this before sending the + * "LP SPI slave ready" signal that releases the master, so the master + * cannot clock SCK while the slave is still in its arm prologue. + */ +volatile uint32_t spi_slave_armed = 0; int main(void) { - /* Setup SPI transaction */ + /* Wait for the HP core to finish writing spi_rx_len, spi_slave_tx_len, + * and spi_slave_tx_buf before we read them. The HP side sets + * spi_test_cmd_reply to LP_CORE_COMMAND_INVALID as a "go" signal + * after filling the shared-memory buffers. + */ + while (spi_test_cmd_reply == LP_CORE_COMMAND_NOK) { + } + spi_test_cmd_reply = LP_CORE_COMMAND_NOK; + + /* Setup SPI transaction. + * When spi_slave_tx_len > 0 the HP side has preloaded spi_slave_tx_buf + * with echo data that the slave should drive on MISO. + */ lp_spi_transaction_t trans_desc = { .rx_length = spi_rx_len, .rx_buffer = (uint8_t *)spi_slave_rx_buf, - .tx_buffer = NULL, + .tx_length = spi_slave_tx_len, + .tx_buffer = spi_slave_tx_len > 0 ? (uint8_t *)spi_slave_tx_buf : NULL, }; - /* Receive data */ - lp_core_lp_spi_slave_transfer(&trans_desc, -1); + /* Arm the slave hardware, then publish the armed flag so the HP test + * can release the master only after the slave is ready to clock. + */ + if (lp_core_lp_spi_slave_arm(&trans_desc) != ESP_OK) { + spi_test_cmd_reply = LP_CORE_COMMAND_NOK; + return 0; + } + spi_slave_armed = 1; + + /* Block until TRANS_DONE, then drain whatever the master clocked in. */ + lp_core_lp_spi_slave_wait(&trans_desc, -1); + spi_slave_armed = 0; /* Synchronize with the HP core running the test */ spi_test_cmd_reply = LP_CORE_COMMAND_OK; diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c index 2f20d6f0e4b..9400fd06c3b 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_i2c.c @@ -140,4 +140,4 @@ static void i2c_slave_read_write_test(void) i2c_driver_delete(I2C_SLAVE_NUM); } -TEST_CASE_MULTIPLE_DEVICES("LP-Core I2C read and write test", "[lp_core][test_env=generic_multi_device][timeout=150]", i2c_master_write_read_test, i2c_slave_read_write_test); +TEST_CASE_MULTIPLE_DEVICES("LP-Core I2C read and write test", "[lp_core_i2c][test_env=generic_multi_device][timeout=150]", i2c_master_write_read_test, i2c_slave_read_write_test); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_spi.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_spi.c index b23e99bd099..472e1d123a9 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_spi.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_spi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,8 @@ #include "lp_core_test_app_spi_slave.h" #include "ulp_lp_core.h" #include "lp_core_spi.h" +#include "driver/rtc_io.h" +#include "soc/lp_spi_struct.h" #include "unity.h" #include "test_utils.h" #include "esp_log.h" @@ -31,6 +33,45 @@ static const char* TAG = "lp_core_spi_test"; #define TEST_DATA_LEN_BYTES 42 uint8_t expected_data[100] = {0}; +/* ------------------------------------------------------------------ */ +/* Cleanup: stop LP core + module-reset LP SPI + deinit LP GPIOs */ +/* ------------------------------------------------------------------ */ + +/* Base LP SPI bus settings */ +lp_spi_host_t host_id = 0; +lp_spi_bus_config_t bus_config = { + .miso_io_num = TEST_GPIO_PIN_MISO, + .mosi_io_num = TEST_GPIO_PIN_MOSI, + .sclk_io_num = TEST_GPIO_PIN_CLK, +}; + +/** + * @brief Reset LP SPI peripheral and GPIO state to a known-clean baseline. + * + * Called at the start AND end of every SPI test so the test is + * self-contained and resilient to whatever ran before it. + */ +static void lp_spi_test_cleanup(void) +{ + ulp_lp_core_stop(); + lp_core_lp_spi_bus_deinit(host_id, &bus_config); + rtc_gpio_deinit(TEST_GPIO_PIN_CS); + + /* Explicitly clear TRANS_DONE to prevent a stale interrupt from being + * latched before the next slave_arm. + */ + LP_SPI.spi_dma_int_clr.reg_trans_done_int_clr = 1; + + /* Brief settle time for the LP peripheral reset to propagate. */ + vTaskDelay(pdMS_TO_TICKS(5)); + + ESP_LOGI(TAG, "LP SPI cleanup done"); +} + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) { TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, (firmware_end - firmware_start)) == ESP_OK); @@ -59,19 +100,25 @@ static void setup_expected_data(void) } } -/* Base LP SPI bus settings */ -lp_spi_host_t host_id = 0; -lp_spi_bus_config_t bus_config = { - .miso_io_num = TEST_GPIO_PIN_MISO, - .mosi_io_num = TEST_GPIO_PIN_MOSI, - .sclk_io_num = TEST_GPIO_PIN_CLK, -}; +/** + * @brief Preload the slave's TX buffer so it echoes the same pattern + * the master sends on MOSI back on MISO. + */ +static void setup_slave_echo_data(void) +{ + uint8_t *tx_data = (uint8_t *)&ulp_spi_slave_tx_buf; + ulp_spi_slave_tx_len = TEST_DATA_LEN_BYTES; + + for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { + tx_data[i] = (i + 1) % 256; + } +} /* Base LP SPI device settings */ lp_spi_device_config_t device = { .cs_io_num = TEST_GPIO_PIN_CS, .spi_mode = 0, - .clock_speed_hz = 10 * 1000, // 10 MHz + .clock_speed_hz = 10 * 1000, // 10 kHz .duty_cycle = 128, // 50% duty cycle }; @@ -83,8 +130,12 @@ lp_spi_slave_config_t slv_device = { static void lp_spi_master_init(int spi_flags, bool setup_master_loop_back) { - /* Initialize LP SPI bus */ - /* Setup loop back for tests which do not use an LP SPI slave for looping back the data. */ + /* Ensure a clean peripheral state before init */ + lp_spi_test_cleanup(); + + /* Initialize LP SPI bus. + * Setup loop back for tests which do not use an LP SPI slave for looping back the data. + */ bus_config.miso_io_num = setup_master_loop_back ? TEST_GPIO_PIN_MOSI : TEST_GPIO_PIN_MISO; TEST_ASSERT(lp_core_lp_spi_bus_initialize(host_id, &bus_config) == ESP_OK); @@ -95,17 +146,21 @@ static void lp_spi_master_init(int spi_flags, bool setup_master_loop_back) static void lp_spi_slave_init(int spi_flags) { + lp_spi_test_cleanup(); + /* Initialize LP SPI bus */ TEST_ASSERT(lp_core_lp_spi_bus_initialize(host_id, &bus_config) == ESP_OK); /* Add LP SPI slave device */ - if (spi_flags != 0) { - slv_device.flags = spi_flags; - } + slv_device.flags = spi_flags; TEST_ASSERT(lp_core_lp_spi_slave_initialize(host_id, &slv_device) == ESP_OK); } -static void lp_spi_master_execute_test(bool wait_for_slave_ready) +/* ------------------------------------------------------------------ */ +/* Master-side test execution */ +/* ------------------------------------------------------------------ */ + +static void lp_spi_master_execute_test(bool wait_for_slave_ready, bool verify_rx) { /* Load and run the LP core firmware */ ulp_lp_core_cfg_t lp_cfg = { @@ -114,6 +169,12 @@ static void lp_spi_master_execute_test(bool wait_for_slave_ready) load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_spi_master_bin_start, lp_core_main_spi_master_bin_end); if (wait_for_slave_ready) { + /* Tell the slave that the master's SPI bus and GPIOs are stable. + * The slave only arms after receiving this signal to avoid + * spurious TRANS_DONE from SCLK glitches during the master's + * boot / GPIO init. + */ + unity_send_signal("LP SPI master initialized"); /* Wait for the HP SPI device to be initialized */ unity_wait_for_signal("LP SPI slave ready"); } @@ -124,105 +185,202 @@ static void lp_spi_master_execute_test(bool wait_for_slave_ready) /* Start the test */ ulp_spi_test_cmd = LP_CORE_LP_SPI_WRITE_READ_TEST; + /* Wait for the test to complete */ while (ulp_spi_test_cmd != LP_CORE_NO_COMMAND) { - /* Wait for the test to complete */ vTaskDelay(1); } /* Verify the received data if we expect the data to be looped back from the LP SPI slave */ uint8_t *rx_data = (uint8_t *)&ulp_spi_master_rx_buf; - for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { - ESP_LOGI(TAG, "LP SPI master received data: 0x%02x", rx_data[i]); + + if (verify_rx) { + bool mismatch = false; + for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { + if (rx_data[i] != expected_data[i]) { + ESP_LOGE(TAG, "Master RX mismatch [%d]: expected 0x%02x got 0x%02x", + i, expected_data[i], rx_data[i]); + mismatch = true; + } + } + if (!mismatch) { + ESP_LOGI(TAG, "Master RX: all %d bytes match", TEST_DATA_LEN_BYTES); + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, ulp_spi_tx_len); + } else { + ESP_LOGI(TAG, "Master TX-only test completed (%d bytes)", TEST_DATA_LEN_BYTES); } - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, ulp_spi_tx_len); + lp_spi_test_cleanup(); } -static void lp_spi_slave_execute_test(void) +/* ------------------------------------------------------------------ */ +/* Slave-side test execution */ +/* ------------------------------------------------------------------ */ + +static void lp_spi_slave_execute_test(bool provide_echo) { - /* Load and run the LP core firmware */ + /* Wait until the master's SPI bus and GPIOs are fully initialized + * and stable before arming the slave. This prevents spurious + * TRANS_DONE triggers from SCLK glitches during the master's + * boot / GPIO init sequence (both boards are reset between tests). + */ + unity_wait_for_signal("LP SPI master initialized"); + + /* Ensure shared-memory handshake variables are in the expected + * initial state *before* loading the binary. LP RAM survives HP + * resets, so stale values from a previous test can fool the + * handshake if we don't clear them here. + */ + ulp_spi_slave_armed = 0; + ulp_spi_test_cmd_reply = LP_CORE_COMMAND_NOK; + + /* Load and run the LP core firmware. The LP core spins on + * spi_test_cmd_reply == LP_CORE_COMMAND_NOK until we release it. + */ ulp_lp_core_cfg_t lp_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_spi_slave_bin_start, lp_core_main_spi_slave_bin_end); - /* Setup expected test data */ + /* Give the LP core a moment to boot and enter its handshake spin + * loop before we write shared-memory buffers. + */ + vTaskDelay(pdMS_TO_TICKS(10)); + + /* Now that the binary is loaded (and the LP core is spinning), fill + * the shared-memory buffers with test data. + */ setup_expected_data(); + if (provide_echo) { + setup_slave_echo_data(); + } else { + ulp_spi_slave_tx_len = 0; + } + + /* Release the LP core: it will read the lengths, build its + * transaction descriptor, and arm the hardware. + */ + ulp_spi_test_cmd_reply = LP_CORE_COMMAND_INVALID; + + /* Wait for the slave hardware to be armed before releasing the + * master. Bounded wait to avoid hanging the whole test suite if the + * LP core fails for any reason. + */ + int armed_wait_ms = 0; + const int armed_timeout_ms = 5000; + while (ulp_spi_slave_armed == 0) { + vTaskDelay(pdMS_TO_TICKS(10)); + armed_wait_ms += 10; + if (armed_wait_ms >= armed_timeout_ms) { + ESP_LOGE(TAG, "LP SPI slave arm timed out after %d ms", armed_timeout_ms); + TEST_FAIL_MESSAGE("LP SPI slave did not arm in time"); + } + } + ESP_LOGI(TAG, "LP SPI slave armed after ~%d ms", armed_wait_ms); + /* Send signal to LP SPI master */ unity_send_signal("LP SPI slave ready"); /* Wait for the test to complete */ + int done_wait_ms = 0; + const int done_timeout_ms = 10000; while (ulp_spi_test_cmd_reply != LP_CORE_COMMAND_OK) { - vTaskDelay(1); + vTaskDelay(pdMS_TO_TICKS(10)); + done_wait_ms += 10; + if (done_wait_ms >= done_timeout_ms) { + ESP_LOGE(TAG, "LP SPI slave transfer timed out after %d ms", done_timeout_ms); + TEST_FAIL_MESSAGE("LP SPI slave transfer did not complete in time"); + } } /* Verify the received data */ uint8_t *rx_data = (uint8_t *)&ulp_spi_slave_rx_buf; + bool mismatch = false; for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) { - ESP_LOGI(TAG, "LP SPI slave received data: 0x%02x", rx_data[i]); + if (rx_data[i] != expected_data[i]) { + ESP_LOGE(TAG, "Slave RX mismatch [%d]: expected 0x%02x got 0x%02x", + i, expected_data[i], rx_data[i]); + mismatch = true; + } + } + if (!mismatch) { + ESP_LOGI(TAG, "Slave RX: all %d bytes match", TEST_DATA_LEN_BYTES); } TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, TEST_DATA_LEN_BYTES); + + lp_spi_test_cleanup(); } +/* ================================================================== */ +/* Individual test-case wrappers (master side) */ +/* ================================================================== */ + void test_lp_spi_master(void) { /* Initialize LP SPI in master mode */ lp_spi_master_init(0, false); /* Start the LP SPI master test */ - lp_spi_master_execute_test(true); + lp_spi_master_execute_test(true, true); } +void test_lp_spi_master_3wire(void) +{ + /* Initialize LP SPI in master mode */ + lp_spi_master_init(LP_SPI_DEVICE_3WIRE, false); + + /* In 3-Wire SIO mode the slave does not echo, so the master + * cannot verify RX data — only the slave side verifies RX. + */ + lp_spi_master_execute_test(true, false); +} + +void test_lp_spi_master_lsbfirst(void) +{ + /* Initialize LP SPI in master mode */ + lp_spi_master_init(LP_SPI_DEVICE_BIT_LSBFIRST, false); + + /* Start the LP SPI master test */ + lp_spi_master_execute_test(true, true); +} + +/* ================================================================== */ +/* Individual test-case wrappers (slave side) */ +/* ================================================================== */ + void test_lp_spi_slave(void) { /* Initialize LP SPI in slave mode */ lp_spi_slave_init(0); /* Start the LP SPI slave test */ - lp_spi_slave_execute_test(); -} -void test_lp_spi_master_3wire(void) -{ - /* Initialize LP SPI in master mode */ - int spi_flags = LP_SPI_DEVICE_3WIRE; - lp_spi_master_init(spi_flags, false); - - /* Start the LP SPI master test */ - lp_spi_master_execute_test(true); + lp_spi_slave_execute_test(true); } void test_lp_spi_slave_3wire(void) { /* Initialize LP SPI in slave mode */ - int spi_flags = LP_SPI_DEVICE_3WIRE; - lp_spi_slave_init(spi_flags); + lp_spi_slave_init(LP_SPI_DEVICE_3WIRE); /* Start the LP SPI slave test */ - lp_spi_slave_execute_test(); -} - -void test_lp_spi_master_lsbfirst(void) -{ - /* Initialize LP SPI in master mode */ - int spi_flags = LP_SPI_DEVICE_BIT_LSBFIRST; - lp_spi_master_init(spi_flags, false); - - /* Start the LP SPI master test */ - lp_spi_master_execute_test(true); + lp_spi_slave_execute_test(false); } void test_lp_spi_slave_lsbfirst(void) { /* Initialize LP SPI in slave mode */ - int spi_flags = LP_SPI_DEVICE_BIT_LSBFIRST; - lp_spi_slave_init(spi_flags); + lp_spi_slave_init(LP_SPI_DEVICE_BIT_LSBFIRST); /* Start the LP SPI slave test */ - lp_spi_slave_execute_test(); + lp_spi_slave_execute_test(true); } +/* ================================================================== */ +/* Loopback tests (single-device, no slave needed) */ +/* ================================================================== */ + /* Test LP-SPI master loopback */ TEST_CASE("LP-Core LP-SPI master loopback test", "[lp_core]") { @@ -230,20 +388,23 @@ TEST_CASE("LP-Core LP-SPI master loopback test", "[lp_core]") lp_spi_master_init(0, true); /* Start the LP SPI master test */ - lp_spi_master_execute_test(false); + lp_spi_master_execute_test(false, true); } -/* Test LP-SPI master loopback with active low CS line */ +/* Test LP-SPI master loopback with active high CS line */ TEST_CASE("LP-Core LP-SPI master loopback test with active high CS line", "[lp_core]") { /* Initialize LP SPI in master mode */ - int spi_flags = LP_SPI_DEVICE_CS_ACTIVE_HIGH; - lp_spi_master_init(spi_flags, true); + lp_spi_master_init(LP_SPI_DEVICE_CS_ACTIVE_HIGH, true); /* Start the LP SPI master test */ - lp_spi_master_execute_test(false); + lp_spi_master_execute_test(false, true); } +/* ================================================================== */ +/* Multi-device tests */ +/* ================================================================== */ + /* Test LP-SPI master and LP-SPI slave communication */ TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-SPI master and LP-SPI slave read write test", "[lp_core_spi][test_env=generic_multi_device][timeout=150]", test_lp_spi_master, test_lp_spi_slave); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py index 1961db15ae8..9915c1af8b3 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -60,6 +60,22 @@ def test_lp_core_multi_device(case_tester) -> None: # type: ignore case_tester.run_all_multi_dev_cases(reset=True) +@pytest.mark.generic_multi_device +@pytest.mark.parametrize('count', [2], indirect=True) +@pytest.mark.parametrize( + 'config', + [ + 'defaults', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_lp_spi_multi_device(case_tester: CaseTester) -> None: + spi_cases = [case for case in case_tester.test_menu if 'lp_core_spi' in case.groups] + for case in spi_cases: + case_tester.run_multi_dev_case(case=case, reset=True) + + @pytest.mark.generic_multi_device @pytest.mark.parametrize( 'target',