mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user