From af2fc96ee10ab423ea270bc134d7da9e867fb37f Mon Sep 17 00:00:00 2001 From: "Michael (XIAO Xufeng)" Date: Wed, 23 Jan 2019 17:07:03 +0800 Subject: [PATCH] spi_master: refactor and add HAL support --- components/driver/include/driver/spi_master.h | 16 +- components/driver/spi_common.c | 27 +- components/driver/spi_master.c | 540 +++---------- components/driver/test/test_common_spi.c | 2 - components/driver/test/test_spi_master.c | 22 +- components/soc/CMakeLists.txt | 2 +- components/soc/component.mk | 2 +- components/soc/esp32/include/soc/soc.h | 1 + components/soc/include/hal/hal_defs.h | 28 + components/soc/include/hal/readme.md | 23 + components/soc/include/hal/spi_hal.h | 220 ++++++ components/soc/include/hal/spi_ll.h | 727 ++++++++++++++++++ components/soc/include/soc/lldesc.h | 48 ++ components/soc/linker.lf | 6 +- components/soc/src/hal/spi_hal.c | 127 +++ components/soc/src/hal/spi_hal_iram.c | 166 ++++ components/soc/src/lldesc.c | 30 + 17 files changed, 1525 insertions(+), 462 deletions(-) create mode 100644 components/soc/include/hal/hal_defs.h create mode 100644 components/soc/include/hal/readme.md create mode 100644 components/soc/include/hal/spi_hal.h create mode 100644 components/soc/include/hal/spi_ll.h create mode 100644 components/soc/include/soc/lldesc.h create mode 100644 components/soc/src/hal/spi_hal.c create mode 100644 components/soc/src/hal/spi_hal_iram.c create mode 100644 components/soc/src/lldesc.c diff --git a/components/driver/include/driver/spi_master.h b/components/driver/include/driver/spi_master.h index 143b4e1c0..3b1723381 100644 --- a/components/driver/include/driver/spi_master.h +++ b/components/driver/include/driver/spi_master.h @@ -380,9 +380,23 @@ void spi_device_release_bus(spi_device_handle_t dev); * @param hz Desired working frequency * @param duty_cycle Duty cycle of the spi clock * @param reg_o Output of value to be set in clock register, or NULL if not needed. + * + * @deprecated The app shouldn't care about the register. Call ``spi_get_actual_clock`` instead. + * * @return Actual working frequency that most fit. */ -int spi_cal_clock(int fapb, int hz, int duty_cycle, uint32_t* reg_o); +int spi_cal_clock(int fapb, int hz, int duty_cycle, uint32_t* reg_o) __attribute__((deprecated)); + +/** + * @brief Calculate the working frequency that is most close to desired frequency. + * + * @param fapb The frequency of apb clock, should be ``APB_CLK_FREQ``. + * @param hz Desired working frequency + * @param duty_cycle Duty cycle of the spi clock + * + * @return Actual working frequency that most fit. + */ +int spi_get_actual_clock(int fapb, int hz, int duty_cycle); /** * @brief Calculate the timing settings of specified frequency and settings. diff --git a/components/driver/spi_common.c b/components/driver/spi_common.c index f27a28101..701450be7 100644 --- a/components/driver/spi_common.c +++ b/components/driver/spi_common.c @@ -26,12 +26,13 @@ #include "esp_err.h" #include "soc/soc.h" #include "soc/dport_reg.h" -#include "esp32/rom/lldesc.h" +#include "soc/lldesc.h" #include "driver/gpio.h" #include "driver/periph_ctrl.h" #include "esp_heap_caps.h" #include "driver/spi_common.h" #include "stdatomic.h" +#include "hal/spi_hal.h" static const char *SPI_TAG = "spi"; @@ -387,29 +388,7 @@ void spicommon_cs_free_io(int cs_gpio_num) //Set up a list of dma descriptors. dmadesc is an array of descriptors. Data is the buffer to point to. void IRAM_ATTR spicommon_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx) { - int n = 0; - while (len) { - int dmachunklen = len; - if (dmachunklen > SPI_MAX_DMA_LEN) dmachunklen = SPI_MAX_DMA_LEN; - if (isrx) { - //Receive needs DMA length rounded to next 32-bit boundary - dmadesc[n].size = (dmachunklen + 3) & (~3); - dmadesc[n].length = (dmachunklen + 3) & (~3); - } else { - dmadesc[n].size = dmachunklen; - dmadesc[n].length = dmachunklen; - } - dmadesc[n].buf = (uint8_t *)data; - dmadesc[n].eof = 0; - dmadesc[n].sosf = 0; - dmadesc[n].owner = 1; - dmadesc[n].qe.stqe_next = &dmadesc[n + 1]; - len -= dmachunklen; - data += dmachunklen; - n++; - } - dmadesc[n - 1].eof = 1; //Mark last DMA desc as end of stream. - dmadesc[n - 1].qe.stqe_next = NULL; + lldesc_setup_link(dmadesc, data, len, isrx); } diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index 7e7effa84..620836cdd 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -1,9 +1,9 @@ -// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// 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 @@ -143,9 +143,11 @@ We have two bits to control the interrupt: #include "driver/periph_ctrl.h" #include "esp_heap_caps.h" #include "stdatomic.h" +#include "sdkconfig.h" + +#include "hal/spi_hal.h" typedef struct spi_device_t spi_device_t; -typedef typeof(SPI1.clock) spi_clock_reg_t; #define NO_CS 3 //Number of CS pins per SPI host @@ -171,9 +173,10 @@ typedef struct { } spi_trans_priv_t; typedef struct { + int id; _Atomic(spi_device_t*) device[NO_CS]; intr_handle_t intr; - spi_dev_t *hw; + spi_hal_context_t hal; spi_trans_priv_t cur_trans_buf; int cur_cs; //current device doing transaction int prev_cs; //last device doing transaction, used to avoid re-configure registers if the device not changed @@ -181,8 +184,6 @@ typedef struct { bool polling; //in process of a polling, avoid of queue new transactions into ISR bool isr_free; //the isr is not sending transactions bool bus_locked;//the bus is controlled by a device - lldesc_t *dmadesc_tx; - lldesc_t *dmadesc_rx; uint32_t flags; int dma_chan; int max_transfer_sz; @@ -192,19 +193,12 @@ typedef struct { #endif } spi_host_t; -typedef struct { - spi_clock_reg_t reg; - int eff_clk; - int dummy_num; - int miso_delay; -} clock_config_t; - struct spi_device_t { int id; QueueHandle_t trans_queue; QueueHandle_t ret_queue; spi_device_interface_config_t cfg; - clock_config_t clk_cfg; + spi_hal_timing_conf_t timing_conf; spi_host_t *host; SemaphoreHandle_t semphr_polling; //semaphore to notify the device it claimed the bus bool waiting; //the device is waiting for the exclusive control of the bus @@ -271,21 +265,15 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus goto cleanup; } + int dma_desc_ct=0; spihost[host]->dma_chan=dma_chan; if (dma_chan == 0) { spihost[host]->max_transfer_sz = 64; } else { //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=lldesc_get_required_num(bus_config->max_transfer_sz); 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; - } + spihost[host]->max_transfer_sz = dma_desc_ct*LLDESC_MAX_NUM_PER_DESC; } int flags = bus_config->intr_flags | ESP_INTR_FLAG_INTRDISABLED; @@ -294,7 +282,7 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus ret = err; goto cleanup; } - spihost[host]->hw=spicommon_hw_for_host(host); + spihost[host]->id = host; spihost[host]->cur_cs = NO_CS; spihost[host]->prev_cs = NO_CS; @@ -303,45 +291,29 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus spihost[host]->isr_free = true; spihost[host]->bus_locked = false; - //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); - //Reset timing - spihost[host]->hw->ctrl2.val=0; - - //master use all 64 bytes of the buffer - spihost[host]->hw->user.usr_miso_highpart=0; - spihost[host]->hw->user.usr_mosi_highpart=0; - - //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_hal_init(&spihost[host]->hal, host); + spihost[host]->hal.dma_enabled = (dma_chan!=0); + if (dma_desc_ct) { + spihost[host]->hal.dmadesc_tx=heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA); + spihost[host]->hal.dmadesc_rx=heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA); + if (!spihost[host]->hal.dmadesc_tx || !spihost[host]->hal.dmadesc_rx) { + ret = ESP_ERR_NO_MEM; + goto cleanup; + } + } + spihost[host]->hal.dmadesc_n = dma_desc_ct; return ESP_OK; cleanup: if (spihost[host]) { - free(spihost[host]->dmadesc_tx); - free(spihost[host]->dmadesc_rx); + spi_hal_deinit(&spihost[host]->hal); #ifdef CONFIG_PM_ENABLE if (spihost[host]->pm_lock) { esp_pm_lock_delete(spihost[host]->pm_lock); } #endif + free(spihost[host]->hal.dmadesc_rx); + free(spihost[host]->hal.dmadesc_tx); } free(spihost[host]); spihost[host] = NULL; @@ -366,12 +338,12 @@ esp_err_t spi_bus_free(spi_host_device_t host) #ifdef CONFIG_PM_ENABLE esp_pm_lock_delete(spihost[host]->pm_lock); #endif - spihost[host]->hw->slave.trans_inten=0; - spihost[host]->hw->slave.trans_done=0; + spi_hal_deinit(&spihost[host]->hal); + free(spihost[host]->hal.dmadesc_rx); + free(spihost[host]->hal.dmadesc_tx); + esp_intr_free(spihost[host]->intr); spicommon_periph_free(host); - free(spihost[host]->dmadesc_tx); - free(spihost[host]->dmadesc_rx); free(spihost[host]); spihost[host]=NULL; return ESP_OK; @@ -379,41 +351,18 @@ esp_err_t spi_bus_free(spi_host_device_t host) void spi_get_timing(bool gpio_is_used, int input_delay_ns, int eff_clk, int* dummy_o, int* cycles_remain_o) { - const int apbclk_kHz = APB_CLK_FREQ/1000; - //calculate how many apb clocks a period has - const int apbclk_n = APB_CLK_FREQ/eff_clk; - const int gpio_delay_ns = gpio_is_used ? 25 : 0; + int timing_dummy; + int timing_miso_delay; - //calculate how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. - int apb_period_n = (1 + input_delay_ns + gpio_delay_ns)*apbclk_kHz/1000/1000; - if (apb_period_n < 0) apb_period_n = 0; - - int dummy_required = apb_period_n/apbclk_n; - - int miso_delay = 0; - if (dummy_required > 0) { - //due to the clock delay between master and slave, there's a range in which data is random - //give MISO a delay if needed to make sure we sample at the time MISO is stable - miso_delay = (dummy_required+1)*apbclk_n-apb_period_n-1; - } else { - //if the dummy is not required, maybe we should also delay half a SPI clock if the data comes too early - if (apb_period_n*4 <= apbclk_n) miso_delay = -1; - } - if (dummy_o!=NULL) *dummy_o = dummy_required; - if (cycles_remain_o!=NULL) *cycles_remain_o = miso_delay; - ESP_LOGD(SPI_TAG,"eff: %d, limit: %dk(/%d), %d dummy, %d delay", eff_clk/1000, apbclk_kHz/(apb_period_n+1), apb_period_n, dummy_required, miso_delay); + spi_hal_cal_timing(eff_clk, gpio_is_used, input_delay_ns, &timing_dummy, &timing_miso_delay); + if (dummy_o) *dummy_o = timing_dummy; + if (cycles_remain_o) *cycles_remain_o = timing_miso_delay; } int spi_get_freq_limit(bool gpio_is_used, int input_delay_ns) { - const int apbclk_kHz = APB_CLK_FREQ/1000; - const int gpio_delay_ns = gpio_is_used ? 25 : 0; + return spi_hal_get_freq_limit(gpio_is_used, input_delay_ns); - //calculate how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. - int apb_period_n = (1 + input_delay_ns + gpio_delay_ns)*apbclk_kHz/1000/1000; - if (apb_period_n < 0) apb_period_n = 0; - - return APB_CLK_FREQ/(apb_period_n+1); } /* @@ -423,13 +372,8 @@ int spi_get_freq_limit(bool gpio_is_used, int input_delay_ns) esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle) { int freecs; - int apbclk=APB_CLK_FREQ; - int eff_clk; int duty_cycle; - int dummy_required; - int miso_delay; - spi_clock_reg_t clk_reg; SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host]!=NULL, "host not initialized", ESP_ERR_INVALID_STATE); SPI_CHECK(dev_config->spics_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num), "spics pin invalid", ESP_ERR_INVALID_ARG); @@ -446,18 +390,22 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_ (dev_config->flags & SPI_DEVICE_HALFDUPLEX), "In full-duplex mode, only support cs pretrans delay = 1 and without address_bits and command_bits", ESP_ERR_INVALID_ARG); duty_cycle = (dev_config->duty_cycle_pos==0) ? 128 : dev_config->duty_cycle_pos; - eff_clk = spi_cal_clock(apbclk, dev_config->clock_speed_hz, duty_cycle, (uint32_t*)&clk_reg); - int freq_limit = spi_get_freq_limit(!(spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS), dev_config->input_delay_ns); - //Speed >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections. - spi_get_timing(!(spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS), dev_config->input_delay_ns, eff_clk, &dummy_required, &miso_delay); - SPI_CHECK( dev_config->flags & SPI_DEVICE_HALFDUPLEX || dummy_required == 0 || - dev_config->flags & SPI_DEVICE_NO_DUMMY, -"When work in full-duplex mode at frequency > %.1fMHz, device cannot read correct data.\n\ -Try to use IOMUX pins to increase the frequency limit, or use the half duplex mode.\n\ -Please note the SPI master can only work at divisors of 80MHz, and the driver always tries to find the closest frequency to your configuration.\n\ -Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output data at higher speed, or read data at your own risk.", - ESP_ERR_INVALID_ARG, freq_limit/1000./1000 ); + int freq; + spi_hal_context_t *hal = &spihost[host]->hal; + hal->half_duplex = dev_config->flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0; + hal->as_cs = dev_config->flags & SPI_DEVICE_CLK_AS_CS ? 1 : 0; + hal->positive_cs = dev_config->flags & SPI_DEVICE_POSITIVE_CS ? 1 : 0; + hal->no_compensate = dev_config->flags & SPI_DEVICE_NO_DUMMY ? 1 : 0; + + spi_hal_timing_conf_t temp_timing_conf; + esp_err_t ret = spi_hal_get_clock_conf(hal, dev_config->clock_speed_hz, duty_cycle, + !(spihost[host]->flags & SPICOMMON_BUSFLAG_NATIVE_PINS), + dev_config->input_delay_ns, &freq, + &temp_timing_conf); + + SPI_CHECK(ret==ESP_OK, "assigned clock speed not supported", ret); + //Allocate memory for device spi_device_t *dev=malloc(sizeof(spi_device_t)); @@ -466,6 +414,7 @@ Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output dat atomic_store(&spihost[host]->device[freecs], dev); dev->id = freecs; dev->waiting = false; + dev->timing_conf = temp_timing_conf; //Allocate queues, set defaults dev->trans_queue = xQueueCreate(dev_config->queue_size, sizeof(spi_trans_priv_t)); @@ -480,31 +429,14 @@ Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output dat memcpy(&dev->cfg, dev_config, sizeof(spi_device_interface_config_t)); dev->cfg.duty_cycle_pos = duty_cycle; // TODO: if we have to change the apb clock among transactions, re-calculate this each time the apb clock lock is acquired. - dev->clk_cfg= (clock_config_t) { - .eff_clk = eff_clk, - .dummy_num = dummy_required, - .reg = clk_reg, - .miso_delay = miso_delay, - }; //Set CS pin, CS options if (dev_config->spics_io_num >= 0) { spicommon_cs_initialize(host, dev_config->spics_io_num, freecs, !(spihost[host]->flags&SPICOMMON_BUSFLAG_NATIVE_PINS)); } - if (dev_config->flags&SPI_DEVICE_CLK_AS_CS) { - spihost[host]->hw->pin.master_ck_sel |= (1<hw->pin.master_ck_sel &= (1<flags&SPI_DEVICE_POSITIVE_CS) { - spihost[host]->hw->pin.master_cs_pol |= (1<hw->pin.master_cs_pol &= (1<hw->ctrl2.mosi_delay_mode = 0; - spihost[host]->hw->ctrl2.mosi_delay_num = 0; + *handle=dev; - ESP_LOGD(SPI_TAG, "SPI%d: New device added to CS%d, effective clock: %dkHz", host+1, freecs, dev->clk_cfg.eff_clk/1000); + ESP_LOGD(SPI_TAG, "SPI%d: New device added to CS%d, effective clock: %dkHz", host+1, freecs, freq/1000); return ESP_OK; nomem: @@ -546,79 +478,20 @@ esp_err_t spi_bus_remove_device(spi_device_handle_t handle) return ESP_OK; } -static int spi_freq_for_pre_n(int fapb, int pre, int n) -{ - return (fapb / (pre * n)); -} - int spi_cal_clock(int fapb, int hz, int duty_cycle, uint32_t *reg_o) { - spi_clock_reg_t reg; - int eff_clk; - - //In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value. - if (hz>((fapb/4)*3)) { - //Using Fapb directly will give us the best result here. - reg.clkcnt_l=0; - reg.clkcnt_h=0; - reg.clkcnt_n=0; - reg.clkdiv_pre=0; - reg.clk_equ_sysclk=1; - eff_clk=fapb; - } else { - //For best duty cycle resolution, we want n to be as close to 32 as possible, but - //we also need a pre/n combo that gets us as close as possible to the intended freq. - //To do this, we bruteforce n and calculate the best pre to go along with that. - //If there's a choice between pre/n combos that give the same result, use the one - //with the higher n. - int pre, n, h, l; - int bestn=-1; - int bestpre=-1; - int besterr=0; - int errval; - for (n=2; n<=64; n++) { //Start at 2: we need to be able to set h/l so we have at least one high and one low pulse. - //Effectively, this does pre=round((fapb/n)/hz). - pre=((fapb/n)+(hz/2))/hz; - if (pre<=0) pre=1; - if (pre>8192) pre=8192; - errval=abs(spi_freq_for_pre_n(fapb, pre, n)-hz); - if (bestn==-1 || errval<=besterr) { - besterr=errval; - bestn=n; - bestpre=pre; - } - } - - n=bestn; - pre=bestpre; - l=n; - //This effectively does round((duty_cycle*n)/256) - h=(duty_cycle*n+127)/256; - if (h<=0) h=1; - - reg.clk_equ_sysclk=0; - reg.clkcnt_n=n-1; - reg.clkdiv_pre=pre-1; - reg.clkcnt_h=h-1; - reg.clkcnt_l=l-1; - eff_clk=spi_freq_for_pre_n(fapb, pre, n); - } - if (reg_o != NULL) *reg_o = reg.val; - return eff_clk; + return spi_ll_master_cal_clock(fapb, hz, duty_cycle, reg_o); } -/* - * Set the spi clock according to pre-calculated register value. - */ -static inline void SPI_MASTER_ISR_ATTR spi_set_clock(spi_dev_t *hw, spi_clock_reg_t reg) +int spi_get_actual_clock(int fapb, int hz, int duty_cycle) { - hw->clock.val = reg.val; + return spi_hal_master_cal_clock(fapb, hz, duty_cycle); } // Setup the device-specified configuration registers. Called every time a new // transaction is to be sent, but only apply new configurations when the device // changes. -static void SPI_MASTER_ISR_ATTR spi_setup_device(spi_host_t *host, int dev_id ) +static void SPI_MASTER_ISR_ATTR spi_setup_device(spi_host_t *host, int dev_id) { //if the configuration is already applied, skip the following. if (dev_id == host->prev_cs) { @@ -627,41 +500,24 @@ static void SPI_MASTER_ISR_ATTR spi_setup_device(spi_host_t *host, int dev_id ) ESP_EARLY_LOGD(SPI_TAG, "SPI device changed from %d to %d", host->prev_cs, dev_id); spi_device_t *dev = atomic_load(&host->device[dev_id]); - //Configure clock settings - spi_set_clock(host->hw, dev->clk_cfg.reg); - //Configure bit order - host->hw->ctrl.rd_bit_order=(dev->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST) ? 1 : 0; - host->hw->ctrl.wr_bit_order=(dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST) ? 1 : 0; - //Configure polarity - if (dev->cfg.mode==0) { - host->hw->pin.ck_idle_edge=0; - host->hw->user.ck_out_edge=0; - } else if (dev->cfg.mode==1) { - host->hw->pin.ck_idle_edge=0; - host->hw->user.ck_out_edge=1; - } else if (dev->cfg.mode==2) { - host->hw->pin.ck_idle_edge=1; - host->hw->user.ck_out_edge=1; - } else if (dev->cfg.mode==3) { - host->hw->pin.ck_idle_edge=1; - host->hw->user.ck_out_edge=0; - } - //Configure misc stuff - host->hw->user.doutdin=(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX) ? 0 : 1; - host->hw->user.sio=(dev->cfg.flags & SPI_DEVICE_3WIRE) ? 1 : 0; - //Configure CS pin and timing - host->hw->ctrl2.setup_time=dev->cfg.cs_ena_pretrans-1; - host->hw->user.cs_setup=dev->cfg.cs_ena_pretrans ? 1 : 0; + spi_hal_context_t *hal = &host->hal; + hal->mode = dev->cfg.mode; + hal->tx_lsbfirst = dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0; + hal->rx_lsbfirst = dev->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0; + hal->no_compensate = dev->cfg.flags & SPI_DEVICE_NO_DUMMY ? 1 : 0; + hal->sio = dev->cfg.flags & SPI_DEVICE_3WIRE ? 1 : 0; + hal->dummy_bits = dev->cfg.dummy_bits; + hal->cs_setup = dev->cfg.cs_ena_pretrans; + hal->cs_hold =dev->cfg.cs_ena_posttrans; //set hold_time to 0 will not actually append delay to CS //set it to 1 since we do need at least one clock of hold time in most cases - host->hw->ctrl2.hold_time=dev->cfg.cs_ena_posttrans; - if (host->hw->ctrl2.hold_time == 0) host->hw->ctrl2.hold_time = 1; - host->hw->user.cs_hold=1; + if (hal->cs_hold == 0) hal->cs_hold = 1; + hal->cs_pin_id = dev_id; + hal->timing_conf = &dev->timing_conf; + + spi_hal_setup_device(hal); - host->hw->pin.cs0_dis = (dev_id == 0) ? 0 : 1; - host->hw->pin.cs1_dis = (dev_id == 1) ? 0 : 1; - host->hw->pin.cs2_dis = (dev_id == 2) ? 0 : 1; //Record the device just configured to save time for next time host->prev_cs = dev_id; } @@ -783,211 +639,59 @@ static inline SPI_MASTER_ISR_ATTR bool device_is_polling(spi_device_t *handle) // The function is called to send a new transaction, in ISR or in the task. // Setup the transaction-specified registers and linked-list used by the DMA (or FIFO if DMA is not used) -static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_t *trans_buf) +static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_t *trans_buf, spi_hal_context_t *hal) { spi_transaction_t *trans = NULL; spi_host_t *host = dev->host; int dev_id = dev->id; - //clear int bit - host->hw->slave.trans_done = 0; - trans = trans_buf->trans; host->cur_cs = dev_id; //We should be done with the transmission. - assert(host->hw->cmd.usr == 0); + + hal->tx_bitlen = trans->length; + hal->rx_bitlen = trans->rxlength; + hal->rcv_buffer = (uint8_t*)host->cur_trans_buf.buffer_to_rcv; + hal->send_buffer = (uint8_t*)host->cur_trans_buf.buffer_to_send; + hal->half_duplex = dev->cfg.flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0; + hal->cmd = trans->cmd; + hal->addr = trans->addr; + //Set up QIO/DIO if needed + hal->io_mode = (trans->flags & SPI_TRANS_MODE_DIO ? + (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR ? SPI_LL_IO_MODE_DIO : SPI_LL_IO_MODE_DUAL) : + (trans->flags & SPI_TRANS_MODE_QIO ? + (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR ? SPI_LL_IO_MODE_QIO : SPI_LL_IO_MODE_QUAD) : + SPI_LL_IO_MODE_NORMAL + )); + + hal->tx_bitlen = trans->length; + hal->rx_bitlen = trans->rxlength; + + if (trans->flags & SPI_TRANS_VARIABLE_CMD) { + hal->cmd_bits = ((spi_transaction_ext_t *)trans)->command_bits; + } else { + hal->cmd_bits = dev->cfg.command_bits; + } + if (trans->flags & SPI_TRANS_VARIABLE_ADDR) { + hal->addr_bits = ((spi_transaction_ext_t *)trans)->address_bits; + } else { + hal->addr_bits = dev->cfg.address_bits; + } + if (trans->flags & SPI_TRANS_VARIABLE_DUMMY) { + hal->dummy_bits = ((spi_transaction_ext_t *)trans)->dummy_bits; + } else { + hal->dummy_bits = dev->cfg.dummy_bits; + } //Reconfigure according to device settings, the function only has effect when the dev_id is changed. spi_setup_device(host, dev_id); - - //Reset DMA peripheral - 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=1; - host->hw->dma_conf.indscr_burst_en=1; - host->hw->dma_conf.outdscr_burst_en=1; - //Set up QIO/DIO if needed - host->hw->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO); - host->hw->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO); - if (trans->flags & SPI_TRANS_MODE_DIO) { - if (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR) { - host->hw->ctrl.fread_dio=1; - host->hw->user.fwrite_dio=1; - } else { - host->hw->ctrl.fread_dual=1; - host->hw->user.fwrite_dual=1; - } - host->hw->ctrl.fastrd_mode=1; - } else if (trans->flags & SPI_TRANS_MODE_QIO) { - if (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR) { - host->hw->ctrl.fread_qio=1; - host->hw->user.fwrite_qio=1; - } else { - host->hw->ctrl.fread_quad=1; - host->hw->user.fwrite_quad=1; - } - host->hw->ctrl.fastrd_mode=1; - } - - //Fill DMA descriptors - int extra_dummy=0; - if (trans_buf->buffer_to_rcv) { - if (host->dma_chan == 0) { - //No need to setup anything; we'll copy the result out of the work registers directly later. - } else { - spicommon_setup_dma_desc_links(host->dmadesc_rx, ((trans->rxlength+7)/8), (uint8_t*)trans_buf->buffer_to_rcv, true); - host->hw->dma_in_link.addr=(int)(&host->dmadesc_rx[0]) & 0xFFFFF; - host->hw->dma_in_link.start=1; - } - //when no_dummy is not set and in half-duplex mode, sets the dummy bit if RX phase exist - if (((dev->cfg.flags&SPI_DEVICE_NO_DUMMY)==0) && (dev->cfg.flags&SPI_DEVICE_HALFDUPLEX)) { - extra_dummy=dev->clk_cfg.dummy_num; - } - } else { - //DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon - if (host->dma_chan != 0 ) { - host->hw->dma_in_link.addr=0; - host->hw->dma_in_link.start=1; - } - } - - if (trans_buf->buffer_to_send) { - if (host->dma_chan == 0) { - //Need to copy data to registers manually - for (int x=0; x < trans->length; x+=32) { - //Use memcpy to get around alignment issues for txdata - uint32_t word; - memcpy(&word, &trans_buf->buffer_to_send[x/32], 4); - host->hw->data_buf[(x/32)]=word; - } - } else { - spicommon_setup_dma_desc_links(host->dmadesc_tx, (trans->length+7)/8, (uint8_t*)trans_buf->buffer_to_send, false); - host->hw->dma_out_link.addr=(int)(&host->dmadesc_tx[0]) & 0xFFFFF; - host->hw->dma_out_link.start=1; - } - } - - //SPI iface needs to be configured for a delay in some cases. - //configure dummy bits - int base_dummy_bits; - if (trans->flags & SPI_TRANS_VARIABLE_DUMMY) { - base_dummy_bits = ((spi_transaction_ext_t *)trans)->dummy_bits; - } else { - base_dummy_bits = dev->cfg.dummy_bits; - } - host->hw->user.usr_dummy=(base_dummy_bits+extra_dummy) ? 1 : 0; - host->hw->user1.usr_dummy_cyclelen=base_dummy_bits+extra_dummy-1; - - int miso_long_delay = 0; - if (dev->clk_cfg.miso_delay<0) { - //if the data comes too late, delay half a SPI clock to improve reading - miso_long_delay = 1; - host->hw->ctrl2.miso_delay_num = 0; - } else { - //if the data is so fast that dummy_bit is used, delay some apb clocks to meet the timing - host->hw->ctrl2.miso_delay_num = extra_dummy ? dev->clk_cfg.miso_delay : 0; - } - - if (miso_long_delay) { - switch (dev->cfg.mode) { - case 0: - host->hw->ctrl2.miso_delay_mode = 2; - break; - case 1: - host->hw->ctrl2.miso_delay_mode = 1; - break; - case 2: - host->hw->ctrl2.miso_delay_mode = 1; - break; - case 3: - host->hw->ctrl2.miso_delay_mode = 2; - break; - } - } else { - host->hw->ctrl2.miso_delay_mode = 0; - } - - host->hw->mosi_dlen.usr_mosi_dbitlen=trans->length-1; - if ( dev->cfg.flags & SPI_DEVICE_HALFDUPLEX ) { - host->hw->miso_dlen.usr_miso_dbitlen=trans->rxlength-1; - } else { - //rxlength is not used in full-duplex mode - host->hw->miso_dlen.usr_miso_dbitlen=trans->length-1; - } - - //Configure bit sizes, load addr and command - int cmdlen; - int addrlen; - if (!(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX) && dev->cfg.cs_ena_pretrans != 0) { - /* The command and address phase is not compatible with cs_ena_pretrans - * in full duplex mode. - */ - cmdlen = 0; - addrlen = 0; - } else { - if (trans->flags & SPI_TRANS_VARIABLE_CMD) { - cmdlen = ((spi_transaction_ext_t *)trans)->command_bits; - } else { - cmdlen = dev->cfg.command_bits; - } - if (trans->flags & SPI_TRANS_VARIABLE_ADDR) { - addrlen = ((spi_transaction_ext_t *)trans)->address_bits; - } else { - addrlen = dev->cfg.address_bits; - } - } - - host->hw->user1.usr_addr_bitlen=addrlen-1; - host->hw->user2.usr_command_bitlen=cmdlen-1; - host->hw->user.usr_addr=addrlen ? 1 : 0; - host->hw->user.usr_command=cmdlen ? 1 : 0; - - if ((dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)==0) { - /* Output command will be sent from bit 7 to 0 of command_value, and - * then bit 15 to 8 of the same register field. Shift and swap to send - * more straightly. - */ - host->hw->user2.usr_command_value = SPI_SWAP_DATA_TX(trans->cmd, cmdlen); - - // shift the address to MSB of addr (and maybe slv_wr_status) register. - // output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register. - if (addrlen > 32) { - host->hw->addr = trans->addr >> (addrlen - 32); - host->hw->slv_wr_status = trans->addr << (64 - addrlen); - } else { - host->hw->addr = trans->addr << (32 - addrlen); - } - } else { - /* The output command start from bit0 to bit 15, kept as is. - * The output address start from the LSB of the highest byte, i.e. - * addr[24] -> addr[31] - * ... - * addr[0] -> addr[7] - * slv_wr_status[24] -> slv_wr_status[31] - * ... - * slv_wr_status[0] -> slv_wr_status[7] - * So swap the byte order to let the LSB sent first. - */ - host->hw->user2.usr_command_value = trans->cmd; - uint64_t addr = __builtin_bswap64(trans->addr); - host->hw->addr = addr>>32; - host->hw->slv_wr_status = addr; - } - - if ((!(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX) && trans_buf->buffer_to_rcv) || - trans_buf->buffer_to_send) { - host->hw->user.usr_mosi = 1; - } else { - host->hw->user.usr_mosi = 0; - } - host->hw->user.usr_miso = (trans_buf->buffer_to_rcv) ? 1 : 0; + spi_hal_setup_trans(hal); + spi_hal_prepare_data(hal); //Call pre-transmission callback, if any if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans); //Kick off transfer - host->hw->cmd.usr=1; + spi_hal_user_start(hal); } // The function is called when a transaction is done, in ISR or in the task. @@ -995,16 +699,7 @@ static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_ static void SPI_MASTER_ISR_ATTR spi_post_trans(spi_host_t *host) { spi_transaction_t *cur_trans = host->cur_trans_buf.trans; - if (host->cur_trans_buf.buffer_to_rcv && host->dma_chan == 0 ) { - //Need to copy from SPI regs to result buffer. - for (int x = 0; x < cur_trans->rxlength; x += 32) { - //Do a memcpy to get around possible alignment issues in rx_buffer - uint32_t word = host->hw->data_buf[x / 32]; - int len = cur_trans->rxlength - x; - if (len > 32) len = 32; - memcpy(&host->cur_trans_buf.buffer_to_rcv[x / 32], &word, (len + 7) / 8); - } - } + spi_hal_fetch_result(&host->hal); //Call post-transaction callback, if any spi_device_t* dev = atomic_load(&host->device[host->cur_cs]); if (dev->cfg.post_cb) dev->cfg.post_cb(cur_trans); @@ -1020,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(host->hw->slave.trans_done == 1); + assert(spi_hal_usr_is_done(&host->hal) == 1); /*------------ deal with the in-flight transaction -----------------*/ if (host->cur_cs != NO_CS) { @@ -1092,7 +787,7 @@ static void SPI_MASTER_ISR_ATTR spi_intr(void *arg) //mark channel as active, so that the DMA will not be reset by the slave spicommon_dmaworkaround_transfer_active(host->dma_chan); } - spi_new_trans(atomic_load(&host->device[i]), cur_trans_buf); + spi_new_trans(atomic_load(&host->device[i]), cur_trans_buf, (&host->hal)); //re-enable interrupt disabled above esp_intr_enable(host->intr); } @@ -1186,7 +881,7 @@ static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_transaction_t *trans_de memcpy( temp, send_ptr, (trans_desc->length + 7) / 8 ); send_ptr = temp; - } + } new_desc->buffer_to_send = send_ptr; return ESP_OK; @@ -1289,7 +984,7 @@ esp_err_t SPI_MASTER_ATTR spi_device_acquire_bus(spi_device_t *device, TickType_ esp_pm_lock_acquire(device->host->pm_lock); #endif //configure the device so that we don't need to do it again in the following transactions - spi_setup_device( host, device->id ); + spi_setup_device(host, device->id); //the DMA is also occupied by the device, all the slave devices that using DMA should wait until bus released. if (host->dma_chan != 0) { spicommon_dmaworkaround_transfer_active(host->dma_chan); @@ -1347,7 +1042,7 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_start(spi_device_handle_t handl host->polling = true; ESP_LOGV(SPI_TAG, "polling trans"); - spi_new_trans(handle, &host->cur_trans_buf); + spi_new_trans(handle, &host->cur_trans_buf, (&host->hal)); return ESP_OK; } @@ -1361,12 +1056,13 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle, assert(host->cur_cs == atomic_load(&host->acquire_cs)); TickType_t start = xTaskGetTickCount(); - while (!host->hw->slave.trans_done) { + while (!spi_hal_usr_is_done(&host->hal)) { TickType_t end = xTaskGetTickCount(); if (end - start > ticks_to_wait) { return ESP_ERR_TIMEOUT; } } + ESP_LOGV(SPI_TAG, "polling trans done"); //deal with the in-flight transaction spi_post_trans(host); diff --git a/components/driver/test/test_common_spi.c b/components/driver/test/test_common_spi.c index b05bd271e..4049cc273 100644 --- a/components/driver/test/test_common_spi.c +++ b/components/driver/test/test_common_spi.c @@ -166,12 +166,10 @@ esp_err_t spitest_check_data(int len, spi_transaction_t *master_t, slave_rxdata_ TEST_ASSERT(rcv_len >= len - 1 && rcv_len <= len + 4); } - //if (dup!=HALF_DUPLEX_MOSI) { if (check_master_data) { TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_t->tx_start, master_t->rx_buffer, (len + 7) / 8); } - //if (dup!=HALF_DUPLEX_MISO) { if (check_slave_data) { TEST_ASSERT_EQUAL_HEX8_ARRAY(master_t->tx_buffer, slave_t->data, (len + 7) / 8); } diff --git a/components/driver/test/test_spi_master.c b/components/driver/test/test_spi_master.c index d529cfc74..8bccb7659 100644 --- a/components/driver/test/test_spi_master.c +++ b/components/driver/test/test_spi_master.c @@ -24,6 +24,7 @@ #include "test/test_common_spi.h" #include "soc/gpio_periph.h" #include "sdkconfig.h" +#include "../cache_utils.h" const static char TAG[] = "test_spi"; @@ -938,7 +939,8 @@ TEST_CASE("SPI master variable dummy test", "[spi]") ********************************************************************************/ #define RECORD_TIME_PREPARE() uint32_t __t1, __t2 #define RECORD_TIME_START() do {__t1 = xthal_get_ccount();}while(0) -#define RECORD_TIME_END(p_time) do{__t2 = xthal_get_ccount(); *p_time = (__t2-__t1)/240;}while(0) +#define RECORD_TIME_END(p_time) do{__t2 = xthal_get_ccount(); *p_time = (__t2-__t1);}while(0) +#define GET_US_BY_CCOUNT(t) ((t)/240.) static void speed_setup(spi_device_handle_t* spi, bool use_dma) { @@ -978,11 +980,13 @@ static IRAM_ATTR void spi_transmit_measure(spi_device_handle_t spi, spi_transact static IRAM_ATTR void spi_transmit_polling_measure(spi_device_handle_t spi, spi_transaction_t* trans, uint32_t* t_flight) { + spi_flash_disable_interrupts_caches_and_other_cpu(); //this can test the code are all in the IRAM at the same time RECORD_TIME_PREPARE(); spi_device_polling_transmit(spi, trans); // prime the flash cache RECORD_TIME_START(); spi_device_polling_transmit(spi, trans); RECORD_TIME_END(t_flight); + spi_flash_enable_interrupts_caches_and_other_cpu(); } TEST_CASE("spi_speed","[spi]") @@ -1010,9 +1014,9 @@ TEST_CASE("spi_speed","[spi]") sorted_array_insert(t_flight_sorted, &t_flight_num, t_flight); } for (int i = 0; i < TEST_TIMES; i++) { - ESP_LOGI(TAG, "%d", t_flight_sorted[i]); + ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); } - TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING, "%d us", t_flight_sorted[(TEST_TIMES+1)/2]); + TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); //acquire the bus to send polling transactions faster ret = spi_device_acquire_bus(spi, portMAX_DELAY); @@ -1025,9 +1029,9 @@ TEST_CASE("spi_speed","[spi]") sorted_array_insert(t_flight_sorted, &t_flight_num, t_flight); } for (int i = 0; i < TEST_TIMES; i++) { - ESP_LOGI(TAG, "%d", t_flight_sorted[i]); + ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); } - TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING, "%d us", t_flight_sorted[(TEST_TIMES+1)/2]); + TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); //release the bus spi_device_release_bus(spi); @@ -1043,9 +1047,9 @@ TEST_CASE("spi_speed","[spi]") sorted_array_insert(t_flight_sorted, &t_flight_num, t_flight); } for (int i = 0; i < TEST_TIMES; i++) { - ESP_LOGI(TAG, "%d", t_flight_sorted[i]); + ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); } - TEST_PERFORMANCE_LESS_THAN( SPI_PER_TRANS_NO_POLLING_NO_DMA, "%d us", t_flight_sorted[(TEST_TIMES+1)/2]); + TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_NO_POLLING_NO_DMA, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); //acquire the bus to send polling transactions faster ret = spi_device_acquire_bus(spi, portMAX_DELAY); @@ -1057,9 +1061,9 @@ TEST_CASE("spi_speed","[spi]") sorted_array_insert(t_flight_sorted, &t_flight_num, t_flight); } for (int i = 0; i < TEST_TIMES; i++) { - ESP_LOGI(TAG, "%d", t_flight_sorted[i]); + ESP_LOGI(TAG, "%.2lf", GET_US_BY_CCOUNT(t_flight_sorted[i])); } - TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING_NO_DMA, "%d us", t_flight_sorted[(TEST_TIMES+1)/2]); + TEST_PERFORMANCE_LESS_THAN(SPI_PER_TRANS_POLLING_NO_DMA, "%d us", (int)GET_US_BY_CCOUNT(t_flight_sorted[(TEST_TIMES+1)/2])); //release the bus spi_device_release_bus(spi); diff --git a/components/soc/CMakeLists.txt b/components/soc/CMakeLists.txt index ec532ad87..024af9a8c 100644 --- a/components/soc/CMakeLists.txt +++ b/components/soc/CMakeLists.txt @@ -9,7 +9,7 @@ if(EXISTS "${COMPONENT_PATH}/${SOC_NAME}") endif() list(APPEND COMPONENT_ADD_INCLUDEDIRS include) -list(APPEND COMPONENT_SRCS "src/memory_layout_utils.c") +list(APPEND COMPONENT_SRCS "src/memory_layout_utils.c src/lldesc.c src/hal/spi_hal.c src/hal/spi_hal_iram.c") set(COMPONENT_ADD_LDFRAGMENTS linker.lf) diff --git a/components/soc/component.mk b/components/soc/component.mk index e2d922532..5535db1eb 100644 --- a/components/soc/component.mk +++ b/components/soc/component.mk @@ -1,6 +1,6 @@ SOC_NAME := $(IDF_TARGET) -COMPONENT_SRCDIRS := $(SOC_NAME) src/ +COMPONENT_SRCDIRS := $(SOC_NAME) src/ src/hal COMPONENT_ADD_INCLUDEDIRS := $(SOC_NAME)/include include diff --git a/components/soc/esp32/include/soc/soc.h b/components/soc/esp32/include/soc/soc.h index 96dea8023..5cb097e43 100644 --- a/components/soc/esp32/include/soc/soc.h +++ b/components/soc/esp32/include/soc/soc.h @@ -281,6 +281,7 @@ #define TIMER_CLK_FREQ (80000000>>4) //80MHz divided by 16 #define SPI_CLK_DIV 4 #define TICKS_PER_US_ROM 26 // CPU is 80MHz +#define GPIO_MATRIX_DELAY_NS 25 //}} /* Overall memory map */ diff --git a/components/soc/include/hal/hal_defs.h b/components/soc/include/hal/hal_defs.h new file mode 100644 index 000000000..c183641db --- /dev/null +++ b/components/soc/include/hal/hal_defs.h @@ -0,0 +1,28 @@ +// Copyright 2010-2018 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. + +#pragma once + +#include "esp_log.h" + +// platform related stuff + +#define HAL_SWAP32(word) __builtin_bswap32(word) +#define HAL_SWAP64(word) __builtin_bswap64(word) + +#define HAL_LOGE(...) ESP_LOGE(__VA_ARGS__) +#define HAL_LOGW(...) ESP_LOGW(__VA_ARGS__) +#define HAL_LOGI(...) ESP_LOGI(__VA_ARGS__) +#define HAL_LOGD(...) ESP_LOGD(__VA_ARGS__) +#define HAL_LOGV(...) ESP_LOGV(__VA_ARGS__) diff --git a/components/soc/include/hal/readme.md b/components/soc/include/hal/readme.md new file mode 100644 index 000000000..e236dbbdd --- /dev/null +++ b/components/soc/include/hal/readme.md @@ -0,0 +1,23 @@ +# HAL Layer Readme + +The HAL layer is designed to be used by the drivers. We don't guarantee the stability and back-compatibility among +versions. The HAL layer may update very frequently with the driver. Please don't use them in the applications or treat +them as stable APIs. + +The HAL layer consists of two layers: HAL (upper) and Lowlevel(bottom). The HAL layer defines the steps and data +required by the peripheral. The lowlevel is a translation layer converting general conceptions to register configurations. + +## Lowlevel + +This layer should be all static inline. The first argument of LL functions is usually a pointer to the beginning address +of the peripheral register. Each chip should have its own LL layer. The functions in this layer should be atomic and +independent from each other so that the upper layer can change/perform one of the options/operation without touching the +others. + +## HAL + +This layer should depend on the operating system as little as possible. It's a wrapping of LL functions, so that the upper +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 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 new file mode 100644 index 000000000..7f6c0da3f --- /dev/null +++ b/components/soc/include/hal/spi_hal.h @@ -0,0 +1,220 @@ +// 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 (common part) + +// SPI HAL usages: +// 1. initialize the bus +// 2. initialize the DMA descriptors if DMA used +// 3. setup the clock speed (since this takes long time) +// 4. call setup_device to update parameters for the specific device +// 5. call setup_trans to update parameters for the specific transaction +// 6. prepare data to send, and prepare the receiving buffer +// 7. trigger user defined SPI transaction to start +// 8. wait until the user transaction is done +// 9. fetch the received data +// Parameter to be updated only during ``setup_device`` will be highlighted in the +// field comments. + +#pragma once +#include "spi_ll.h" +#include +#include "soc/lldesc.h" + +/** + * Timing configuration structure that should be calculated by + * ``spi_hal_setup_clock`` at initialization and hold. Filled into the + * ``timing_conf`` member of the context of HAL before setup a device. + */ +typedef struct { + spi_ll_clock_val_t clock_reg; ///< Register value used by the LL layer + int timing_dummy; ///< Extra dummy needed to compensate the timing + int timing_miso_delay; ///< Extra miso delay clocks to compensate the timing +} spi_hal_timing_conf_t; + +/** + * Context that should be maintained by both the driver and the HAL. + */ +typedef struct { + /* 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. + */ + lldesc_t *dmadesc_rx; /**< 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. + /* + * Device specific, all these parameters will be updated to the peripheral + * only when ``spi_hal_setup_device``. They may not get updated when + * ``spi_hal_setup_trans``. + */ + int mode; ///< SPI mode, device specific + int cs_setup; ///< Setup time of CS active edge before the first SPI clock, device specific + int cs_hold; ///< Hold time of CS inactive edge after the last SPI clock, device specific + int cs_pin_id; ///< CS pin to use, 0-2, otherwise all the CS pins are not used. Device specific + spi_hal_timing_conf_t *timing_conf; /**< Pointer to an structure holding + * the pre-calculated timing configuration for the device at initialization, + * device specific + */ + struct { + uint32_t sio : 1; ///< Whether to use SIO mode, device specific + uint32_t half_duplex : 1; ///< Whether half duplex mode is used, device specific + uint32_t tx_lsbfirst : 1; ///< Whether LSB is sent first for TX data, device specific + uint32_t rx_lsbfirst : 1; ///< Whether LSB is received first for RX data, device specific + uint32_t dma_enabled : 1; ///< Whether the DMA is enabled, do not update after initialization + uint32_t no_compensate : 1; ///< No need to add dummy to compensate the timing, device specific + uint32_t as_cs : 1; ///< Whether the AS_CS feature is enabled, device specific + uint32_t positive_cs : 1; ///< Whether the postive CS feature is abled, device specific + };//boolean configurations + + /* + * Transaction specific (data), all these parameters will be updated to the + * peripheral every transaction. + */ + uint16_t cmd; ///< Command value to be sent + int cmd_bits; ///< Length (in bits) of the command phase + int addr_bits; ///< Length (in bits) of the address phase + int dummy_bits; ///< Base length (in bits) of the dummy phase. Note when the compensation is enabled, some extra dummy bits may be appended. + int tx_bitlen; ///< TX length, in bits + int rx_bitlen; ///< RX length, in bits + uint64_t addr; ///< Address value to be sent + uint8_t *send_buffer; ///< Data to be sent + 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; + +/** + * 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_hal_init(spi_hal_context_t *hal, int host_id); + +/** + * Deinit the peripheral (and the context if needed). + * + * @param hal Context of the HAL layer. + */ +void spi_hal_deinit(spi_hal_context_t *hal); + +/** + * Setup device-related configurations according to the settings in the context. + * + * @param hal Context of the HAL layer. + */ +void spi_hal_setup_device(const spi_hal_context_t *hal); + +/** + * Setup transaction related configurations according to the settings in the context. + * + * @param hal Context of the HAL layer. + */ +void spi_hal_setup_trans(const spi_hal_context_t *hal); + +/** + * Prepare the data for the current transaction. + * + * @param hal Context of the HAL layer. + */ +void spi_hal_prepare_data(const spi_hal_context_t *hal); + +/** + * Trigger start a user-defined transaction. + * + * @param hal Context of the HAL layer. + */ +void spi_hal_user_start(const spi_hal_context_t *hal); + +/** + * Check whether the transaction is done (trans_done is set). + * + * @param hal Context of the HAL layer. + */ +bool spi_hal_usr_is_done(const spi_hal_context_t *hal); + +/** + * Post transaction operations, mainly fetch data from the buffer. + * + * @param hal Context of the HAL layer. + */ +void spi_hal_fetch_result(const spi_hal_context_t *hal); + +/*---------------------------------------------------------- + * Utils + * ---------------------------------------------------------*/ +/** + * Get the configuration of clock and timing. The configuration will be used when ``spi_hal_setup_device``. + * + * It is highly suggested to do this at initialization, since it takes long time. + * + * @param hal Context of the HAL layer. + * @param speed_hz Desired frequency. + * @param duty_cycle Desired duty cycle of SPI clock + * @param use_gpio true if the GPIO matrix is used, otherwise false + * @param input_delay_ns Maximum delay between SPI launch clock and the data to + * be valid. This is used to compensate/calculate the maximum frequency + * allowed. Left 0 if not known. + * @param out_freq Output of the actual frequency, left NULL if not required. + * @param timing_conf Output of the timing configuration. + * + * @return ESP_OK if desired is available, otherwise fail. + */ +esp_err_t spi_hal_get_clock_conf(const spi_hal_context_t *hal, int speed_hz, int duty_cycle, bool use_gpio, int input_delay_ns, int *out_freq, spi_hal_timing_conf_t *timing_conf); + +/** + * Get the frequency actual used. + * + * @param hal Context of the HAL layer. + * @param fapb APB clock frequency. + * @param hz Desired frequencyc. + * @param duty_cycle Desired duty cycle. + */ +int spi_hal_master_cal_clock(int fapb, int hz, int duty_cycle); + +/** + * Get the timing configuration for given parameters. + * + * @param eff_clk Actual SPI clock frequency + * @param gpio_is_used true if the GPIO matrix is used, otherwise false. + * @param input_delay_ns Maximum delay between SPI launch clock and the data to + * be valid. This is used to compensate/calculate the maximum frequency + * allowed. Left 0 if not known. + * @param dummy_n Dummy cycles required to correctly read the data. + * @param miso_delay_n suggested delay on the MISO line, in APB clocks. + */ +void spi_hal_cal_timing(int eff_clk, bool gpio_is_used, int input_delay_ns, int *dummy_n, int *miso_delay_n); + +/** + * Get the maximum frequency allowed to read if no compensation is used. + * + * @param gpio_is_used true if the GPIO matrix is used, otherwise false. + * @param input_delay_ns Maximum delay between SPI launch clock and the data to + * be valid. This is used to compensate/calculate the maximum frequency + * allowed. Left 0 if not known. + */ +int spi_hal_get_freq_limit(bool gpio_is_used, int input_delay_ns); + diff --git a/components/soc/include/hal/spi_ll.h b/components/soc/include/hal/spi_ll.h new file mode 100644 index 000000000..9b51130ff --- /dev/null +++ b/components/soc/include/hal/spi_ll.h @@ -0,0 +1,727 @@ +// 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 LL layer for SPI register operations + +#pragma once + +#include "hal_defs.h" +#include "soc/spi_periph.h" +#include "esp32/rom/lldesc.h" +#include +#include +#include //for abs() + +/// Registers to reset during initialization. Don't use in app. +#define SPI_LL_RST_MASK (SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST) +/// Interrupt not used. Don't use in app. +#define SPI_LL_UNUSED_INT_MASK (SPI_INT_EN | SPI_SLV_WR_STA_DONE | SPI_SLV_RD_STA_DONE | SPI_SLV_WR_BUF_DONE | SPI_SLV_RD_BUF_DONE) +/// Swap the bit order to its correct place to send +#define HAL_SPI_SWAP_DATA_TX(data, len) HAL_SWAP32((uint32_t)data<<(32-len)) + +/** + * The data structure holding calculated clock configuration. Since the + * calculation needs long time, it should be calculated during initialization and + * stored somewhere to be quickly used. + */ +typedef uint32_t spi_ll_clock_val_t; + +/** IO modes supported by the master. */ +typedef enum { + SPI_LL_IO_MODE_NORMAL = 0, ///< 1-bit mode for all phases + SPI_LL_IO_MODE_DIO, ///< 2-bit mode for address and data phases, 1-bit mode for command phase + SPI_LL_IO_MODE_DUAL, ///< 2-bit mode for data phases only, 1-bit mode for command and address phases + SPI_LL_IO_MODE_QIO, ///< 4-bit mode for address and data phases, 1-bit mode for command phase + SPI_LL_IO_MODE_QUAD, ///< 4-bit mode for data phases only, 1-bit mode for command and address phases +} spi_ll_io_mode_t; + +/*------------------------------------------------------------------------------ + * Control + *----------------------------------------------------------------------------*/ +/** + * Initialize SPI peripheral (master). + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_init(spi_dev_t *hw) +{ + //Reset DMA + 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; + //Reset timing + hw->ctrl2.val = 0; + + //master 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; +} + +/** + * Reset TX and RX DMAs. + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_reset_dma(spi_dev_t *hw) +{ + //Reset DMA peripheral + 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->dma_conf.out_data_burst_en = 1; + hw->dma_conf.indscr_burst_en = 1; + hw->dma_conf.outdscr_burst_en = 1; +} + +/** + * Start RX DMA. + * + * @param hw Beginning address of the peripheral registers. + * @param addr Address of the beginning DMA descriptor. + */ +static inline void spi_ll_rxdma_start(spi_dev_t *hw, lldesc_t *addr) +{ + hw->dma_in_link.addr = (int) addr & 0xFFFFF; + hw->dma_in_link.start = 1; +} + +/** + * Start TX DMA. + * + * @param hw Beginning address of the peripheral registers. + * @param addr Address of the beginning DMA descriptor. + */ +static inline void spi_ll_txdma_start(spi_dev_t *hw, lldesc_t *addr) +{ + hw->dma_out_link.addr = (int) addr & 0xFFFFF; + hw->dma_out_link.start = 1; +} + +/** + * Write to SPI buffer. + * + * @param hw Beginning address of the peripheral registers. + * @param buffer_to_send Data address to copy to the buffer. + * @param bitlen Length to copy, in bits. + */ +static inline void spi_ll_write_buffer(spi_dev_t *hw, const uint8_t *buffer_to_send, size_t bitlen) +{ + for (int x = 0; x < bitlen; x += 32) { + //Use memcpy to get around alignment issues for txdata + uint32_t word; + memcpy(&word, &buffer_to_send[x / 8], 4); + hw->data_buf[(x / 32)] = word; + } +} + +/** + * Read from SPI buffer. + * + * @param hw Beginning address of the peripheral registers. + * @param buffer_to_rcv Address to copy buffer data to. + * @param bitlen Length to copy, in bits. + */ +static inline void spi_ll_read_buffer(spi_dev_t *hw, uint8_t *buffer_to_rcv, size_t bitlen) +{ + for (int x = 0; x < bitlen; x += 32) { + //Do a memcpy to get around possible alignment issues in rx_buffer + uint32_t word = hw->data_buf[x / 32]; + int len = bitlen - x; + if (len > 32) { + len = 32; + } + memcpy(&buffer_to_rcv[x / 8], &word, (len + 7) / 8); + } +} + +/** + * Check whether user-defined transaction is done. + * + * @param hw Beginning address of the peripheral registers. + * + * @return true if transaction is done, otherwise false. + */ +static inline bool spi_ll_usr_is_done(spi_dev_t *hw) +{ + return hw->slave.trans_done; +} + +/** + * Trigger start of user-defined transaction. + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_user_start(spi_dev_t *hw) +{ + hw->cmd.usr = 1; +} + +/** + * Get current running command bit-mask. (Preview) + * + * @param hw Beginning address of the peripheral registers. + * + * @return Bitmask of running command, see ``SPI_CMD_REG``. 0 if no in-flight command. + */ +static inline uint32_t spi_ll_get_running_cmd(spi_dev_t *hw) +{ + return hw->cmd.val; +} + +/** + * Disable the trans_done interrupt. + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_disable_int(spi_dev_t *hw) +{ + hw->slave.trans_inten = 0; +} + +/** + * Clear the trans_done interrupt. + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_clear_int_stat(spi_dev_t *hw) +{ + hw->slave.trans_done = 0; +} + +/** + * Set the trans_done interrupt. + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_set_int_stat(spi_dev_t *hw) +{ + hw->slave.trans_done = 1; +} + +/** + * Enable the trans_done interrupt. + * + * @param hw Beginning address of the peripheral registers. + */ +static inline void spi_ll_enable_int(spi_dev_t *hw) +{ + hw->slave.trans_inten = 1; +} + + +/*------------------------------------------------------------------------------ + * Configs: mode + *----------------------------------------------------------------------------*/ +/** + * Enable/disable the postive-cs feature. + * + * @param hw Beginning address of the peripheral registers. + * @param cs One of the CS (0-2) to enable/disable the feature. + * @param pos_cs true to enable the feature, otherwise disable (default). + */ +static inline void spi_ll_master_set_pos_cs(spi_dev_t *hw, int cs, uint32_t pos_cs) +{ + if (pos_cs) { + hw->pin.master_cs_pol |= (1 << cs); + } else { + hw->pin.master_cs_pol &= (1 << cs); + } +} + +/** + * Enable/disable the LSBFIRST feature for TX data. + * + * @param hw Beginning address of the peripheral registers. + * @param lsbfirst true if LSB of TX data to be sent first, otherwise MSB is sent first (default). + */ +static inline void spi_ll_set_tx_lsbfirst(spi_dev_t *hw, bool lsbfirst) +{ + hw->ctrl.wr_bit_order = lsbfirst; +} + +/** + * Enable/disable the LSBFIRST feature for RX data. + * + * @param hw Beginning address of the peripheral registers. + * @param lsbfirst true if first bit received as LSB, otherwise as MSB (default). + */ +static inline void spi_ll_set_rx_lsbfirst(spi_dev_t *hw, bool lsbfirst) +{ + hw->ctrl.rd_bit_order = lsbfirst; +} + +/** + * Set SPI mode for the peripheral as master. + * + * @param hw Beginning address of the peripheral registers. + * @param mode SPI mode to work at, 0-3. + */ +static inline void spi_ll_master_set_mode(spi_dev_t *hw, uint8_t mode) +{ + //Configure polarity + if (mode == 0) { + hw->pin.ck_idle_edge = 0; + hw->user.ck_out_edge = 0; + } else if (mode == 1) { + hw->pin.ck_idle_edge = 0; + hw->user.ck_out_edge = 1; + } else if (mode == 2) { + hw->pin.ck_idle_edge = 1; + hw->user.ck_out_edge = 1; + } else if (mode == 3) { + hw->pin.ck_idle_edge = 1; + hw->user.ck_out_edge = 0; + } +} + +/** + * Set SPI to work in full duplex or half duplex mode. + * + * @param hw Beginning address of the peripheral registers. + * @param half_duplex true to work in half duplex mode, otherwise in full duplex mode. + */ +static inline void spi_ll_set_half_duplex(spi_dev_t *hw, bool half_duplex) +{ + hw->user.doutdin = !half_duplex; +} + +/** + * Set SPI to work in SIO mode or not. + * + * SIO is a mode which MOSI and MISO share a line. The device MUST work in half-duplexmode. + * + * @param hw Beginning address of the peripheral registers. + * @param sio_mode true to work in SIO mode, otherwise false. + */ +static inline void spi_ll_set_sio_mode(spi_dev_t *hw, int sio_mode) +{ + hw->user.sio = sio_mode; +} + +/** + * Configure the io mode for the master to work at. + * + * @param hw Beginning address of the peripheral registers. + * @param io_mode IO mode to work at, see ``spi_ll_io_mode_t``. + */ +static inline void spi_ll_master_set_io_mode(spi_dev_t *hw, spi_ll_io_mode_t io_mode) +{ + hw->ctrl.val &= ~(SPI_FREAD_DUAL | SPI_FREAD_QUAD | SPI_FREAD_DIO | SPI_FREAD_QIO); + hw->user.val &= ~(SPI_FWRITE_DUAL | SPI_FWRITE_QUAD | SPI_FWRITE_DIO | SPI_FWRITE_QIO); + switch (io_mode) { + case SPI_LL_IO_MODE_DIO: + hw->ctrl.fread_dio = 1; + hw->user.fwrite_dio = 1; + break; + case SPI_LL_IO_MODE_DUAL: + hw->ctrl.fread_dual = 1; + hw->user.fwrite_dual = 1; + break; + case SPI_LL_IO_MODE_QIO: + hw->ctrl.fread_qio = 1; + hw->user.fwrite_qio = 1; + break; + case SPI_LL_IO_MODE_QUAD: + hw->ctrl.fread_quad = 1; + hw->user.fwrite_quad = 1; + break; + default: + break; + }; + if (io_mode != SPI_LL_IO_MODE_NORMAL) { + hw->ctrl.fastrd_mode = 1; + } +} + +/** + * Select one of the CS to use in current transaction. + * + * @param hw Beginning address of the peripheral registers. + * @param cs_id The cs to use, 0-2, otherwise none of them is used. + */ +static inline void spi_ll_master_select_cs(spi_dev_t *hw, int cs_id) +{ + hw->pin.cs0_dis = (cs_id == 0) ? 0 : 1; + hw->pin.cs1_dis = (cs_id == 1) ? 0 : 1; + hw->pin.cs2_dis = (cs_id == 2) ? 0 : 1; +} + +/*------------------------------------------------------------------------------ + * Configs: parameters + *----------------------------------------------------------------------------*/ +/** + * Set the clock for master by stored value. + * + * @param hw Beginning address of the peripheral registers. + * @param val stored clock configuration calculated before (by ``spi_ll_cal_clock``). + */ +static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, spi_ll_clock_val_t *val) +{ + hw->clock.val = *(uint32_t *)val; +} + +/** + * Get the frequency of given dividers. Don't use in app. + * + * @param fapb APB clock of the system. + * @param pre Pre devider. + * @param n main divider. + * + * @return Frequency of given dividers. + */ +static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) +{ + return (fapb / (pre * n)); +} + +/** + * Calculate the nearest frequency avaliable for master. + * + * @param fapb APB clock of the system. + * @param hz Frequncy desired. + * @param duty_cycle Duty cycle desired. + * @param out_reg Output address to store the calculated clock configurations for the return frequency. + * + * @return Actual (nearest) frequency. + */ +static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) +{ + typeof(SPI1.clock) reg; + int eff_clk; + + //In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value. + if (hz > ((fapb / 4) * 3)) { + //Using Fapb directly will give us the best result here. + reg.clkcnt_l = 0; + reg.clkcnt_h = 0; + reg.clkcnt_n = 0; + reg.clkdiv_pre = 0; + reg.clk_equ_sysclk = 1; + eff_clk = fapb; + } else { + //For best duty cycle resolution, we want n to be as close to 32 as possible, but + //we also need a pre/n combo that gets us as close as possible to the intended freq. + //To do this, we bruteforce n and calculate the best pre to go along with that. + //If there's a choice between pre/n combos that give the same result, use the one + //with the higher n. + int pre, n, h, l; + int bestn = -1; + int bestpre = -1; + int besterr = 0; + int errval; + for (n = 2; n <= 64; n++) { //Start at 2: we need to be able to set h/l so we have at least one high and one low pulse. + //Effectively, this does pre=round((fapb/n)/hz). + pre = ((fapb / n) + (hz / 2)) / hz; + if (pre <= 0) { + pre = 1; + } + if (pre > 8192) { + pre = 8192; + } + errval = abs(spi_ll_freq_for_pre_n(fapb, pre, n) - hz); + if (bestn == -1 || errval <= besterr) { + besterr = errval; + bestn = n; + bestpre = pre; + } + } + + n = bestn; + pre = bestpre; + l = n; + //This effectively does round((duty_cycle*n)/256) + h = (duty_cycle * n + 127) / 256; + if (h <= 0) { + h = 1; + } + + reg.clk_equ_sysclk = 0; + reg.clkcnt_n = n - 1; + reg.clkdiv_pre = pre - 1; + reg.clkcnt_h = h - 1; + reg.clkcnt_l = l - 1; + eff_clk = spi_ll_freq_for_pre_n(fapb, pre, n); + } + if (out_reg != NULL) { + *(uint32_t *)out_reg = reg.val; + } + return eff_clk; +} + +/** + * Calculate and set clock for SPI master according to desired parameters. + * + * This takes long, suggest to calculate the configuration during + * initialization by ``spi_ll_master_cal_clock`` and store the result, then + * configure the clock by stored value when used by + * ``spi_ll_msater_set_clock_by_reg``. + * + * @param hw Beginning address of the peripheral registers. + * @param fapb APB clock of the system. + * @param hz Frequncy desired. + * @param duty_cycle Duty cycle desired. + * + * @return Actual frequency that is used. + */ +static inline int spi_ll_master_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) +{ + spi_ll_clock_val_t reg_val; + int freq = spi_ll_master_cal_clock(fapb, hz, duty_cycle, ®_val); + spi_ll_master_set_clock_by_reg(hw, ®_val); + return freq; +} + +/** + * Enable/disable the CK sel feature for a CS pin. + * + * CK sel is a feature to toggle the CS line along with the clock. + * + * @param hw Beginning address of the peripheral registers. + * @param cs CS pin to enable/disable the feature, 0-2. + * @param cksel true to enable the feature, otherwise false. + */ +static inline void spi_ll_master_set_cksel(spi_dev_t *hw, int cs, uint32_t cksel) +{ + if (cksel) { + hw->pin.master_ck_sel |= (1 << cs); + } else { + hw->pin.master_ck_sel &= (1 << cs); + } +} + +/** + * Set the mosi delay after the output edge to the signal. (Preview) + * + * The delay mode/num is a Espressif conception, may change in the new chips. + * + * @param hw Beginning address of the peripheral registers. + * @param delay_mode Delay mode, see TRM. + * @param delay_num APB clocks to delay. + */ +static inline void spi_ll_set_mosi_delay(spi_dev_t *hw, int delay_mode, int delay_num) +{ + hw->ctrl2.mosi_delay_mode = delay_mode; + hw->ctrl2.mosi_delay_num = delay_num; +} + +/** + * Set the miso delay applied to the input signal before the internal peripheral. (Preview) + * + * The delay mode/num is a Espressif conception, may change in the new chips. + * + * @param hw Beginning address of the peripheral registers. + * @param delay_mode Delay mode, see TRM. + * @param delay_num APB clocks to delay. + */ +static inline void spi_ll_set_miso_delay(spi_dev_t *hw, int delay_mode, int delay_num) +{ + hw->ctrl2.miso_delay_mode = delay_mode; + hw->ctrl2.miso_delay_num = delay_num; +} + +/** + * Set dummy clocks to output before RX phase (master), or clocks to skip + * before the data phase and after the address phase (slave). + * + * Note this phase is also used to compensate RX timing in half duplex mode. + * + * @param hw Beginning address of the peripheral registers. + * @param dummy_n Dummy cycles used. 0 to disable the dummy phase. + */ +static inline void spi_ll_set_dummy(spi_dev_t *hw, int dummy_n) +{ + hw->user.usr_dummy = dummy_n ? 1 : 0; + hw->user1.usr_dummy_cyclelen = dummy_n - 1; +} + +/** + * Set the delay of SPI clocks before the CS inactive edge after the last SPI clock. + * + * @param hw Beginning address of the peripheral registers. + * @param hold Delay of SPI clocks after the last clock, 0 to disable the hold phase. + */ +static inline void spi_ll_master_set_cs_hold(spi_dev_t *hw, int hold) +{ + hw->ctrl2.hold_time = hold; + hw->user.cs_hold = hold ? 1 : 0; +} + +/** + * Set the delay of SPI clocks before the first SPI clock after the CS active edge. + * + * Note ESP32 doesn't support to use this feature when command/address phases + * are used in full duplex mode. + * + * @param hw Beginning address of the peripheral registers. + * @param setup Delay of SPI clocks after the CS active edge, 0 to disable the setup phase. + */ +static inline void spi_ll_master_set_cs_setup(spi_dev_t *hw, uint8_t setup) +{ + hw->ctrl2.setup_time = setup - 1; + hw->user.cs_setup = setup ? 1 : 0; +} + +/*------------------------------------------------------------------------------ + * Configs: data + *----------------------------------------------------------------------------*/ +/** + * Set the input length. + * + * @param hw Beginning address of the peripheral registers. + * @param bitlen input length, in bits. + */ +static inline void spi_ll_set_miso_bitlen(spi_dev_t *hw, size_t bitlen) +{ + hw->miso_dlen.usr_miso_dbitlen = bitlen - 1; +} + +/** + * Set the output length. + * + * @param hw Beginning address of the peripheral registers. + * @param bitlen output length, in bits. + */ +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 length of command phase. + * + * When in 4-bit mode, the SPI cycles of the phase will be shorter. E.g. 16-bit + * command phases takes 4 cycles in 4-bit mode. + * + * @param hw Beginning address of the peripheral registers. + * @param bitlen Length of command phase, in bits. 0 to disable the command phase. + */ +static inline void spi_ll_set_command_bitlen(spi_dev_t *hw, int bitlen) +{ + hw->user2.usr_command_bitlen = bitlen - 1; + hw->user.usr_command = bitlen ? 1 : 0; +} + +/** + * Set the length of address phase. + * + * When in 4-bit mode, the SPI cycles of the phase will be shorter. E.g. 16-bit + * address phases takes 4 cycles in 4-bit mode. + * + * @param hw Beginning address of the peripheral registers. + * @param bitlen Length of address phase, in bits. 0 to disable the address phase. + */ +static inline void spi_ll_set_addr_bitlen(spi_dev_t *hw, int bitlen) +{ + hw->user1.usr_addr_bitlen = bitlen - 1; + hw->user.usr_addr = bitlen ? 1 : 0; +} + +/** + * Set the address value in an intuitive way. + * + * The length and lsbfirst is required to shift and swap the address to the right place. + * + * @param hw Beginning address of the peripheral registers. + * @param address Address to set + * @param addrlen Length of the address phase + * @param lsbfirst whether the LSB first feature is enabled. + */ +static inline void spi_ll_set_address(spi_dev_t *hw, uint64_t addr, int addrlen, uint32_t lsbfirst) +{ + if (lsbfirst) { + /* The output address start from the LSB of the highest byte, i.e. + * addr[24] -> addr[31] + * ... + * addr[0] -> addr[7] + * slv_wr_status[24] -> slv_wr_status[31] + * ... + * slv_wr_status[0] -> slv_wr_status[7] + * So swap the byte order to let the LSB sent first. + */ + addr = HAL_SWAP64(addr); + hw->addr = addr >> 32; + hw->slv_wr_status = addr; + } else { + // shift the address to MSB of addr (and maybe slv_wr_status) register. + // output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register. + if (addrlen > 32) { + hw->addr = addr >> (addrlen - 32); + hw->slv_wr_status = addr << (64 - addrlen); + } else { + hw->addr = addr << (32 - addrlen); + } + } +} + +/** + * Set the command value in an intuitive way. + * + * The length and lsbfirst is required to shift and swap the command to the right place. + * + * @param hw Beginning command of the peripheral registers. + * @param command Command to set + * @param addrlen Length of the command phase + * @param lsbfirst whether the LSB first feature is enabled. + */ +static inline void spi_ll_set_command(spi_dev_t *hw, uint16_t cmd, int cmdlen, bool lsbfirst) +{ + if (lsbfirst) { + // The output command start from bit0 to bit 15, kept as is. + hw->user2.usr_command_value = cmd; + } else { + /* Output command will be sent from bit 7 to 0 of command_value, and + * then bit 15 to 8 of the same register field. Shift and swap to send + * more straightly. + */ + hw->user2.usr_command_value = HAL_SPI_SWAP_DATA_TX(cmd, cmdlen); + + } +} + +/** + * Enable/disable the RX data phase. + * + * @param hw Beginning address of the peripheral registers. + * @param enable true if RX phase exist, otherwise false. + */ +static inline void spi_ll_enable_miso(spi_dev_t *hw, int enable) +{ + hw->user.usr_miso = enable; +} + +/** + * Enable/disable the TX data phase. + * + * @param hw Beginning address of the peripheral registers. + * @param enable true if TX phase exist, otherwise false. + */ +static inline void spi_ll_enable_mosi(spi_dev_t *hw, int enable) +{ + hw->user.usr_mosi = enable; +} + + +#undef SPI_LL_RST_MASK +#undef SPI_LL_UNUSED_INT_MASK diff --git a/components/soc/include/soc/lldesc.h b/components/soc/include/soc/lldesc.h new file mode 100644 index 000000000..410e98978 --- /dev/null +++ b/components/soc/include/soc/lldesc.h @@ -0,0 +1,48 @@ +// Copyright 2010-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. + +#pragma once + +#include +#include "esp32/rom/lldesc.h" + +//the size field has 12 bits, but 0 not for 4096. +//to avoid possible problem when the size is not word-aligned, we only use 4096-4 per desc. +/** Maximum size of data in the buffer that a DMA descriptor can hold. */ +#define LLDESC_MAX_NUM_PER_DESC (4096-4) + +/** + * Generate a linked list pointing to a (huge) buffer in an descriptor array. + * + * The caller should ensure there is enough size to hold the array, by calling + * ``lldesc_get_required_num``. + * + * @param out_desc_array Output of a descriptor array, the head should be fed to the DMA. + * @param buffer Buffer for the descriptors to point to. + * @param size Size (or length for TX) of the buffer + * @param isrx The RX DMA may require the buffer to be word-aligned, set to true for a RX link, otherwise false. + */ +void lldesc_setup_link(lldesc_t *out_desc_array, const void *buffer, int size, bool isrx); + +/** + * Get the number of descriptors required for a given buffer size. + * + * @param data_size Size to check descriptor num. + * + * @return Numbers required. + */ +static inline int lldesc_get_required_num(int data_size) +{ + return (data_size + LLDESC_MAX_NUM_PER_DESC - 1) / LLDESC_MAX_NUM_PER_DESC; +} \ No newline at end of file diff --git a/components/soc/linker.lf b/components/soc/linker.lf index f9526f1c6..a38aac878 100644 --- a/components/soc/linker.lf +++ b/components/soc/linker.lf @@ -1,6 +1,6 @@ [mapping] archive: libsoc.a -entries: +entries: cpu_util (noflash_text) rtc_clk (noflash) rtc_clk_init (noflash_text) @@ -9,4 +9,6 @@ entries: rtc_pm (noflash_text) rtc_sleep (noflash_text) rtc_time (noflash_text) - rtc_wdt (noflash_text) \ No newline at end of file + rtc_wdt (noflash_text) + spi_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 new file mode 100644 index 000000000..364a4b3eb --- /dev/null +++ b/components/soc/src/hal/spi_hal.c @@ -0,0 +1,127 @@ +// 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. + +// The HAL layer for SPI (common part) + +#include "hal/spi_hal.h" + + +static const char SPI_HAL_TAG[] = "spi_hal"; +#define SPI_HAL_CHECK(a, str, ret_val, ...) \ + if (!(a)) { \ + HAL_LOGE(SPI_HAL_TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + return (ret_val); \ + } + +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); + //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(hw); + spi_ll_set_int_stat(hw); + spi_ll_set_mosi_delay(hw, 0, 0); +} + +void spi_hal_deinit(spi_hal_context_t *hal) +{ + spi_dev_t *hw = hal->hw; + if (hw) { + spi_ll_disable_int(hw); + spi_ll_clear_int_stat(hw); + } +} + +esp_err_t spi_hal_get_clock_conf(const spi_hal_context_t *hal, int speed_hz, int duty_cycle, bool use_gpio, int input_delay_ns, int *out_freq, spi_hal_timing_conf_t *timing_conf) +{ + spi_hal_timing_conf_t temp_conf; + + int eff_clk_n = spi_ll_master_cal_clock(APB_CLK_FREQ, speed_hz, duty_cycle, &temp_conf.clock_reg); + + //When the speed is too fast, we may need to use dummy cycles to compensate the reading. + //But these don't work for full-duplex connections. + spi_hal_cal_timing(eff_clk_n, use_gpio, input_delay_ns, &temp_conf.timing_dummy, &temp_conf.timing_miso_delay); + + const int freq_limit = spi_hal_get_freq_limit(use_gpio, input_delay_ns); + + SPI_HAL_CHECK(hal->half_duplex || temp_conf.timing_dummy == 0 || hal->no_compensate, + "When work in full-duplex mode at frequency > %.1fMHz, device cannot read correct data.\n\ +Try to use IOMUX pins to increase the frequency limit, or use the half duplex mode.\n\ +Please note the SPI master can only work at divisors of 80MHz, and the driver always tries to find the closest frequency to your configuration.\n\ +Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output data at higher speed, or read data at your own risk.", + ESP_ERR_NOT_SUPPORTED, freq_limit / 1000. / 1000 ); + + if (timing_conf) { + *timing_conf = temp_conf; + } + if (out_freq) { + *out_freq = eff_clk_n; + } + return ESP_OK; +} + +int spi_hal_master_cal_clock(int fapb, int hz, int duty_cycle) +{ + return spi_ll_master_cal_clock(fapb, hz, duty_cycle, NULL); +} + +void spi_hal_cal_timing(int eff_clk, bool gpio_is_used, int input_delay_ns, int *dummy_n, int *miso_delay_n) +{ + const int apbclk_kHz = APB_CLK_FREQ / 1000; + //calculate how many apb clocks a period has + const int apbclk_n = APB_CLK_FREQ / eff_clk; + const int gpio_delay_ns = gpio_is_used ? GPIO_MATRIX_DELAY_NS : 0; + + //calculate how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. + int apb_period_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000; + if (apb_period_n < 0) { + apb_period_n = 0; + } + + int dummy_required = apb_period_n / apbclk_n; + + int miso_delay = 0; + if (dummy_required > 0) { + //due to the clock delay between master and slave, there's a range in which data is random + //give MISO a delay if needed to make sure we sample at the time MISO is stable + miso_delay = (dummy_required + 1) * apbclk_n - apb_period_n - 1; + } else { + //if the dummy is not required, maybe we should also delay half a SPI clock if the data comes too early + if (apb_period_n * 4 <= apbclk_n) { + miso_delay = -1; + } + } + *dummy_n = dummy_required; + *miso_delay_n = miso_delay; + HAL_LOGD(SPI_HAL_TAG, "eff: %d, limit: %dk(/%d), %d dummy, %d delay", eff_clk / 1000, apbclk_kHz / (apb_period_n + 1), apb_period_n, dummy_required, miso_delay); +} + +int spi_hal_get_freq_limit(bool gpio_is_used, int input_delay_ns) +{ + const int apbclk_kHz = APB_CLK_FREQ / 1000; + const int gpio_delay_ns = gpio_is_used ? GPIO_MATRIX_DELAY_NS : 0; + + //calculate how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. + int apb_period_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000; + if (apb_period_n < 0) { + apb_period_n = 0; + } + + return APB_CLK_FREQ / (apb_period_n + 1); +} \ No newline at end of file diff --git a/components/soc/src/hal/spi_hal_iram.c b/components/soc/src/hal/spi_hal_iram.c new file mode 100644 index 000000000..00bd082bf --- /dev/null +++ b/components/soc/src/hal/spi_hal_iram.c @@ -0,0 +1,166 @@ +// 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. + +// The HAL layer for SPI (common part, in iram) +// make these functions in a seperate file to make sure all LL functions are in the IRAM. + +#include "hal/spi_hal.h" + +void spi_hal_setup_device(const spi_hal_context_t *hal) +{ + //Configure clock settings + spi_dev_t *hw = hal->hw; + spi_ll_master_set_cksel(hw, hal->cs_pin_id, hal->as_cs); + spi_ll_master_set_pos_cs(hw, hal->cs_pin_id, hal->positive_cs); + spi_ll_master_set_clock_by_reg(hw, &hal->timing_conf->clock_reg); + //Configure bit order + spi_ll_set_rx_lsbfirst(hw, hal->rx_lsbfirst); + spi_ll_set_tx_lsbfirst(hw, hal->tx_lsbfirst); + spi_ll_master_set_mode(hw, hal->mode); + //Configure misc stuff + spi_ll_set_half_duplex(hw, hal->half_duplex); + spi_ll_set_sio_mode(hw, hal->sio); + //Configure CS pin and timing + spi_ll_master_set_cs_setup(hw, hal->cs_setup); + spi_ll_master_set_cs_hold(hw, hal->cs_hold); + spi_ll_master_select_cs(hw, hal->cs_pin_id); +} + +void spi_hal_setup_trans(const spi_hal_context_t *hal) +{ + spi_dev_t *hw = hal->hw; + + spi_ll_clear_int_stat(hal->hw); + assert(spi_ll_get_running_cmd(hw) == 0); + + //clear int bit + spi_ll_master_set_io_mode(hw, SPI_LL_IO_MODE_NORMAL); + + int extra_dummy = 0; + //when no_dummy is not set and in half-duplex mode, sets the dummy bit if RX phase exist + if (hal->rcv_buffer && !hal->no_compensate && hal->half_duplex) { + extra_dummy = hal->timing_conf->timing_dummy; + } + + //SPI iface needs to be configured for a delay in some cases. + //configure dummy bits + spi_ll_set_dummy(hw, extra_dummy + hal->dummy_bits); + + uint32_t miso_delay_num = 0; + uint32_t miso_delay_mode = 0; + if (hal->timing_conf->timing_miso_delay < 0) { + //if the data comes too late, delay half a SPI clock to improve reading + switch (hal->mode) { + case 0: + miso_delay_mode = 2; + break; + case 1: + miso_delay_mode = 1; + break; + case 2: + miso_delay_mode = 1; + break; + case 3: + miso_delay_mode = 2; + break; + } + miso_delay_num = 0; + } else { + //if the data is so fast that dummy_bit is used, delay some apb clocks to meet the timing + miso_delay_num = extra_dummy ? hal->timing_conf->timing_miso_delay : 0; + miso_delay_mode = 0; + } + spi_ll_set_miso_delay(hw, miso_delay_mode, miso_delay_num); + + spi_ll_set_mosi_bitlen(hw, hal->tx_bitlen); + + if (hal->half_duplex) { + spi_ll_set_miso_bitlen(hw, hal->rx_bitlen); + } else { + //rxlength is not used in full-duplex mode + spi_ll_set_miso_bitlen(hw, hal->tx_bitlen); + } + + //Configure bit sizes, load addr and command + int cmdlen = hal->cmd_bits; + int addrlen = hal->addr_bits; + if (!hal->half_duplex && hal->cs_setup != 0) { + /* The command and address phase is not compatible with cs_ena_pretrans + * in full duplex mode. + */ + cmdlen = 0; + addrlen = 0; + } + + spi_ll_set_addr_bitlen(hw, addrlen); + spi_ll_set_command_bitlen(hw, cmdlen); + + spi_ll_set_command(hw, hal->cmd, cmdlen, hal->tx_lsbfirst); + spi_ll_set_address(hw, hal->addr, addrlen, hal->tx_lsbfirst); +} + +void spi_hal_prepare_data(const spi_hal_context_t *hal) +{ + spi_dev_t *hw = hal->hw; + spi_ll_reset_dma(hw); + //Fill DMA descriptors + if (hal->rcv_buffer) { + if (!hal->dma_enabled) { + //No need to setup anything; we'll copy the result out of the work registers directly later. + } else { + lldesc_setup_link(hal->dmadesc_rx, hal->rcv_buffer, ((hal->rx_bitlen + 7) / 8), true); + spi_ll_rxdma_start(hw, hal->dmadesc_rx); + } + } else { + //DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon + if (hal->dma_enabled) { + spi_ll_rxdma_start(hw, 0); + } + } + + if (hal->send_buffer) { + if (!hal->dma_enabled) { + //Need to copy data to registers manually + spi_ll_write_buffer(hw, hal->send_buffer, hal->tx_bitlen); + } else { + lldesc_setup_link(hal->dmadesc_tx, hal->send_buffer, (hal->tx_bitlen + 7) / 8, false); + spi_ll_txdma_start(hw, hal->dmadesc_tx); + } + } + //in ESP32 these registers should be configured after the DMA is set + if ((!hal->half_duplex && hal->rcv_buffer) || hal->send_buffer) { + spi_ll_enable_mosi(hw, 1); + } else { + spi_ll_enable_mosi(hw, 0); + } + spi_ll_enable_miso(hw, (hal->rcv_buffer) ? 1 : 0); +} + +void spi_hal_user_start(const spi_hal_context_t *hal) +{ + spi_ll_user_start(hal->hw); +} + +bool spi_hal_usr_is_done(const spi_hal_context_t *hal) +{ + return spi_ll_usr_is_done(hal->hw); +} + +void spi_hal_fetch_result(const spi_hal_context_t *hal) +{ + if (hal->rcv_buffer && !hal->dma_enabled) { + //Need to copy from SPI regs to result buffer. + spi_ll_read_buffer(hal->hw, hal->rcv_buffer, hal->rx_bitlen); + } +} diff --git a/components/soc/src/lldesc.c b/components/soc/src/lldesc.c new file mode 100644 index 000000000..046c6e2aa --- /dev/null +++ b/components/soc/src/lldesc.c @@ -0,0 +1,30 @@ +#include "soc/lldesc.h" + +void lldesc_setup_link(lldesc_t *dmadesc, const void *data, int len, bool isrx) +{ + int n = 0; + while (len) { + int dmachunklen = len; + if (dmachunklen > LLDESC_MAX_NUM_PER_DESC) { + dmachunklen = LLDESC_MAX_NUM_PER_DESC; + } + if (isrx) { + //Receive needs DMA length rounded to next 32-bit boundary + dmadesc[n].size = (dmachunklen + 3) & (~3); + dmadesc[n].length = (dmachunklen + 3) & (~3); + } else { + dmadesc[n].size = dmachunklen; + dmadesc[n].length = dmachunklen; + } + dmadesc[n].buf = (uint8_t *)data; + dmadesc[n].eof = 0; + dmadesc[n].sosf = 0; + dmadesc[n].owner = 1; + dmadesc[n].qe.stqe_next = &dmadesc[n + 1]; + len -= dmachunklen; + data += dmachunklen; + n++; + } + dmadesc[n - 1].eof = 1; //Mark last DMA desc as end of stream. + dmadesc[n - 1].qe.stqe_next = NULL; +}