Merge branch 'feat/lp_core_lp_spi_support' into 'master'

feat(lp-spi): Added support for LP SPI to the LP core

Closes IDF-7538 and IDF-10210

See merge request espressif/esp-idf!31492
This commit is contained in:
Sudeep Mohanty
2024-06-27 14:20:29 +08:00
18 changed files with 1138 additions and 253 deletions

View File

@ -243,6 +243,10 @@ config SOC_LP_I2C_SUPPORTED
bool
default y
config SOC_LP_SPI_SUPPORTED
bool
default y
config SOC_SPIRAM_SUPPORTED
bool
default y
@ -1279,6 +1283,14 @@ config SOC_SPI_MAX_PRE_DIVIDER
int
default 16
config SOC_LP_SPI_PERIPH_NUM
bool
default y
config SOC_LP_SPI_MAXIMUM_BUFFER_SIZE
int
default 64
config SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE
bool
default y

View File

@ -1,5 +1,5 @@
/**
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -116,7 +116,7 @@ typedef union {
uint32_t reg_clkdiv_pre:4;
uint32_t reserved_22:9;
/** reg_clk_equ_sysclk : R/W; bitpos: [31]; default: 1;
* In the master mode 1: spi_clk is eqaul to system 0: spi_clk is divided from system
* In the master mode 1: spi_clk is equal to system 0: spi_clk is divided from system
* clock. Can be configured in CONF state.
*/
uint32_t reg_clk_equ_sysclk:1;
@ -813,244 +813,19 @@ typedef union {
} lp_spi_sleep_conf1_reg_t;
/** Group: LP SPI W0 REG */
/** Type of spi_w0 register
* SPI CPU-controlled buffer0
/** Group: LP SPI Wn REG */
/** Type of spi_wn register
* SPI CPU-controlled buffer
*/
typedef union {
struct {
/** reg_buf0 : R/W/SS; bitpos: [31:0]; default: 0;
/** reg_buf : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf0:32;
uint32_t reg_buf:32;
};
uint32_t val;
} lp_spi_w0_reg_t;
/** Group: LP SPI W1 REG */
/** Type of spi_w1 register
* SPI CPU-controlled buffer1
*/
typedef union {
struct {
/** reg_buf1 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf1:32;
};
uint32_t val;
} lp_spi_w1_reg_t;
/** Group: LP SPI W2 REG */
/** Type of spi_w2 register
* SPI CPU-controlled buffer2
*/
typedef union {
struct {
/** reg_buf2 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf2:32;
};
uint32_t val;
} lp_spi_w2_reg_t;
/** Group: LP SPI W3 REG */
/** Type of spi_w3 register
* SPI CPU-controlled buffer3
*/
typedef union {
struct {
/** reg_buf3 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf3:32;
};
uint32_t val;
} lp_spi_w3_reg_t;
/** Group: LP SPI W4 REG */
/** Type of spi_w4 register
* SPI CPU-controlled buffer4
*/
typedef union {
struct {
/** reg_buf4 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf4:32;
};
uint32_t val;
} lp_spi_w4_reg_t;
/** Group: LP SPI W5 REG */
/** Type of spi_w5 register
* SPI CPU-controlled buffer5
*/
typedef union {
struct {
/** reg_buf5 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf5:32;
};
uint32_t val;
} lp_spi_w5_reg_t;
/** Group: LP SPI W6 REG */
/** Type of spi_w6 register
* SPI CPU-controlled buffer6
*/
typedef union {
struct {
/** reg_buf6 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf6:32;
};
uint32_t val;
} lp_spi_w6_reg_t;
/** Group: LP SPI W7 REG */
/** Type of spi_w7 register
* SPI CPU-controlled buffer7
*/
typedef union {
struct {
/** reg_buf7 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf7:32;
};
uint32_t val;
} lp_spi_w7_reg_t;
/** Group: LP SPI W8 REG */
/** Type of spi_w8 register
* SPI CPU-controlled buffer8
*/
typedef union {
struct {
/** reg_buf8 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf8:32;
};
uint32_t val;
} lp_spi_w8_reg_t;
/** Group: LP SPI W9 REG */
/** Type of spi_w9 register
* SPI CPU-controlled buffer9
*/
typedef union {
struct {
/** reg_buf9 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf9:32;
};
uint32_t val;
} lp_spi_w9_reg_t;
/** Group: LP SPI W10 REG */
/** Type of spi_w10 register
* SPI CPU-controlled buffer10
*/
typedef union {
struct {
/** reg_buf10 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf10:32;
};
uint32_t val;
} lp_spi_w10_reg_t;
/** Group: LP SPI W11 REG */
/** Type of spi_w11 register
* SPI CPU-controlled buffer11
*/
typedef union {
struct {
/** reg_buf11 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf11:32;
};
uint32_t val;
} lp_spi_w11_reg_t;
/** Group: LP SPI W12 REG */
/** Type of spi_w12 register
* SPI CPU-controlled buffer12
*/
typedef union {
struct {
/** reg_buf12 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf12:32;
};
uint32_t val;
} lp_spi_w12_reg_t;
/** Group: LP SPI W13 REG */
/** Type of spi_w13 register
* SPI CPU-controlled buffer13
*/
typedef union {
struct {
/** reg_buf13 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf13:32;
};
uint32_t val;
} lp_spi_w13_reg_t;
/** Group: LP SPI W14 REG */
/** Type of spi_w14 register
* SPI CPU-controlled buffer14
*/
typedef union {
struct {
/** reg_buf14 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf14:32;
};
uint32_t val;
} lp_spi_w14_reg_t;
/** Group: LP SPI W15 REG */
/** Type of spi_w15 register
* SPI CPU-controlled buffer15
*/
typedef union {
struct {
/** reg_buf15 : R/W/SS; bitpos: [31:0]; default: 0;
* data buffer
*/
uint32_t reg_buf15:32;
};
uint32_t val;
} lp_spi_w15_reg_t;
} lp_spi_wn_reg_t;
/** Group: LP SPI SLAVE REG */
@ -1062,7 +837,7 @@ typedef union {
/** reg_clk_mode : R/W; bitpos: [1:0]; default: 0;
* SPI clock mode bits. 0: SPI clock is off when CS inactive 1: SPI clock is delayed
* one cycle after CS inactive 2: SPI clock is delayed two cycles after CS inactive 3:
* SPI clock is alwasy on. Can be configured in CONF state.
* SPI clock is always on. Can be configured in CONF state.
*/
uint32_t reg_clk_mode:2;
/** reg_clk_mode_13 : R/W; bitpos: [2]; default: 0;
@ -1238,22 +1013,7 @@ typedef struct {
volatile lp_spi_sleep_conf1_reg_t spi_sleep_conf1;
volatile lp_spi_dma_int_set_reg_t spi_dma_int_set;
uint32_t reserved_050[18];
volatile lp_spi_w0_reg_t spi_w0;
volatile lp_spi_w1_reg_t spi_w1;
volatile lp_spi_w2_reg_t spi_w2;
volatile lp_spi_w3_reg_t spi_w3;
volatile lp_spi_w4_reg_t spi_w4;
volatile lp_spi_w5_reg_t spi_w5;
volatile lp_spi_w6_reg_t spi_w6;
volatile lp_spi_w7_reg_t spi_w7;
volatile lp_spi_w8_reg_t spi_w8;
volatile lp_spi_w9_reg_t spi_w9;
volatile lp_spi_w10_reg_t spi_w10;
volatile lp_spi_w11_reg_t spi_w11;
volatile lp_spi_w12_reg_t spi_w12;
volatile lp_spi_w13_reg_t spi_w13;
volatile lp_spi_w14_reg_t spi_w14;
volatile lp_spi_w15_reg_t spi_w15;
volatile lp_spi_wn_reg_t data_buf[16];
uint32_t reserved_0d8[2];
volatile lp_spi_slave_reg_t spi_slave;
volatile lp_spi_slave1_reg_t spi_slave1;
@ -1263,11 +1023,12 @@ typedef struct {
volatile lp_rnd_eco_cs_reg_t rnd_eco_cs;
volatile lp_rnd_eco_low_reg_t rnd_eco_low;
volatile lp_rnd_eco_high_reg_t rnd_eco_high;
} lp_dev_t;
} lp_spi_dev_t;
extern lp_spi_dev_t LP_SPI;
#ifndef __cplusplus
_Static_assert(sizeof(lp_dev_t) == 0x100, "Invalid size of lp_dev_t structure");
_Static_assert(sizeof(lp_spi_dev_t) == 0x100, "Invalid size of lp_dev_t structure");
#endif
#ifdef __cplusplus

View File

@ -78,6 +78,7 @@
#define SOC_LP_GPIO_MATRIX_SUPPORTED 1
#define SOC_LP_PERIPHERALS_SUPPORTED 1
#define SOC_LP_I2C_SUPPORTED 1
#define SOC_LP_SPI_SUPPORTED 1
#define SOC_SPIRAM_SUPPORTED 1
#define SOC_PSRAM_DMA_CAPABLE 1
// #define SOC_ULP_SUPPORTED 1 //TODO: IDF-7534
@ -504,6 +505,10 @@
#define SOC_MEMSPI_IS_INDEPENDENT 1
#define SOC_SPI_MAX_PRE_DIVIDER 16
/*-------------------------- LP SPI CAPS ----------------------------------------*/
#define SOC_LP_SPI_PERIPH_NUM 1
#define SOC_LP_SPI_MAXIMUM_BUFFER_SIZE 64
/*-------------------------- SPI MEM CAPS ---------------------------------------*/
#define SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE (1)
#define SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND (1)

View File

@ -65,6 +65,10 @@ if(CONFIG_ULP_COPROC_TYPE_LP_CORE)
if(CONFIG_SOC_LP_TIMER_SUPPORTED)
list(APPEND srcs "lp_core/shared/ulp_lp_core_lp_timer_shared.c")
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
list(APPEND srcs "lp_core/lp_core_spi.c")
endif()
endif()
idf_component_register(SRCS ${srcs}

View File

@ -114,7 +114,8 @@ elseif(ULP_COCPU_IS_LP_CORE)
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_print.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_panic.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_interrupt.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c")
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c")
target_link_options(${ULP_APP_NAME} PRIVATE "-nostartfiles")
target_link_options(${ULP_APP_NAME} PRIVATE "-Wl,--no-warn-rwx-segments")

View File

@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LP SPI peripheral
* Since we have just one LP SPI peripheral, we can define it as a uint32_t type for now, instead of an enum.
*/
typedef uint32_t lp_spi_host_t;
/**
* @brief LP SPI device configuration flags
*/
#define LP_SPI_DEVICE_TXBIT_LSBFIRST (1<<0) /*!< Transmit command/address/data LSB first instead of the default MSB first */
#define LP_SPI_DEVICE_RXBIT_LSBFIRST (1<<1) /*!< Receive data LSB first instead of the default MSB first */
#define LP_SPI_DEVICE_BIT_LSBFIRST (LP_SPI_DEVICE_TXBIT_LSBFIRST|LP_SPI_DEVICE_RXBIT_LSBFIRST) /*!< Transmit and receive LSB first */
#define LP_SPI_DEVICE_3WIRE (1<<2) /*!< Use MOSI (=spid) for both sending and receiving data */
#define LP_SPI_DEVICE_CS_ACTIVE_HIGH (1<<3) /*!< Make CS line active-high during a transanction instead of the default active-low state. Only available in SPI master mode. */
#define LP_SPI_DEVICE_HALF_DUPLEX (1<<4) /*!< Transmit data before receiving it, instead of simultaneously. Only available in SPI master mode. */
/**
* @brief LP SPI bus configuration parameters
*/
typedef struct {
int mosi_io_num; /*!< GPIO pin for Master out, Slave In signal, a.k.a, SPI_D. */
int miso_io_num; /*!< GPIO pin for Master in, Slave Out signal, a.k.a, SPI_Q. */
int sclk_io_num; /*!< GPIO pin for LP SPI Clock signal. */
} lp_spi_bus_config_t;
/**
* @brief LP SPI device configuration parameters
*/
typedef struct {
int cs_io_num; /*!< GPIO pin for the device Chip Select (CS) signal. */
int clock_speed_hz; /*!< SPI clock speed in Hz. */
int spi_mode; /*!< SPI mode, representing a pair of Clock Polarity (CPOL) and Clock Phase (CPHA) configuration:
- SPI Mode 0: (0, 0)
- SPI Mode 1: (0, 1)
- SPI Mode 2: (1, 0)
- SPI Mode 3: (1, 1)
*/
int duty_cycle; /*!< Duty cycle of positive SPI clock, in 1/256th increments (128 = 50% duty cycle). Setting this to 0 (=not setting it) is equivalent to setting this to 128. */
int flags; /*!< Bitwise OR of LP_SPI_DEVICE_* flags */
int cs_ena_pretrans; /*!< Amount of SPI bit-cycles the CS should be active for, before the transmission (0-16). This only works on half-duplex transactions. */
int cs_ena_posttrans; /*!< Amount of SPI bit-cycles the CS should stay active for, after the transmission (0-16). This only works on half-duplex transactions. */
} lp_spi_device_config_t;
/**
* @brief LP SPI slave configuration parameters
*/
typedef struct {
int cs_io_num; /*!< GPIO pin for the device Chip Select (CS) signal. */
int spi_mode; /*!< SPI mode, representing a pair of Clock Polarity (CPOL) and Clock Phase (CPHA) configuration:
- SPI Mode 0: (0, 0)
- SPI Mode 1: (0, 1)
- SPI Mode 2: (1, 0)
- SPI Mode 3: (1, 1)
*/
int flags; /*!< Bitwise OR of LP_SPI_DEVICE_* flags */
} lp_spi_slave_config_t;
/**
* @brief Initialize the LP SPI bus for use by the LP core
*
* @param host_id LP SPI host number
* @param bus_config LP SPI bus configuration parameters
*
* @return esp_err_t ESP_OK when successful
* ESP_ERR_INVALID_ARG if the configuration is invalid
*/
esp_err_t lp_core_lp_spi_bus_initialize(lp_spi_host_t host_id, const lp_spi_bus_config_t *bus_config);
/**
* @brief Initialize the LP SPI controller in master mode and add an SPI device to the LP SPI bus.
*
* @param host_id LP SPI host number
* @param dev_config LP SPI device configuration parameters
*
* @return esp_err_t ESP_OK when successful
* ESP_ERR_INVALID_ARG if the configuration is invalid
* ESP_FAIL if the device could not be added
*/
esp_err_t lp_core_lp_spi_bus_add_device(lp_spi_host_t host_id, const lp_spi_device_config_t *dev_config);
/**
* @brief Initialize the LP SPI controller in slave mode
*
* @param host_id LP SPI host number
* @param slave_config LP SPI slave configuration parameters
*
* @return esp_err_t ESP_OK when successful
* ESP_FAIL if the SPI controller could not be initialized in slave mode
*/
esp_err_t lp_core_lp_spi_slave_initialize(lp_spi_host_t host_id, const lp_spi_slave_config_t *slave_config);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
/**
* The LP SPI bus identifier to initiate a transaction on.
*/
typedef uint32_t lp_spi_bus_t;
/**
* This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.
*/
typedef struct {
uint32_t tx_length; /*!< Total data length to transmit in bytes */
uint32_t rx_length; /*!< Total data length to receive in bytes */
const void *tx_buffer; /*!< Pointer to the transmit buffer. Must be set for master mode transactions. Can be NULL for slave mode transactions. */
void *rx_buffer; /*!< Pointer to the receive buffer. Must be set for slave mode transactions. Can be NULL for master mode transactions. */
lp_spi_bus_t bus; /*!< The LP SPI bus to transmit the data on */
// The following are only used in master mode transactions
int command; /*!< Command data, of which the length is set in the ``command_bits`` field of this structure. */
uint32_t address; /*!< Address data, of which the length is set in the ``address_bits`` field of this structure. */
uint8_t command_bits; /*!< Default amount of bits in command phase */
uint8_t address_bits; /*!< Default amount of bits in address phase */
uint8_t dummy_bits; /*!< Amount of dummy bits to insert between address and data phase. */
} lp_spi_transaction_t;
/**
* @brief Initiate an LP SPI transaction in master mode to transmit device to an SPI device and optionally receive data
* from the device.
*
* @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_master_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait);
/**
* @brief Initiate an LP SPI transaction in slave mode to receive data from an SPI master and optionally transmit data
* back to the master.
*
* @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_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,262 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_LP_SPI_SUPPORTED
#include <stdint.h>
#include <string.h>
#include "esp_err.h"
#include "ulp_lp_core_spi.h"
#include "soc/lp_spi_struct.h"
/* Use the register structure to access LP_SPI module registers */
lp_spi_dev_t *lp_spi_dev = &LP_SPI;
static inline esp_err_t lp_core_spi_wait_for_interrupt(int32_t ticks_to_wait)
{
uint32_t to = 0;
while (!lp_spi_dev->spi_dma_int_raw.reg_trans_done_int_raw) {
if (ticks_to_wait > -1) {
/* If the ticks_to_wait value is not -1, keep track of ticks and
* break from the loop once the timeout is reached.
*/
to++;
if (to >= ticks_to_wait) {
/* Clear interrupt bits */
lp_spi_dev->spi_dma_int_clr.reg_trans_done_int_clr = 1;
return ESP_ERR_TIMEOUT;
}
}
}
return ESP_OK;
}
//////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// Public APIs ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
esp_err_t lp_core_lp_spi_master_transfer(lp_spi_transaction_t *trans_desc, int32_t ticks_to_wait)
{
esp_err_t ret = ESP_OK;
/* Argument sanity check
* Note: The Tx buffer is mandatory for this API.
*/
if (trans_desc == NULL || trans_desc->tx_buffer == NULL || trans_desc->tx_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;
/* Make sure that we do not have any ongoing transactions */
if (lp_spi_dev->spi_cmd.reg_usr) {
return ESP_ERR_INVALID_STATE;
}
/* Configure dummy bits */
lp_spi_dev->spi_user.reg_usr_dummy = trans_desc->dummy_bits ? 1 : 0;
if (trans_desc->dummy_bits) {
lp_spi_dev->spi_user1.reg_usr_dummy_cyclelen = trans_desc->dummy_bits - 1;
}
/* Configure the command and command bit length */
lp_spi_dev->spi_user.reg_usr_command = trans_desc->command_bits ? 1 : 0;
if (trans_desc->command_bits) {
lp_spi_dev->spi_user2.reg_usr_command_bitlen = trans_desc->command_bits - 1;
lp_spi_dev->spi_user2.reg_usr_command_value = lp_spi_dev->spi_ctrl.reg_wr_bit_order ? trans_desc->command : __builtin_bswap32(trans_desc->command << (32 - trans_desc->command_bits));
}
/* Configure the address and address bit length */
lp_spi_dev->spi_user.reg_usr_addr = trans_desc->address_bits ? 1 : 0;
if (trans_desc->address_bits) {
lp_spi_dev->spi_user1.reg_usr_addr_bitlen = trans_desc->address_bits - 1;
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;
/* 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.
*/
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;
/* 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) {
;
}
/* 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;
}
/* 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;
/* 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;
}
/* Apply the configuration */
lp_spi_dev->spi_cmd.reg_update = 1;
while (lp_spi_dev->spi_cmd.reg_update) {
;
}
/* 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;
}
return ret;
}
#endif /* SOC_LP_SPI_SUPPORTED */

View File

@ -0,0 +1,306 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "lp_core_spi.h"
#include "driver/rtc_io.h"
#include "driver/lp_io.h"
#include "hal/rtc_io_types.h"
#include "include/lp_core_spi.h"
#include "soc/lp_spi_struct.h"
#include "soc/lp_gpio_sig_map.h"
#include "soc/lpperi_struct.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/esp_clk_tree_common.h"
#include "hal/spi_ll.h"
static const char *LP_SPI_TAG = "lp_spi";
/* Use the LP SPI register structure to access peripheral registers */
lp_spi_dev_t *lp_spi_dev = &LP_SPI;
static esp_err_t lp_spi_config_io(gpio_num_t pin, rtc_gpio_mode_t direction, uint32_t out_pad_idx, uint32_t in_pad_idx)
{
esp_err_t ret = ESP_OK;
/* If pin is -1, then it is not connected to any LP_IO */
if (pin == -1) {
return ESP_OK;
}
/* Initialize LP_IO */
ESP_RETURN_ON_ERROR(rtc_gpio_init(pin), LP_SPI_TAG, "LP IO Init failed for GPIO %d", pin);
/* 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);
return ret;
}
static esp_err_t lp_spi_bus_init_io(const lp_spi_bus_config_t *bus_config)
{
esp_err_t ret = ESP_OK;
/* Argument sanity check */
#if SOC_LP_GPIO_MATRIX_SUPPORTED
/* LP SPI signals can be routed to any LP_IO */
ESP_RETURN_ON_FALSE((rtc_gpio_is_valid_gpio(bus_config->mosi_io_num)), ESP_FAIL, LP_SPI_TAG, "mosi_io_num error");
ESP_RETURN_ON_FALSE((bus_config->miso_io_num == -1) || (rtc_gpio_is_valid_gpio(bus_config->miso_io_num)), ESP_FAIL, LP_SPI_TAG, "miso_io_num error");
ESP_RETURN_ON_FALSE((rtc_gpio_is_valid_gpio(bus_config->sclk_io_num)), ESP_FAIL, LP_SPI_TAG, "sclk_io_num error");
/* Configure miso pin*/
ret = lp_spi_config_io(bus_config->miso_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_Q_PAD_OUT_IDX, LP_SPI_Q_PAD_IN_IDX);
/* Configure mosi pin */
ret = lp_spi_config_io(bus_config->mosi_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_D_PAD_OUT_IDX, LP_SPI_D_PAD_IN_IDX);
/* Configure sclk pin */
ret = lp_spi_config_io(bus_config->sclk_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_CK_PAD_OUT_IDX, LP_SPI_CK_PAD_IN_IDX);
#else
#error "LP SPI bus initialization is not supported without LP GPIO Matrix."
#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */
return ret;
}
static esp_err_t lp_spi_cs_pin_init(int cs_io_num)
{
esp_err_t ret = ESP_OK;
#if SOC_LP_GPIO_MATRIX_SUPPORTED
/* CS signal can be routed to any LP_IO */
ESP_RETURN_ON_FALSE((rtc_gpio_is_valid_gpio(cs_io_num)), ESP_FAIL, LP_SPI_TAG, "cs_io_num error");
/* Configure CS pin */
ret = lp_spi_config_io(cs_io_num, RTC_GPIO_MODE_INPUT_OUTPUT, LP_SPI_CS_PAD_OUT_IDX, LP_SPI_CS_PAD_IN_IDX);
#else
#error "LP SPI device Chip Select (CS) initialization is not supported without LP GPIO Matrix."
#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */
return ret;
}
static void lp_spi_enable_clock_gate(void)
{
lpperi_dev_t *lp_peri_dev = &LPPERI;
PERIPH_RCC_ATOMIC() {
(void)__DECLARE_RCC_ATOMIC_ENV; // Avoid warnings for unused variable __DECLARE_RCC_ATOMIC_ENV
lp_peri_dev->clk_en.ck_en_lp_spi = 1;
}
}
static esp_err_t lp_spi_clock_init(const lp_spi_device_config_t *dev_config)
{
esp_err_t ret = ESP_OK;
/* Max requested clock frequency cannot be more than the LP_FAST_CLK frequency */
uint32_t max_clock_source_hz = esp_clk_tree_lp_fast_get_freq_hz(ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX);
ESP_RETURN_ON_FALSE(dev_config->clock_speed_hz <= max_clock_source_hz, ESP_ERR_INVALID_ARG, LP_SPI_TAG, "Invalid clock speed for SPI device. Max allowed = %ld Hz", max_clock_source_hz);
/* Set the duty cycle. If not specified, use 50% */
int duty_cycle = dev_config->duty_cycle ? dev_config->duty_cycle : 128;
/* Calculate the clock pre-div values. We use the HP SPI LL function here for the calculation. */
spi_ll_clock_val_t spi_clock;
spi_ll_master_cal_clock(max_clock_source_hz, dev_config->clock_speed_hz, duty_cycle, &spi_clock);
lp_spi_dev->spi_clock.val = spi_clock;
return ret;
}
static void lp_spi_master_init(void)
{
/* Initialize the LP SPI in master mode.
* (We do not have a HAL/LL layer for LP SPI, yet, so let's use the LP SPI registers directly).
*/
/* Clear Slave mode to enable Master mode */
lp_spi_dev->spi_slave.reg_slave_mode = 0;
lp_spi_dev->spi_slave.reg_clk_mode = 0;
/* Reset CS timing */
lp_spi_dev->spi_user1.reg_cs_setup_time = 0;
lp_spi_dev->spi_user1.reg_cs_hold_time = 0;
/* Use all 64 bytes of the Tx/Rx buffers in CPU controlled transfer */
lp_spi_dev->spi_user.reg_usr_mosi_highpart = 0;
lp_spi_dev->spi_user.reg_usr_miso_highpart = 0;
}
static void lp_spi_slave_init(void)
{
/* Set Slave mode */
lp_spi_dev->spi_slave.reg_slave_mode = 1;
/* Reset the SPI peripheral */
lp_spi_dev->spi_slave.reg_soft_reset = 1;
lp_spi_dev->spi_slave.reg_soft_reset = 0;
/* Configure slave */
lp_spi_dev->spi_clock.val = 0;
lp_spi_dev->spi_user.val = 0;
lp_spi_dev->spi_ctrl.val = 0;
lp_spi_dev->spi_user.reg_doutdin = 1; //we only support full duplex
lp_spi_dev->spi_user.reg_sio = 0;
/* Use all 64 bytes of the Tx/Rx buffers in CPU controlled transfer */
lp_spi_dev->spi_user.reg_usr_miso_highpart = 0;
lp_spi_dev->spi_user.reg_usr_mosi_highpart = 0;
}
static void lp_spi_master_setup_device(const lp_spi_device_config_t *dev_config)
{
/* Configure transmission bit order */
lp_spi_dev->spi_ctrl.reg_rd_bit_order = dev_config->flags & LP_SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0;
lp_spi_dev->spi_ctrl.reg_wr_bit_order = dev_config->flags & LP_SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0;
/* Configure SPI mode in master mode */
if (dev_config->spi_mode == 0) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 0;
lp_spi_dev->spi_user.reg_ck_out_edge = 0;
} else if (dev_config->spi_mode == 1) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 0;
lp_spi_dev->spi_user.reg_ck_out_edge = 1;
} else if (dev_config->spi_mode == 2) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 1;
lp_spi_dev->spi_user.reg_ck_out_edge = 1;
} else if (dev_config->spi_mode == 3) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 1;
lp_spi_dev->spi_user.reg_ck_out_edge = 0;
}
/* Configure the polarity of the CS line */
lp_spi_dev->spi_misc.reg_master_cs_pol = dev_config->flags & LP_SPI_DEVICE_CS_ACTIVE_HIGH ? 1 : 0;
/* Configure half-duplex (0) or full-duplex (1) mode for LP SPI master */
lp_spi_dev->spi_user.reg_doutdin = dev_config->flags & LP_SPI_DEVICE_HALF_DUPLEX ? 0 : 1;
/* Configure 3-Wire half-duplex mode */
lp_spi_dev->spi_user.reg_sio = dev_config->flags & LP_SPI_DEVICE_3WIRE ? 1 : 0;
/* Configure CS setup and hold times */
lp_spi_dev->spi_user1.reg_cs_setup_time = dev_config->cs_ena_pretrans == 0 ? 0 : dev_config->cs_ena_pretrans - 1;
lp_spi_dev->spi_user.reg_cs_setup = dev_config->cs_ena_pretrans ? 1 : 0;
lp_spi_dev->spi_user1.reg_cs_hold_time = dev_config->cs_ena_posttrans;
lp_spi_dev->spi_user.reg_cs_hold = dev_config->cs_ena_posttrans ? 1 : 0;
/* Select the CS pin */
lp_spi_dev->spi_misc.reg_cs0_dis = 0;
}
static void lp_spi_slave_setup_device(const lp_spi_slave_config_t *slave_config)
{
/* Configure transmission bit order */
lp_spi_dev->spi_ctrl.reg_rd_bit_order = slave_config->flags & LP_SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0;
lp_spi_dev->spi_ctrl.reg_wr_bit_order = slave_config->flags & LP_SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0;
/* Configure SPI mode in slave mode */
if (slave_config->spi_mode == 0) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 0;
lp_spi_dev->spi_user.reg_rsck_i_edge = 0;
lp_spi_dev->spi_user.reg_tsck_i_edge = 0;
lp_spi_dev->spi_slave.reg_clk_mode_13 = 0;
} else if (slave_config->spi_mode == 1) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 0;
lp_spi_dev->spi_user.reg_rsck_i_edge = 1;
lp_spi_dev->spi_user.reg_tsck_i_edge = 1;
lp_spi_dev->spi_slave.reg_clk_mode_13 = 1;
} else if (slave_config->spi_mode == 2) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 1;
lp_spi_dev->spi_user.reg_rsck_i_edge = 1;
lp_spi_dev->spi_user.reg_tsck_i_edge = 1;
lp_spi_dev->spi_slave.reg_clk_mode_13 = 0;
} else if (slave_config->spi_mode == 3) {
lp_spi_dev->spi_misc.reg_ck_idle_edge = 1;
lp_spi_dev->spi_user.reg_rsck_i_edge = 0;
lp_spi_dev->spi_user.reg_tsck_i_edge = 0;
lp_spi_dev->spi_slave.reg_clk_mode_13 = 1;
}
if (slave_config->flags & LP_SPI_DEVICE_CS_ACTIVE_HIGH) {
ESP_LOGW(LP_SPI_TAG, "Active high CS line is not supported in slave mode. Using active low CS line.");
}
lp_spi_dev->spi_misc.reg_slave_cs_pol = 0;
if (slave_config->flags & LP_SPI_DEVICE_HALF_DUPLEX) {
ESP_LOGW(LP_SPI_TAG, "Half-duplex mode is not supported in slave mode. Using full-duplex mode.");
}
lp_spi_dev->spi_user.reg_doutdin = 1;
/* Configure 3-Wire half-duplex mode */
lp_spi_dev->spi_user.reg_sio = slave_config->flags & LP_SPI_DEVICE_3WIRE ? 1 : 0;
/* Select the CS pin */
lp_spi_dev->spi_misc.reg_cs0_dis = 0;
}
//////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// Public APIs ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
esp_err_t lp_core_lp_spi_bus_initialize(lp_spi_host_t host_id, const lp_spi_bus_config_t *bus_config)
{
(void)host_id;
/* Sanity check arguments */
if (bus_config == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Connect the LP SPI peripheral to a "bus", i.e. a set of
* GPIO pins defined in the bus_config structure.
*/
esp_err_t ret = lp_spi_bus_init_io(bus_config);
return ret;
}
esp_err_t lp_core_lp_spi_bus_add_device(lp_spi_host_t host_id, const lp_spi_device_config_t *dev_config)
{
(void)host_id;
esp_err_t ret = ESP_OK;
/* Configure the CS pin */
ESP_RETURN_ON_ERROR(lp_spi_cs_pin_init(dev_config->cs_io_num), LP_SPI_TAG, "CS pin initialization failed");
/* Enable the LP SPI clock gate */
lp_spi_enable_clock_gate();
/* Lazy initialize the LP SPI in master mode */
lp_spi_master_init();
/* Configure clock */
ESP_RETURN_ON_ERROR(lp_spi_clock_init(dev_config), LP_SPI_TAG, "Clock initialization failed");
/* Setup the SPI device */
lp_spi_master_setup_device(dev_config);
return ret;
}
esp_err_t lp_core_lp_spi_slave_initialize(lp_spi_host_t host_id, const lp_spi_slave_config_t *slave_config)
{
(void)host_id;
esp_err_t ret = ESP_OK;
/* Configure the CS pin */
ESP_RETURN_ON_ERROR(lp_spi_cs_pin_init(slave_config->cs_io_num), LP_SPI_TAG, "CS pin initialization failed");
/* Enable the LP SPI clock gate */
lp_spi_enable_clock_gate();
/* Initialize the LP SPI in slave mode */
lp_spi_slave_init();
/* Setup the SPI device */
lp_spi_slave_setup_device(slave_config);
return ret;
}

View File

@ -8,6 +8,10 @@ if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
list(APPEND app_sources "test_lp_core_uart.c")
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
list(APPEND app_sources "test_lp_core_spi.c")
endif()
set(lp_core_sources "lp_core/test_main.c")
set(lp_core_sources_counter "lp_core/test_main_counter.c")
@ -25,6 +29,11 @@ if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
set(lp_core_sources_uart "lp_core/test_main_uart.c")
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
set(lp_core_sources_spi_master "lp_core/test_main_spi_master.c")
set(lp_core_sources_spi_slave "lp_core/test_main_spi_slave.c")
endif()
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "lp_core"
REQUIRES ulp unity esp_timer test_utils
@ -49,3 +58,8 @@ endif()
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
ulp_embed_binary(lp_core_test_app_uart "${lp_core_sources_uart}" "${lp_core_exp_dep_srcs}")
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
ulp_embed_binary(lp_core_test_app_spi_master "${lp_core_sources_spi_master}" "${lp_core_exp_dep_srcs}")
ulp_embed_binary(lp_core_test_app_spi_slave "${lp_core_sources_spi_slave}" "${lp_core_exp_dep_srcs}")
endif()

View File

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "ulp_lp_core_spi.h"
#include "test_shared.h"
volatile lp_core_test_commands_t spi_test_cmd = LP_CORE_NO_COMMAND;
volatile uint8_t spi_master_tx_buf[100] = {0};
volatile uint8_t spi_master_rx_buf[100] = {0};
volatile uint32_t spi_tx_len = 0;
int main(void)
{
/* Wait for the HP core to start the test */
while (spi_test_cmd == LP_CORE_NO_COMMAND) {
}
/* Setup SPI transaction */
lp_spi_transaction_t trans_desc = {
.tx_length = spi_tx_len,
.rx_length = spi_tx_len,
.tx_buffer = (uint8_t *)spi_master_tx_buf,
.rx_buffer = (uint8_t *)spi_master_rx_buf,
};
/* Transmit data */
lp_core_lp_spi_master_transfer(&trans_desc, -1);
/* Synchronize with the HP core running the test */
spi_test_cmd = LP_CORE_NO_COMMAND;
return 0;
}

View File

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "ulp_lp_core_spi.h"
#include "test_shared.h"
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;
int main(void)
{
/* Setup SPI transaction */
lp_spi_transaction_t trans_desc = {
.rx_length = spi_rx_len,
.rx_buffer = (uint8_t *)spi_slave_rx_buf,
.tx_buffer = NULL,
};
/* Receive data */
lp_core_lp_spi_slave_transfer(&trans_desc, -1);
/* Synchronize with the HP core running the test */
spi_test_cmd_reply = LP_CORE_COMMAND_OK;
return 0;
}

View File

@ -24,6 +24,7 @@ typedef enum {
LP_CORE_LP_UART_READ_TEST,
LP_CORE_LP_UART_MULTI_BYTE_READ_TEST,
LP_CORE_LP_UART_PRINT_TEST,
LP_CORE_LP_SPI_WRITE_READ_TEST,
LP_CORE_NO_COMMAND,
} lp_core_test_commands_t;

View File

@ -0,0 +1,254 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "lp_core_test_app_spi_master.h"
#include "lp_core_test_app_spi_slave.h"
#include "ulp_lp_core.h"
#include "lp_core_spi.h"
#include "unity.h"
#include "test_utils.h"
#include "esp_log.h"
#include "test_shared.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
extern const uint8_t lp_core_main_spi_master_bin_start[] asm("_binary_lp_core_test_app_spi_master_bin_start");
extern const uint8_t lp_core_main_spi_master_bin_end[] asm("_binary_lp_core_test_app_spi_master_bin_end");
extern const uint8_t lp_core_main_spi_slave_bin_start[] asm("_binary_lp_core_test_app_spi_slave_bin_start");
extern const uint8_t lp_core_main_spi_slave_bin_end[] asm("_binary_lp_core_test_app_spi_slave_bin_end");
static const char* TAG = "lp_core_spi_test";
#define TEST_GPIO_PIN_MISO 6
#define TEST_GPIO_PIN_MOSI 7
#define TEST_GPIO_PIN_CLK 8
#define TEST_GPIO_PIN_CS 4
#define TEST_DATA_LEN_BYTES 42
uint8_t expected_data[100] = {0};
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);
TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK);
}
static void setup_test_data(void)
{
uint8_t *tx_data = (uint8_t *)&ulp_spi_master_tx_buf;
ulp_spi_tx_len = TEST_DATA_LEN_BYTES;
/* Setup test data */
for (int i = 0; i < ulp_spi_tx_len; i++) {
tx_data[i] = (i + 1) % 256;
expected_data[i] = tx_data[i];
}
}
static void setup_expected_data(void)
{
ulp_spi_rx_len = TEST_DATA_LEN_BYTES;
/* Setup expected data */
for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) {
expected_data[i] = (i + 1) % 256;
}
}
/* 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,
};
/* 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
.duty_cycle = 128, // 50% duty cycle
};
/* Base LP SPI slave device settings */
lp_spi_slave_config_t slv_device = {
.cs_io_num = TEST_GPIO_PIN_CS,
.spi_mode = 0,
};
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. */
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);
/* Add LP SPI device */
device.flags = spi_flags;
TEST_ASSERT(lp_core_lp_spi_bus_add_device(host_id, &device) == ESP_OK);
}
static void lp_spi_slave_init(int spi_flags)
{
/* 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;
}
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)
{
/* Load and run the LP core firmware */
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_master_bin_start, lp_core_main_spi_master_bin_end);
if (wait_for_slave_ready) {
/* Wait for the HP SPI device to be initialized */
unity_wait_for_signal("LP SPI slave ready");
}
/* Setup test data */
setup_test_data();
/* Start the test */
ulp_spi_test_cmd = LP_CORE_LP_SPI_WRITE_READ_TEST;
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]);
}
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, ulp_spi_tx_len);
}
static void lp_spi_slave_execute_test(void)
{
/* Load and run the LP core firmware */
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 */
setup_expected_data();
/* Send signal to LP SPI master */
unity_send_signal("LP SPI slave ready");
/* Wait for the test to complete */
while (ulp_spi_test_cmd_reply != LP_CORE_COMMAND_OK) {
vTaskDelay(1);
}
/* Verify the received data */
uint8_t *rx_data = (uint8_t *)&ulp_spi_slave_rx_buf;
for (int i = 0; i < TEST_DATA_LEN_BYTES; i++) {
ESP_LOGI(TAG, "LP SPI slave received data: 0x%02x", rx_data[i]);
}
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_data, TEST_DATA_LEN_BYTES);
}
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);
}
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);
}
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);
/* 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);
}
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);
/* Start the LP SPI slave test */
lp_spi_slave_execute_test();
}
/* Test LP-SPI master loopback */
TEST_CASE("LP-Core LP-SPI master loopback test", "[lp_core]")
{
/* Initialize LP SPI in master mode */
lp_spi_master_init(0, true);
/* Start the LP SPI master test */
lp_spi_master_execute_test(false);
}
/* Test LP-SPI master loopback with active low 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);
/* Start the LP SPI master test */
lp_spi_master_execute_test(false);
}
/* 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);
/* Test LP-SPI master in 3-Wire SPI mode */
TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-SPI master and LP-SPI slave in 3-Wire SPI mode", "[lp_core_spi][test_env=generic_multi_device][timeout=150]", test_lp_spi_master_3wire, test_lp_spi_slave_3wire);
/* Test LP-SPI master and LP-SPI slave in LSB first mode */
TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-SPI master and LP-SPI in LSB first SPI mode", "[lp_core_spi][test_env=generic_multi_device][timeout=150]", test_lp_spi_master_lsbfirst, test_lp_spi_slave_lsbfirst);

View File

@ -7,6 +7,7 @@ components/ulp/lp_core/lp_core/include/ulp_lp_core_print.h
components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h
components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h
components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h
components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h
# ESSL headers do not belong to any IDF component, in a user project it will come from a managed component
components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio.h
components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h

View File

@ -1,6 +1,7 @@
INPUT += \
$(PROJECT_PATH)/components/ulp/lp_core/include/lp_core_i2c.h \
$(PROJECT_PATH)/components/ulp/lp_core/include/lp_core_uart.h \
$(PROJECT_PATH)/components/ulp/lp_core/include/lp_core_spi.h \
$(PROJECT_PATH)/components/ulp/lp_core/include/ulp_lp_core.h \
$(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_gpio.h \
$(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h \
@ -8,6 +9,7 @@ INPUT += \
$(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h \
$(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_utils.h \
$(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h \
$(PROJECT_PATH)/components/ulp/lp_core/lp_core/include/ulp_lp_core_spi.h \
$(PROJECT_PATH)/components/ulp/ulp_common/include/ulp_common.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_host.h \

View File

@ -168,6 +168,7 @@ To enhance the capabilities of the ULP LP-Core coprocessor, it has access to per
* LP IO
* LP I2C
* LP UART
:SOC_LP_SPI_SUPPORTED: * LP SPI
.. only:: CONFIG_ESP_ROM_HAS_LP_ROM
@ -240,6 +241,10 @@ Main CPU API Reference
.. include-build-file:: inc/lp_core_i2c.inc
.. include-build-file:: inc/lp_core_uart.inc
.. only:: CONFIG_SOC_LP_SPI_SUPPORTED
.. include-build-file:: inc/lp_core_spi.inc
LP Core API Reference
~~~~~~~~~~~~~~~~~~~~~~
@ -250,4 +255,8 @@ LP Core API Reference
.. include-build-file:: inc/ulp_lp_core_print.inc
.. include-build-file:: inc/ulp_lp_core_interrupts.inc
.. only:: CONFIG_SOC_LP_SPI_SUPPORTED
.. include-build-file:: inc/ulp_lp_core_spi.inc
.. _esp-idf-monitor: https://github.com/espressif/esp-idf-monitor

View File

@ -164,6 +164,7 @@ ULP LP-Core 支持的外设
* LP IO
* LP I2C
* LP UART
:SOC_LP_SPI_SUPPORTED: * LP SPI
.. only:: CONFIG_ESP_ROM_HAS_LP_ROM
@ -236,6 +237,10 @@ API 参考
.. include-build-file:: inc/lp_core_i2c.inc
.. include-build-file:: inc/lp_core_uart.inc
.. only:: CONFIG_SOC_LP_SPI_SUPPORTED
.. include-build-file:: inc/lp_core_spi.inc
LP 内核 API 参考
~~~~~~~~~~~~~~~~~~~~~~
@ -246,4 +251,8 @@ LP 内核 API 参考
.. include-build-file:: inc/ulp_lp_core_print.inc
.. include-build-file:: inc/ulp_lp_core_interrupts.inc
.. only:: CONFIG_SOC_LP_SPI_SUPPORTED
.. include-build-file:: inc/ulp_lp_core_spi.inc
.. _esp-idf-monitor: https://github.com/espressif/esp-idf-monitor