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 0def39cc015..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 */ @@ -94,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; @@ -252,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. */ @@ -304,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; +}