Merge branch 'feature/spi_slave_support_hal' into 'master'

spi_slave: add HAL support

See merge request idf/esp-idf!4830
This commit is contained in:
Jeroen Domburg 2019-05-22 13:42:11 +08:00
commit 0e7442bb7a
11 changed files with 466 additions and 203 deletions

View file

@ -715,7 +715,7 @@ static void SPI_MASTER_ISR_ATTR spi_intr(void *arg)
BaseType_t do_yield = pdFALSE;
spi_host_t *host = (spi_host_t *)arg;
assert(spi_hal_usr_is_done(&host->hal) == 1);
assert(spi_hal_usr_is_done(&host->hal));
/*------------ deal with the in-flight transaction -----------------*/
if (host->cur_cs != NO_CS) {

View file

@ -13,6 +13,9 @@
// limitations under the License.
#include <string.h>
#include <hal/spi_ll.h>
#include <hal/spi_slave_hal.h>
#include <soc/lldesc.h>
#include "driver/spi_common.h"
#include "driver/spi_slave.h"
#include "soc/dport_reg.h"
@ -62,10 +65,8 @@ typedef struct {
int id;
spi_slave_interface_config_t cfg;
intr_handle_t intr;
spi_dev_t *hw;
spi_slave_hal_context_t hal;
spi_slave_transaction_t *cur_trans;
lldesc_t *dmadesc_tx;
lldesc_t *dmadesc_rx;
uint32_t flags;
int max_transfer_sz;
QueueHandle_t trans_queue;
@ -117,7 +118,8 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b
spi_chan_claimed=spicommon_periph_claim(host, "spi slave");
SPI_CHECK(spi_chan_claimed, "host already in use", ESP_ERR_INVALID_STATE);
if ( dma_chan != 0 ) {
bool use_dma = dma_chan != 0;
if (use_dma) {
dma_chan_claimed=spicommon_dma_chan_claim(dma_chan);
if ( !dma_chan_claimed ) {
spicommon_periph_free( host );
@ -141,20 +143,15 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b
}
spicommon_cs_initialize(host, slave_config->spics_io_num, 0, !bus_is_iomux(spihost[host]));
// The slave DMA suffers from unexpected transactions. Forbid reading if DMA is enabled by disabling the CS line.
if (dma_chan != 0) freeze_cs(spihost[host]);
if (use_dma) freeze_cs(spihost[host]);
int dma_desc_ct = 0;
spihost[host]->dma_chan = dma_chan;
if (dma_chan != 0) {
if (use_dma) {
//See how many dma descriptors we need and allocate them
int dma_desc_ct = (bus_config->max_transfer_sz + SPI_MAX_DMA_LEN - 1) / SPI_MAX_DMA_LEN;
dma_desc_ct = (bus_config->max_transfer_sz + SPI_MAX_DMA_LEN - 1) / SPI_MAX_DMA_LEN;
if (dma_desc_ct == 0) dma_desc_ct = 1; //default to 4k when max is not given
spihost[host]->max_transfer_sz = dma_desc_ct * SPI_MAX_DMA_LEN;
spihost[host]->dmadesc_tx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
spihost[host]->dmadesc_rx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
if (!spihost[host]->dmadesc_tx || !spihost[host]->dmadesc_rx) {
ret = ESP_ERR_NO_MEM;
goto cleanup;
}
} else {
//We're limited to non-DMA transfers: the SPI work registers can hold 64 bytes at most.
spihost[host]->max_transfer_sz = 16 * 4;
@ -184,103 +181,25 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b
ret = err;
goto cleanup;
}
spihost[host]->hw = spicommon_hw_for_host(host);
//Configure slave
spihost[host]->hw->clock.val = 0;
spihost[host]->hw->user.val = 0;
spihost[host]->hw->ctrl.val = 0;
spihost[host]->hw->slave.wr_rd_buf_en = 1; //no sure if needed
spihost[host]->hw->user.doutdin = 1; //we only support full duplex
spihost[host]->hw->user.sio = 0;
spihost[host]->hw->slave.slave_mode = 1;
spihost[host]->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
spihost[host]->hw->dma_out_link.start = 0;
spihost[host]->hw->dma_in_link.start = 0;
spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
spihost[host]->hw->dma_conf.out_data_burst_en = 1;
spihost[host]->hw->slave.sync_reset = 1;
spihost[host]->hw->slave.sync_reset = 0;
spi_slave_hal_context_t *hal = &spihost[host]->hal;
spi_slave_hal_init(hal, host);
spihost[host]->hw->ctrl.rd_bit_order = (slave_config->flags & SPI_SLAVE_RXBIT_LSBFIRST) ? 1 : 0;
spihost[host]->hw->ctrl.wr_bit_order = (slave_config->flags & SPI_SLAVE_TXBIT_LSBFIRST) ? 1 : 0;
const int mode = slave_config->mode;
if (mode == 0) {
//The timing needs to be fixed to meet the requirements of DMA
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 2;
spihost[host]->hw->ctrl2.mosi_delay_num = 2;
} else if (mode == 1) {
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = 2;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 0;
} else if (mode == 2) {
//The timing needs to be fixed to meet the requirements of DMA
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 1;
spihost[host]->hw->ctrl2.mosi_delay_num = 2;
} else if (mode == 3) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = 1;
spihost[host]->hw->ctrl2.miso_delay_num = 0;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 0;
}
/* Silicon issues exists in mode 0 and 2 with DMA, change clock phase to
* avoid dma issue. This will cause slave output to appear at most half a
* spi clock before
*/
if (dma_chan != 0) {
if (mode == 0) {
spihost[host]->hw->pin.ck_idle_edge = 0;
spihost[host]->hw->user.ck_i_edge = 1;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 2;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 3;
} else if (mode == 2) {
spihost[host]->hw->pin.ck_idle_edge = 1;
spihost[host]->hw->user.ck_i_edge = 0;
spihost[host]->hw->ctrl2.miso_delay_mode = 0;
spihost[host]->hw->ctrl2.miso_delay_num = 2;
spihost[host]->hw->ctrl2.mosi_delay_mode = 0;
spihost[host]->hw->ctrl2.mosi_delay_num = 3;
if (dma_desc_ct) {
hal->dmadesc_tx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
hal->dmadesc_rx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
if (!hal->dmadesc_tx || !hal->dmadesc_rx) {
ret = ESP_ERR_NO_MEM;
goto cleanup;
}
}
hal->dmadesc_n = dma_desc_ct;
hal->rx_lsbfirst = (slave_config->flags & SPI_SLAVE_RXBIT_LSBFIRST) ? 1 : 0;
hal->tx_lsbfirst = (slave_config->flags & SPI_SLAVE_TXBIT_LSBFIRST) ? 1 : 0;
hal->mode = slave_config->mode;
hal->use_dma = use_dma;
//Reset DMA
spihost[host]->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
spihost[host]->hw->dma_out_link.start = 0;
spihost[host]->hw->dma_in_link.start = 0;
spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
//Disable unneeded ints
spihost[host]->hw->slave.rd_buf_done = 0;
spihost[host]->hw->slave.wr_buf_done = 0;
spihost[host]->hw->slave.rd_sta_done = 0;
spihost[host]->hw->slave.wr_sta_done = 0;
spihost[host]->hw->slave.rd_buf_inten = 0;
spihost[host]->hw->slave.wr_buf_inten = 0;
spihost[host]->hw->slave.rd_sta_inten = 0;
spihost[host]->hw->slave.wr_sta_inten = 0;
//Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as
//disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling
//any transactions that are queued.
spihost[host]->hw->slave.trans_inten = 1;
spihost[host]->hw->slave.trans_done = 1;
spi_slave_hal_setup_device(hal);
return ESP_OK;
@ -288,8 +207,8 @@ cleanup:
if (spihost[host]) {
if (spihost[host]->trans_queue) vQueueDelete(spihost[host]->trans_queue);
if (spihost[host]->ret_queue) vQueueDelete(spihost[host]->ret_queue);
free(spihost[host]->dmadesc_tx);
free(spihost[host]->dmadesc_rx);
free(spihost[host]->hal.dmadesc_tx);
free(spihost[host]->hal.dmadesc_rx);
#ifdef CONFIG_PM_ENABLE
if (spihost[host]->pm_lock) {
esp_pm_lock_release(spihost[host]->pm_lock);
@ -297,6 +216,7 @@ cleanup:
}
#endif
}
spi_slave_hal_deinit(&spihost[host]->hal);
free(spihost[host]);
spihost[host] = NULL;
spicommon_periph_free(host);
@ -313,8 +233,8 @@ esp_err_t spi_slave_free(spi_host_device_t host)
if ( spihost[host]->dma_chan > 0 ) {
spicommon_dma_chan_free ( spihost[host]->dma_chan );
}
free(spihost[host]->dmadesc_tx);
free(spihost[host]->dmadesc_rx);
free(spihost[host]->hal.dmadesc_tx);
free(spihost[host]->hal.dmadesc_rx);
esp_intr_free(spihost[host]->intr);
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_release(spihost[host]->pm_lock);
@ -410,46 +330,25 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg)
BaseType_t do_yield = pdFALSE;
spi_slave_transaction_t *trans = NULL;
spi_slave_t *host = (spi_slave_t *)arg;
spi_slave_hal_context_t *hal = &host->hal;
#ifdef DEBUG_SLAVE
dumpregs(host->hw);
if (host->dmadesc_rx) dumpll(&host->dmadesc_rx[0]);
#endif
//Ignore all but the trans_done int.
if (!host->hw->slave.trans_done) return;
assert(spi_slave_hal_usr_is_done(hal));
bool use_dma = host->dma_chan != 0;
if (host->cur_trans) {
// When DMA is enabled, the slave rx dma suffers from unexpected transactions. Forbid reading until transaction ready.
if (host->dma_chan != 0) freeze_cs(host);
if (use_dma) freeze_cs(host);
//when data of cur_trans->length are all sent, the slv_rdata_bit
//will be the length sent-1 (i.e. cur_trans->length-1 ), otherwise
//the length sent.
host->cur_trans->trans_len = host->hw->slv_rd_bit.slv_rdata_bit;
if (host->cur_trans->trans_len == host->cur_trans->length - 1) {
host->cur_trans->trans_len++;
}
spi_slave_hal_store_result(hal);
host->cur_trans->trans_len = spi_slave_hal_get_rcv_bitlen(hal);
if (host->dma_chan == 0 && host->cur_trans->rx_buffer) {
//Copy result out
uint32_t *data = host->cur_trans->rx_buffer;
for (int x = 0; x < host->cur_trans->trans_len; x += 32) {
uint32_t word;
int len = host->cur_trans->trans_len - x;
if (len > 32) len = 32;
word = host->hw->data_buf[(x / 32)];
memcpy(&data[x / 32], &word, (len + 7) / 8);
}
} else if (host->dma_chan != 0 && host->cur_trans->rx_buffer) {
int i;
//In case CS goes high too soon, the transfer is aborted while the DMA channel still thinks it's going. This
//leads to issues later on, so in that case we need to reset the channel. The state can be detected because
//the DMA system doesn't give back the offending descriptor; the owner is still set to DMA.
for (i = 0; host->dmadesc_rx[i].eof == 0 && host->dmadesc_rx[i].owner == 0; i++) ;
if (host->dmadesc_rx[i].owner) {
spicommon_dmaworkaround_req_reset(host->dma_chan, spi_slave_restart_after_dmareset, host);
}
if (spi_slave_hal_dma_need_reset(hal)) {
spicommon_dmaworkaround_req_reset(host->dma_chan, spi_slave_restart_after_dmareset, host);
}
if (host->cfg.post_trans_cb) host->cfg.post_trans_cb(host->cur_trans);
//Okay, transaction is done.
@ -457,7 +356,7 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg)
xQueueSendFromISR(host->ret_queue, &host->cur_trans, &do_yield);
host->cur_trans = NULL;
}
if (host->dma_chan != 0) {
if (use_dma) {
spicommon_dmaworkaround_idle(host->dma_chan);
if (spicommon_dmaworkaround_reset_in_progress()) {
//We need to wait for the reset to complete. Disable int (will be re-enabled on reset callback) and exit isr.
@ -474,70 +373,28 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg)
esp_intr_disable(host->intr);
} else {
//We have a transaction. Send it.
host->hw->slave.trans_done = 0; //clear int bit
host->cur_trans = trans;
if (host->dma_chan != 0) {
hal->bitlen = trans->length;
hal->rx_buffer = trans->rx_buffer;
hal->tx_buffer = trans->tx_buffer;
if (use_dma) {
spicommon_dmaworkaround_transfer_active(host->dma_chan);
host->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
host->hw->dma_out_link.start = 0;
host->hw->dma_in_link.start = 0;
host->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
host->hw->dma_conf.out_data_burst_en = 0;
host->hw->dma_conf.indscr_burst_en = 0;
host->hw->dma_conf.outdscr_burst_en = 0;
//Fill DMA descriptors
if (trans->rx_buffer) {
host->hw->user.usr_miso_highpart = 0;
spicommon_setup_dma_desc_links(host->dmadesc_rx, ((trans->length + 7) / 8), trans->rx_buffer, true);
host->hw->dma_in_link.addr = (int)(&host->dmadesc_rx[0]) & 0xFFFFF;
host->hw->dma_in_link.start = 1;
}
if (trans->tx_buffer) {
spicommon_setup_dma_desc_links(host->dmadesc_tx, (trans->length + 7) / 8, trans->tx_buffer, false);
host->hw->user.usr_mosi_highpart = 0;
host->hw->dma_out_link.addr = (int)(&host->dmadesc_tx[0]) & 0xFFFFF;
host->hw->dma_out_link.start = 1;
}
host->hw->slave.sync_reset = 1;
host->hw->slave.sync_reset = 0;
} else {
//No DMA. Turn off SPI and copy data to transmit buffers.
host->hw->cmd.usr = 0;
host->hw->slave.sync_reset = 1;
host->hw->slave.sync_reset = 0;
host->hw->user.usr_miso_highpart = 0;
host->hw->user.usr_mosi_highpart = 0;
if (trans->tx_buffer) {
const uint32_t *data = host->cur_trans->tx_buffer;
for (int x = 0; x < trans->length; x += 32) {
uint32_t word;
memcpy(&word, &data[x / 32], 4);
host->hw->data_buf[(x / 32)] = word;
}
}
}
host->hw->slv_rd_bit.slv_rdata_bit = 0;
host->hw->slv_wrbuf_dlen.bit_len = trans->length - 1;
host->hw->slv_rdbuf_dlen.bit_len = trans->length - 1;
host->hw->mosi_dlen.usr_mosi_dbitlen = trans->length - 1;
host->hw->miso_dlen.usr_miso_dbitlen = trans->length - 1;
host->hw->user.usr_mosi = (trans->tx_buffer == NULL) ? 0 : 1;
host->hw->user.usr_miso = (trans->rx_buffer == NULL) ? 0 : 1;
spi_slave_hal_prepare_data(hal);
//The slave rx dma get disturbed by unexpected transaction. Only connect the CS when slave is ready.
if (host->dma_chan != 0) restore_cs(host);
if (use_dma) {
restore_cs(host);
}
//Kick off transfer
host->hw->cmd.usr = 1;
spi_slave_hal_user_start(hal);
if (host->cfg.post_setup_cb) host->cfg.post_setup_cb(trans);
}
if (do_yield) portYIELD_FROM_ISR();
}

View file

@ -9,7 +9,14 @@ if(EXISTS "${COMPONENT_DIR}/${soc_name}")
endif()
list(APPEND COMPONENT_ADD_INCLUDEDIRS include)
list(APPEND COMPONENT_SRCS "src/memory_layout_utils.c src/lldesc.c src/hal/spi_hal.c src/hal/spi_hal_iram.c src/soc_include_legacy_warn.c")
list(APPEND COMPONENT_SRCS "src/memory_layout_utils.c"
"src/lldesc.c"
"src/hal/spi_hal.c"
"src/hal/spi_hal_iram.c"
"src/hal/spi_slave_hal.c"
"src/hal/spi_slave_hal_iram.c"
"src/soc_include_legacy_warn.c"
)
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)

View file

@ -20,4 +20,6 @@ This layer should depend on the operating system as little as possible. It's a w
layer can combine basic steps into different working ways (polling, non-polling, interrupt, etc.). Without using
queues/locks/delay/loop/etc., this layer can be easily port to other os or simulation systems.
To get better performance and better porting ability, ``context``s are used to hold sustainable data and pass the parameters.
To develop your own driver, it is suggested to copy the HAL layer to your own code and keep them until manual update.

View file

@ -18,7 +18,7 @@
* See readme.md in soc/include/hal/readme.md
******************************************************************************/
// The HAL layer for SPI (common part)
// The HAL layer for SPI master (common part)
// SPI HAL usages:
// 1. initialize the bus
@ -53,7 +53,9 @@ typedef struct {
* Context that should be maintained by both the driver and the HAL.
*/
typedef struct {
/* configured by driver at initialization */
/* configured by driver at initialization, don't touch */
spi_dev_t *hw; ///< Beginning address of the peripheral registers.
/* should be configured by driver at initialization */
lldesc_t *dmadesc_tx; /**< Array of DMA descriptor used by the TX DMA.
* The amount should be larger than dmadesc_n. The driver should ensure that
* the data to be sent is shorter than the descriptors can hold.
@ -102,8 +104,6 @@ typedef struct {
uint8_t *rcv_buffer; ///< Buffer to hold the receive data.
spi_ll_io_mode_t io_mode; ///< IO mode of the master
/* auto generated at initialization, don't touch */
spi_dev_t *hw; ///< Beginning address of the peripheral registers.
} spi_hal_context_t;
/**

View file

@ -60,7 +60,7 @@ typedef enum {
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_init(spi_dev_t *hw)
static inline void spi_ll_master_init(spi_dev_t *hw)
{
//Reset DMA
hw->dma_conf.val |= SPI_LL_RST_MASK;
@ -70,7 +70,36 @@ static inline void spi_ll_init(spi_dev_t *hw)
//Reset timing
hw->ctrl2.val = 0;
//master use all 64 bytes of the buffer
//use all 64 bytes of the buffer
hw->user.usr_miso_highpart = 0;
hw->user.usr_mosi_highpart = 0;
//Disable unneeded ints
hw->slave.val &= ~SPI_LL_UNUSED_INT_MASK;
}
/**
* Initialize SPI peripheral (slave).
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_slave_init(spi_dev_t *hw)
{
//Configure slave
hw->clock.val = 0;
hw->user.val = 0;
hw->ctrl.val = 0;
hw->slave.wr_rd_buf_en = 1; //no sure if needed
hw->user.doutdin = 1; //we only support full duplex
hw->user.sio = 0;
hw->slave.slave_mode = 1;
hw->dma_conf.val |= SPI_LL_RST_MASK;
hw->dma_out_link.start = 0;
hw->dma_in_link.start = 0;
hw->dma_conf.val &= ~SPI_LL_RST_MASK;
hw->slave.sync_reset = 1;
hw->slave.sync_reset = 0;
//use all 64 bytes of the buffer
hw->user.usr_miso_highpart = 0;
hw->user.usr_mosi_highpart = 0;
@ -296,6 +325,69 @@ static inline void spi_ll_master_set_mode(spi_dev_t *hw, uint8_t mode)
}
}
/**
* Set SPI mode for the peripheral as slave.
*
* @param hw Beginning address of the peripheral registers.
* @param mode SPI mode to work at, 0-3.
*/
static inline void spi_ll_slave_set_mode(spi_dev_t *hw, const int mode, bool dma_used)
{
if (mode == 0) {
//The timing needs to be fixed to meet the requirements of DMA
hw->pin.ck_idle_edge = 1;
hw->user.ck_i_edge = 0;
hw->ctrl2.miso_delay_mode = 0;
hw->ctrl2.miso_delay_num = 0;
hw->ctrl2.mosi_delay_mode = 2;
hw->ctrl2.mosi_delay_num = 2;
} else if (mode == 1) {
hw->pin.ck_idle_edge = 1;
hw->user.ck_i_edge = 1;
hw->ctrl2.miso_delay_mode = 2;
hw->ctrl2.miso_delay_num = 0;
hw->ctrl2.mosi_delay_mode = 0;
hw->ctrl2.mosi_delay_num = 0;
} else if (mode == 2) {
//The timing needs to be fixed to meet the requirements of DMA
hw->pin.ck_idle_edge = 0;
hw->user.ck_i_edge = 1;
hw->ctrl2.miso_delay_mode = 0;
hw->ctrl2.miso_delay_num = 0;
hw->ctrl2.mosi_delay_mode = 1;
hw->ctrl2.mosi_delay_num = 2;
} else if (mode == 3) {
hw->pin.ck_idle_edge = 0;
hw->user.ck_i_edge = 0;
hw->ctrl2.miso_delay_mode = 1;
hw->ctrl2.miso_delay_num = 0;
hw->ctrl2.mosi_delay_mode = 0;
hw->ctrl2.mosi_delay_num = 0;
}
/* Silicon issues exists in mode 0 and 2 with DMA, change clock phase to
* avoid dma issue. This will cause slave output to appear at most half a
* spi clock before
*/
if (dma_used) {
if (mode == 0) {
hw->pin.ck_idle_edge = 0;
hw->user.ck_i_edge = 1;
hw->ctrl2.miso_delay_mode = 0;
hw->ctrl2.miso_delay_num = 2;
hw->ctrl2.mosi_delay_mode = 0;
hw->ctrl2.mosi_delay_num = 3;
} else if (mode == 2) {
hw->pin.ck_idle_edge = 1;
hw->user.ck_i_edge = 0;
hw->ctrl2.miso_delay_mode = 0;
hw->ctrl2.miso_delay_num = 2;
hw->ctrl2.mosi_delay_mode = 0;
hw->ctrl2.mosi_delay_num = 3;
}
}
}
/**
* Set SPI to work in full duplex or half duplex mode.
*
@ -587,7 +679,7 @@ static inline void spi_ll_master_set_cs_setup(spi_dev_t *hw, uint8_t setup)
* Configs: data
*----------------------------------------------------------------------------*/
/**
* Set the input length.
* Set the input length (master).
*
* @param hw Beginning address of the peripheral registers.
* @param bitlen input length, in bits.
@ -598,7 +690,7 @@ static inline void spi_ll_set_miso_bitlen(spi_dev_t *hw, size_t bitlen)
}
/**
* Set the output length.
* Set the output length (master).
*
* @param hw Beginning address of the peripheral registers.
* @param bitlen output length, in bits.
@ -608,6 +700,28 @@ static inline void spi_ll_set_mosi_bitlen(spi_dev_t *hw, size_t bitlen)
hw->mosi_dlen.usr_mosi_dbitlen = bitlen - 1;
}
/**
* Set the maximum input length (slave).
*
* @param hw Beginning address of the peripheral registers.
* @param bitlen input length, in bits.
*/
static inline void spi_ll_slave_set_rx_bitlen(spi_dev_t *hw, size_t bitlen)
{
hw->slv_wrbuf_dlen.bit_len = bitlen - 1;
}
/**
* Set the maximum output length (slave).
*
* @param hw Beginning address of the peripheral registers.
* @param bitlen output length, in bits.
*/
static inline void spi_ll_slave_set_tx_bitlen(spi_dev_t *hw, size_t bitlen)
{
hw->slv_rdbuf_dlen.bit_len = bitlen - 1;
}
/**
* Set the length of command phase.
*
@ -722,6 +836,29 @@ static inline void spi_ll_enable_mosi(spi_dev_t *hw, int enable)
hw->user.usr_mosi = enable;
}
/**
* Reset the slave peripheral before next transaction.
*
* @param hw Beginning address of the peripheral registers.
*/
static inline void spi_ll_slave_reset(spi_dev_t *hw)
{
hw->slave.sync_reset = 1;
hw->slave.sync_reset = 0;
}
/**
* Get the received bit length of the slave.
*
* @param hw Beginning address of the peripheral registers.
*
* @return Received bits of the slave.
*/
static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw)
{
return hw->slv_rd_bit.slv_rdata_bit;
}
#undef SPI_LL_RST_MASK
#undef SPI_LL_UNUSED_INT_MASK

View file

@ -0,0 +1,151 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in soc/include/hal/readme.md
******************************************************************************/
// The HAL layer for SPI slave (common part)
// SPI slave HAL usages:
// 1. initialize the bus
// 2. initialize the DMA descriptors if DMA used
// 3. call setup_device to update parameters for the device
// 4. prepare data to send, and prepare the receiving buffer
// 5. trigger user defined SPI transaction to start
// 6. wait until the user transaction is done
// 7. store the received data and get the length
// 8. check and reset the DMA (if needed) before the next transaction
#pragma once
#include "soc/lldesc.h"
#include "soc/spi_struct.h"
#include <esp_types.h>
/**
* Context that should be maintained by both the driver and the HAL.
*/
typedef struct {
/* configured by driver at initialization, don't touch */
spi_dev_t *hw; ///< Beginning address of the peripheral registers.
/* should be configured by driver at initialization */
lldesc_t *dmadesc_rx; /**< Array of DMA descriptor used by the TX DMA.
* The amount should be larger than dmadesc_n. The driver should ensure that
* the data to be sent is shorter than the descriptors can hold.
*/
lldesc_t *dmadesc_tx; /**< Array of DMA descriptor used by the RX DMA.
* The amount should be larger than dmadesc_n. The driver should ensure that
* the data to be sent is shorter than the descriptors can hold.
*/
int dmadesc_n; ///< The amount of descriptors of both ``dmadesc_tx`` and ``dmadesc_rx`` that the HAL can use.
/*
* configurations to be filled after ``spi_slave_hal_init``. Updated to
* peripheral registers when ``spi_slave_hal_setup_device`` is called.
*/
struct {
uint32_t rx_lsbfirst : 1;
uint32_t tx_lsbfirst : 1;
uint32_t use_dma : 1;
};
int mode;
/*
* Transaction specific (data), all these parameters will be updated to the
* peripheral every transaction.
*/
uint32_t bitlen; ///< Expected maximum length of the transaction, in bits.
const void *tx_buffer; ///< Data to be sent
void *rx_buffer; ///< Buffer to hold the received data.
/* Other transaction result after one transaction */
uint32_t rcv_bitlen; ///< Length of the last transaction, in bits.
} spi_slave_hal_context_t;
/**
* Init the peripheral and the context.
*
* @param hal Context of the HAL layer.
* @param host_id Index of the SPI peripheral. 0 for SPI1, 1 for HSPI (SPI2) and 2 for VSPI (SPI3).
*/
void spi_slave_hal_init(spi_slave_hal_context_t *hal, int host_id);
/**
* Deinit the peripheral (and the context if needed).
*
* @param hal Context of the HAL layer.
*/
void spi_slave_hal_deinit(spi_slave_hal_context_t *hal);
/**
* Setup device-related configurations according to the settings in the context.
*
* @param hal Context of the HAL layer.
*/
void spi_slave_hal_setup_device(const spi_slave_hal_context_t *hal);
/**
* Prepare the data for the current transaction.
*
* @param hal Context of the HAL layer.
*/
void spi_slave_hal_prepare_data(const spi_slave_hal_context_t *hal);
/**
* Trigger start a user-defined transaction.
*
* @param hal Context of the HAL layer.
*/
void spi_slave_hal_user_start(const spi_slave_hal_context_t *hal);
/**
* Check whether the transaction is done (trans_done is set).
*
* @param hal Context of the HAL layer.
*/
bool spi_slave_hal_usr_is_done(spi_slave_hal_context_t* hal);
/**
* Post transaction operations, fetch data from the buffer and recored the length.
*
* @param hal Context of the HAL layer.
*/
void spi_slave_hal_store_result(spi_slave_hal_context_t *hal);
/**
* Get the length of last transaction, in bits. Should be called after ``spi_slave_hal_store_result``.
*
* Note that if last transaction is longer than configured before, the return
* value will be truncated to the configured length.
*
* @param hal Context of the HAL layer.
*
* @return Length of the last transaction, in bits.
*/
uint32_t spi_slave_hal_get_rcv_bitlen(spi_slave_hal_context_t *hal);
/**
* Check whether we need to reset the DMA according to the status of last transactions.
*
* In ESP32, sometimes we may need to reset the DMA for the slave before the
* next transaction. Call this to check it.
*
* @param hal Context of the HAL layer.
*
* @return true if reset is needed, else false.
*/
bool spi_slave_hal_dma_need_reset(const spi_slave_hal_context_t *hal);

View file

@ -11,4 +11,5 @@ entries:
rtc_time (noflash_text)
rtc_wdt (noflash_text)
spi_hal_iram (noflash_text)
spi_slave_hal_iram (noflash_text)
lldesc (noflash_text)

View file

@ -29,7 +29,8 @@ void spi_hal_init(spi_hal_context_t *hal, int host_id)
memset(hal, 0, sizeof(spi_hal_context_t));
spi_dev_t *hw = spi_periph_signal[host_id].hw;
hal->hw = hw;
spi_ll_init(hw);
spi_ll_master_init(hw);
//Force a transaction done interrupt. This interrupt won't fire yet because
//we initialized the SPI interrupt as disabled. This way, we can just
//enable the SPI interrupt and the interrupt handler will kick in, handling

View file

@ -0,0 +1,29 @@
#include "hal/spi_slave_hal.h"
#include "hal/spi_ll.h"
void spi_slave_hal_init(spi_slave_hal_context_t *hal, int host_id)
{
memset(hal, 0, sizeof(spi_slave_hal_context_t));
spi_dev_t *hw = spi_periph_signal[host_id].hw;
hal->hw = hw;
spi_ll_slave_init(hal->hw);
//Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as
//disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling
//any transactions that are queued.
spi_ll_enable_int(hal->hw);
spi_ll_set_int_stat(hal->hw);
}
void spi_slave_hal_setup_device(const spi_slave_hal_context_t *hal)
{
spi_ll_set_rx_lsbfirst(hal->hw, hal->rx_lsbfirst);
spi_ll_set_tx_lsbfirst(hal->hw, hal->tx_lsbfirst);
spi_ll_slave_set_mode(hal->hw, hal->mode, hal->use_dma);
}
void spi_slave_hal_deinit(spi_slave_hal_context_t *hal)
{
}

View file

@ -0,0 +1,78 @@
#include "hal/spi_slave_hal.h"
#include "hal/spi_ll.h"
bool spi_slave_hal_usr_is_done(spi_slave_hal_context_t* hal)
{
return spi_ll_usr_is_done(hal->hw);
}
void spi_slave_hal_user_start(const spi_slave_hal_context_t *hal)
{
spi_ll_clear_int_stat(hal->hw); //clear int bit
spi_ll_user_start(hal->hw);
}
void spi_slave_hal_prepare_data(const spi_slave_hal_context_t *hal)
{
if (hal->use_dma) {
spi_ll_reset_dma(hal->hw);
//Fill DMA descriptors
if (hal->rx_buffer) {
lldesc_setup_link(hal->dmadesc_rx, hal->rx_buffer, ((hal->bitlen + 7) / 8), true);
spi_ll_rxdma_start(hal->hw, &hal->dmadesc_rx[0]);
}
if (hal->tx_buffer) {
lldesc_setup_link(hal->dmadesc_tx, hal->tx_buffer, (hal->bitlen + 7) / 8, false);
spi_ll_txdma_start(hal->hw, (&hal->dmadesc_tx[0]));
}
} else {
//No DMA. Turn off SPI and copy data to transmit buffers.
if (hal->tx_buffer) {
spi_ll_write_buffer(hal->hw, hal->tx_buffer, hal->bitlen);
}
}
spi_ll_slave_reset(hal->hw);
spi_ll_slave_set_rx_bitlen(hal->hw, hal->bitlen);
spi_ll_slave_set_tx_bitlen(hal->hw, hal->bitlen);
spi_ll_enable_mosi(hal->hw, (hal->tx_buffer == NULL) ? 0 : 1);
spi_ll_enable_miso(hal->hw, (hal->rx_buffer == NULL) ? 0 : 1);
}
void spi_slave_hal_store_result(spi_slave_hal_context_t *hal)
{
//when data of cur_trans->length are all sent, the slv_rdata_bit
//will be the length sent-1 (i.e. cur_trans->length-1 ), otherwise
//the length sent.
hal->rcv_bitlen = spi_ll_slave_get_rcv_bitlen(hal->hw);
if (hal->rcv_bitlen == hal->bitlen - 1) {
hal->rcv_bitlen++;
}
if (!hal->use_dma && hal->rx_buffer) {
//Copy result out
spi_ll_read_buffer(hal->hw, hal->rx_buffer, hal->bitlen);
}
}
uint32_t spi_slave_hal_get_rcv_bitlen(spi_slave_hal_context_t *hal)
{
return hal->rcv_bitlen;
}
bool spi_slave_hal_dma_need_reset(const spi_slave_hal_context_t *hal)
{
bool ret;
ret = false;
if (hal->use_dma && hal->rx_buffer) {
int i;
//In case CS goes high too soon, the transfer is aborted while the DMA channel still thinks it's going. This
//leads to issues later on, so in that case we need to reset the channel. The state can be detected because
//the DMA system doesn't give back the offending descriptor; the owner is still set to DMA.
for (i = 0; hal->dmadesc_rx[i].eof == 0 && hal->dmadesc_rx[i].owner == 0; i++) {}
if (hal->dmadesc_rx[i].owner) {
ret = true;
}
}
return ret;
}