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:
Angus Gratton 2019-04-15 07:57:11 +08:00
commit f871cc5ffa
17 changed files with 1525 additions and 462 deletions

View file

@ -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.

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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)

View file

@ -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

View file

@ -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 */

View 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__)

View 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.

View 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);

View 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, &reg_val);
spi_ll_master_set_clock_by_reg(hw, &reg_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

View 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;
}

View file

@ -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)

View 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);
}

View 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);
}
}

View 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;
}