Merge branch 'feat/spi_hal_support' into 'master'
spi_master: refactor and add HAL support See merge request idf/esp-idf!4159
This commit is contained in:
commit
f871cc5ffa
17 changed files with 1525 additions and 462 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<<freecs);
|
||||
} else {
|
||||
spihost[host]->hw->pin.master_ck_sel &= (1<<freecs);
|
||||
}
|
||||
if (dev_config->flags&SPI_DEVICE_POSITIVE_CS) {
|
||||
spihost[host]->hw->pin.master_cs_pol |= (1<<freecs);
|
||||
} else {
|
||||
spihost[host]->hw->pin.master_cs_pol &= (1<<freecs);
|
||||
}
|
||||
spihost[host]->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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
28
components/soc/include/hal/hal_defs.h
Normal file
28
components/soc/include/hal/hal_defs.h
Normal file
|
@ -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__)
|
23
components/soc/include/hal/readme.md
Normal file
23
components/soc/include/hal/readme.md
Normal file
|
@ -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.
|
220
components/soc/include/hal/spi_hal.h
Normal file
220
components/soc/include/hal/spi_hal.h
Normal file
|
@ -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 <esp_err.h>
|
||||
#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);
|
||||
|
727
components/soc/include/hal/spi_ll.h
Normal file
727
components/soc/include/hal/spi_ll.h
Normal file
|
@ -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 <string.h>
|
||||
#include <esp_types.h>
|
||||
#include <stdlib.h> //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
|
48
components/soc/include/soc/lldesc.h
Normal file
48
components/soc/include/soc/lldesc.h
Normal file
|
@ -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 <stdbool.h>
|
||||
#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;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[mapping:soc]
|
||||
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)
|
||||
rtc_wdt (noflash_text)
|
||||
spi_hal_iram (noflash_text)
|
||||
lldesc (noflash_text)
|
127
components/soc/src/hal/spi_hal.c
Normal file
127
components/soc/src/hal/spi_hal.c
Normal file
|
@ -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);
|
||||
}
|
166
components/soc/src/hal/spi_hal_iram.c
Normal file
166
components/soc/src/hal/spi_hal_iram.c
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
30
components/soc/src/lldesc.c
Normal file
30
components/soc/src/lldesc.c
Normal file
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue