From 33db6d608e4d032d5c242f3e6b3278880fa86435 Mon Sep 17 00:00:00 2001 From: "Michael (XIAO Xufeng)" Date: Thu, 18 Apr 2019 22:13:05 +0800 Subject: [PATCH] spi_slave: add HAL support --- components/driver/spi_master.c | 2 +- components/driver/spi_slave.c | 241 ++++---------------- components/soc/CMakeLists.txt | 9 +- components/soc/include/hal/readme.md | 2 + components/soc/include/hal/spi_hal.h | 8 +- components/soc/include/hal/spi_ll.h | 145 +++++++++++- components/soc/include/hal/spi_slave_hal.h | 151 ++++++++++++ components/soc/linker.lf | 1 + components/soc/src/hal/spi_hal.c | 3 +- components/soc/src/hal/spi_slave_hal.c | 29 +++ components/soc/src/hal/spi_slave_hal_iram.c | 78 +++++++ 11 files changed, 466 insertions(+), 203 deletions(-) create mode 100644 components/soc/include/hal/spi_slave_hal.h create mode 100644 components/soc/src/hal/spi_slave_hal.c create mode 100644 components/soc/src/hal/spi_slave_hal_iram.c diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index ca0267c23..94d875f90 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -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) { diff --git a/components/driver/spi_slave.c b/components/driver/spi_slave.c index dbb7a3eac..b4a15c158 100644 --- a/components/driver/spi_slave.c +++ b/components/driver/spi_slave.c @@ -13,6 +13,9 @@ // limitations under the License. #include +#include +#include +#include #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(); } + diff --git a/components/soc/CMakeLists.txt b/components/soc/CMakeLists.txt index 2c52f2a27..3b5d84ebc 100644 --- a/components/soc/CMakeLists.txt +++ b/components/soc/CMakeLists.txt @@ -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) diff --git a/components/soc/include/hal/readme.md b/components/soc/include/hal/readme.md index e236dbbdd..17ee6b2c6 100644 --- a/components/soc/include/hal/readme.md +++ b/components/soc/include/hal/readme.md @@ -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. diff --git a/components/soc/include/hal/spi_hal.h b/components/soc/include/hal/spi_hal.h index 7f6c0da3f..ddc34e29b 100644 --- a/components/soc/include/hal/spi_hal.h +++ b/components/soc/include/hal/spi_hal.h @@ -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; /** diff --git a/components/soc/include/hal/spi_ll.h b/components/soc/include/hal/spi_ll.h index 9b51130ff..31feb8330 100644 --- a/components/soc/include/hal/spi_ll.h +++ b/components/soc/include/hal/spi_ll.h @@ -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 diff --git a/components/soc/include/hal/spi_slave_hal.h b/components/soc/include/hal/spi_slave_hal.h new file mode 100644 index 000000000..81d1706a7 --- /dev/null +++ b/components/soc/include/hal/spi_slave_hal.h @@ -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 + +/** + * 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); diff --git a/components/soc/linker.lf b/components/soc/linker.lf index 74da25bd7..d91f728b6 100644 --- a/components/soc/linker.lf +++ b/components/soc/linker.lf @@ -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) \ No newline at end of file diff --git a/components/soc/src/hal/spi_hal.c b/components/soc/src/hal/spi_hal.c index 364a4b3eb..bddf98361 100644 --- a/components/soc/src/hal/spi_hal.c +++ b/components/soc/src/hal/spi_hal.c @@ -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 diff --git a/components/soc/src/hal/spi_slave_hal.c b/components/soc/src/hal/spi_slave_hal.c new file mode 100644 index 000000000..82e656ca8 --- /dev/null +++ b/components/soc/src/hal/spi_slave_hal.c @@ -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) +{ + +} \ No newline at end of file diff --git a/components/soc/src/hal/spi_slave_hal_iram.c b/components/soc/src/hal/spi_slave_hal_iram.c new file mode 100644 index 000000000..1b97d38ba --- /dev/null +++ b/components/soc/src/hal/spi_slave_hal_iram.c @@ -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; +} \ No newline at end of file