spi_master: refactor hal context structures

This commit seperates the hal context into different configuration
structures based on their members' definitions. Through refactoring
spi_master.c, the device related configuration should be passed in and
set each time before a new transaction. The transaction related
configuration now is a local variable in case of the fact that error
occurs without any notice when user forgets to pass new transaction
configuration in (which means the old driver will use the trans_config
that is saved from last transaction).

Besides, via above refactor, this commit fixs a bug which leads to
wrong cs polarity setting.
Closes https://github.com/espressif/esp-idf/pull/5490

Moreover, via above refactor, this commit also fixs a bug about duplex
mode switching when multiple devices are added to the bus.
Closes https://github.com/espressif/esp-idf/issues/4641
This commit is contained in:
Armando 2020-09-09 10:21:49 +08:00
parent 0fe231d2b3
commit 27a6f2666a
9 changed files with 323 additions and 266 deletions

View File

@ -164,9 +164,8 @@ struct spi_device_t {
QueueHandle_t trans_queue;
QueueHandle_t ret_queue;
spi_device_interface_config_t cfg;
spi_hal_timing_conf_t timing_conf;
spi_hal_dev_config_t hal_dev;
spi_host_t *host;
spi_bus_lock_dev_handle_t dev_lock;
};
@ -230,11 +229,18 @@ static esp_err_t spi_master_init_driver(spi_host_device_t host_id)
}
}
spi_hal_init(&host->hal, host_id);
//assign the SPI, RX DMA and TX DMA peripheral registers beginning address
spi_hal_dma_config_t hal_dma_config = {
//On ESP32-S2 and earlier chips, DMA registers are part of SPI registers. Pass the registers of SPI peripheral to control it.
.dma_in = SPI_LL_GET_HW(host_id),
.dma_out = SPI_LL_GET_HW(host_id),
.dmadesc_tx = bus_attr->dmadesc_tx,
.dmadesc_rx = bus_attr->dmadesc_rx,
.dmadesc_n = bus_attr->dma_desc_num
};
spi_hal_init(&host->hal, host_id, &hal_dma_config);
host->hal.dma_enabled = (bus_attr->dma_chan != 0);
host->hal.dmadesc_tx = bus_attr->dmadesc_tx;
host->hal.dmadesc_rx = bus_attr->dmadesc_rx;
host->hal.dmadesc_n = bus_attr->dma_desc_num;
if (host_id != SPI1_HOST) {
//SPI1 attributes are already initialized at start up.
@ -293,7 +299,6 @@ void spi_get_timing(bool gpio_is_used, int input_delay_ns, int eff_clk, int* dum
int spi_get_freq_limit(bool gpio_is_used, int input_delay_ns)
{
return spi_hal_get_freq_limit(gpio_is_used, input_delay_ns);
}
/*
@ -302,7 +307,6 @@ 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_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle)
{
int duty_cycle;
spi_device_t *dev = NULL;
esp_err_t err = ESP_OK;
@ -339,33 +343,32 @@ esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interfa
int freecs = spi_bus_lock_get_dev_id(dev_handle);
SPI_CHECK(freecs != -1, "no free cs pins for the host", ESP_ERR_NOT_FOUND);
duty_cycle = (dev_config->duty_cycle_pos==0) ? 128 : dev_config->duty_cycle_pos;
int freq;
spi_hal_context_t *hal = &(host->hal);
hal->half_duplex = dev_config->flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0;
#ifdef SOC_SPI_SUPPORT_AS_CS
hal->as_cs = dev_config->flags & SPI_DEVICE_CLK_AS_CS ? 1 : 0;
#endif
hal->positive_cs = dev_config->flags & SPI_DEVICE_POSITIVE_CS ? 1 : 0;
hal->no_compensate = dev_config->flags & SPI_DEVICE_NO_DUMMY ? 1 : 0;
//input parameters to calculate timing configuration
int half_duplex = dev_config->flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0;
int no_compensate = dev_config->flags & SPI_DEVICE_NO_DUMMY ? 1 : 0;
int duty_cycle = (dev_config->duty_cycle_pos==0) ? 128 : dev_config->duty_cycle_pos;
int use_gpio = !(bus_attr->flags & SPICOMMON_BUSFLAG_IOMUX_PINS);
spi_hal_timing_param_t timing_param = {
.half_duplex = half_duplex,
.no_compensate = no_compensate,
.clock_speed_hz = dev_config->clock_speed_hz,
.duty_cycle = duty_cycle,
.input_delay_ns = dev_config->input_delay_ns,
.use_gpio = use_gpio
};
//output values of timing configuration
spi_hal_timing_conf_t temp_timing_conf;
esp_err_t ret = spi_hal_cal_clock_conf(hal, dev_config->clock_speed_hz, duty_cycle,
!(bus_attr->flags & SPICOMMON_BUSFLAG_IOMUX_PINS),
dev_config->input_delay_ns, &freq,
&temp_timing_conf);
int freq;
esp_err_t ret = spi_hal_cal_clock_conf(&timing_param, &freq, &temp_timing_conf);
SPI_CHECK(ret==ESP_OK, "assigned clock speed not supported", ret);
//Allocate memory for device
dev=malloc(sizeof(spi_device_t));
if (dev==NULL) goto nomem;
dev = malloc(sizeof(spi_device_t));
if (dev == NULL) goto nomem;
memset(dev, 0, sizeof(spi_device_t));
host->device[freecs] = dev;
dev->id = freecs;
dev->timing_conf = temp_timing_conf;
dev->dev_lock = dev_handle;
//Allocate queues, set defaults
@ -375,8 +378,6 @@ esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interfa
goto nomem;
}
dev->host= host;
//We want to save a copy of the dev config in the dev struct.
memcpy(&dev->cfg, dev_config, sizeof(spi_device_interface_config_t));
dev->cfg.duty_cycle_pos = duty_cycle;
@ -384,10 +385,37 @@ esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interfa
//Set CS pin, CS options
if (dev_config->spics_io_num >= 0) {
spicommon_cs_initialize(host_id, dev_config->spics_io_num, freecs, !(bus_attr->flags & SPICOMMON_BUSFLAG_IOMUX_PINS));
spicommon_cs_initialize(host_id, dev_config->spics_io_num, freecs, use_gpio);
}
*handle=dev;
//save a pointer to device in spi_host_t
host->device[freecs] = dev;
//save a pointer to host in spi_device_t
dev->host= host;
//initialise the device specific configuration
spi_hal_dev_config_t *hal_dev = &(dev->hal_dev);
hal_dev->mode = dev_config->mode;
hal_dev->cs_setup = dev_config->cs_ena_pretrans;
hal_dev->cs_hold = dev_config->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
if (hal_dev->cs_hold == 0) {
hal_dev->cs_hold = 1;
}
hal_dev->cs_pin_id = dev->id;
hal_dev->timing_conf = temp_timing_conf;
hal_dev->sio = (dev_config->flags) & SPI_DEVICE_3WIRE ? 1 : 0;
hal_dev->half_duplex = dev_config->flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0;
hal_dev->tx_lsbfirst = dev_config->flags & SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0;
hal_dev->rx_lsbfirst = dev_config->flags & SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0;
hal_dev->no_compensate = dev_config->flags & SPI_DEVICE_NO_DUMMY ? 1 : 0;
#ifdef SOC_SPI_SUPPORT_AS_CS
hal_dev->as_cs = dev_config->flags& SPI_DEVICE_CLK_AS_CS ? 1 : 0;
#endif
hal_dev->positive_cs = dev_config->flags & SPI_DEVICE_POSITIVE_CS ? 1 : 0;
*handle = dev;
ESP_LOGD(SPI_TAG, "SPI%d: New device added to CS%d, effective clock: %dkHz", host_id+1, freecs, freq/1000);
return ESP_OK;
@ -447,24 +475,9 @@ static SPI_MASTER_ISR_ATTR void spi_setup_device(spi_device_t *dev)
//if the configuration is already applied, skip the following.
return;
}
spi_host_t* host = dev->host;
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
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);
spi_hal_context_t *hal = &dev->host->hal;
spi_hal_dev_config_t *hal_dev = &(dev->hal_dev);
spi_hal_setup_device(hal, hal_dev);
}
static SPI_MASTER_ISR_ATTR spi_device_t *get_acquiring_dev(spi_host_t *host)
@ -501,54 +514,53 @@ static void spi_bus_intr_disable(void *host)
// 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, spi_hal_context_t *hal)
static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_t *trans_buf)
{
spi_transaction_t *trans = NULL;
spi_host_t *host = dev->host;
int dev_id = dev->id;
spi_hal_context_t *hal = &(host->hal);
spi_hal_dev_config_t *hal_dev = &(dev->hal_dev);
trans = trans_buf->trans;
host->cur_cs = dev_id;
host->cur_cs = dev->id;
//Reconfigure according to device settings, the function only has effect when the dev_id is changed.
spi_setup_device(host->device[dev_id]);
spi_setup_device(dev);
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 the transaction specific configuration each time before a transaction setup
spi_hal_trans_config_t hal_trans = {};
hal_trans.tx_bitlen = trans->length;
hal_trans.rx_bitlen = trans->rxlength;
hal_trans.rcv_buffer = (uint8_t*)host->cur_trans_buf.buffer_to_rcv;
hal_trans.send_buffer = (uint8_t*)host->cur_trans_buf.buffer_to_send;
hal_trans.cmd = trans->cmd;
hal_trans.addr = trans->addr;
//Set up QIO/DIO if needed
hal->io_mode = (trans->flags & SPI_TRANS_MODE_DIO ?
hal_trans.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;
hal_trans.cmd_bits = ((spi_transaction_ext_t *)trans)->command_bits;
} else {
hal->cmd_bits = dev->cfg.command_bits;
hal_trans.cmd_bits = dev->cfg.command_bits;
}
if (trans->flags & SPI_TRANS_VARIABLE_ADDR) {
hal->addr_bits = ((spi_transaction_ext_t *)trans)->address_bits;
hal_trans.addr_bits = ((spi_transaction_ext_t *)trans)->address_bits;
} else {
hal->addr_bits = dev->cfg.address_bits;
hal_trans.addr_bits = dev->cfg.address_bits;
}
if (trans->flags & SPI_TRANS_VARIABLE_DUMMY) {
hal->dummy_bits = ((spi_transaction_ext_t *)trans)->dummy_bits;
hal_trans.dummy_bits = ((spi_transaction_ext_t *)trans)->dummy_bits;
} else {
hal->dummy_bits = dev->cfg.dummy_bits;
hal_trans.dummy_bits = dev->cfg.dummy_bits;
}
spi_hal_setup_trans(hal);
spi_hal_prepare_data(hal);
spi_hal_setup_trans(hal, hal_dev, &hal_trans);
spi_hal_prepare_data(hal, hal_dev, &hal_trans);
//Call pre-transmission callback, if any
if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans);
@ -561,6 +573,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;
spi_hal_fetch_result(&host->hal);
//Call post-transaction callback, if any
spi_device_t* dev = host->device[host->cur_cs];
@ -569,7 +582,6 @@ static void SPI_MASTER_ISR_ATTR spi_post_trans(spi_host_t *host)
host->cur_cs = DEV_NUM_MAX;
}
// This is run in interrupt context.
static void SPI_MASTER_ISR_ATTR spi_intr(void *arg)
{
@ -649,7 +661,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(bus_attr->dma_chan);
}
spi_new_trans(device_to_send, cur_trans_buf, (&host->hal));
spi_new_trans(device_to_send, cur_trans_buf);
}
// Exit of the ISR, handle interrupt re-enable (if sending transaction), retry (if there's coming BG),
// or resume acquiring device task (if quit due to bus acquiring).
@ -667,7 +679,7 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl
bool rx_enabled = (trans_desc->flags & SPI_TRANS_USE_RXDATA) || (trans_desc->rx_buffer);
spi_transaction_ext_t *t_ext = (spi_transaction_ext_t *)trans_desc;
bool dummy_enabled = (((trans_desc->flags & SPI_TRANS_VARIABLE_DUMMY)? t_ext->dummy_bits: handle->cfg.dummy_bits) != 0);
bool extra_dummy_enabled = handle->timing_conf.timing_dummy;
bool extra_dummy_enabled = handle->hal_dev.timing_conf.timing_dummy;
bool is_half_duplex = ((handle->cfg.flags & SPI_DEVICE_HALFDUPLEX) != 0);
//check transmission length
@ -763,7 +775,6 @@ clean_up:
return ESP_ERR_NO_MEM;
}
esp_err_t SPI_MASTER_ATTR spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait)
{
esp_err_t ret = check_trans_valid(handle, trans_desc);
@ -841,7 +852,6 @@ esp_err_t SPI_MASTER_ATTR spi_device_transmit(spi_device_handle_t handle, spi_tr
return ESP_OK;
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_acquire_bus(spi_device_t *device, TickType_t wait)
{
spi_host_t *const host = device->host;
@ -897,7 +907,6 @@ void SPI_MASTER_ISR_ATTR spi_device_release_bus(spi_device_t *dev)
assert(ret == ESP_OK);
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_start(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait)
{
esp_err_t ret;
@ -923,12 +932,11 @@ 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, (&host->hal));
spi_new_trans(handle, &host->cur_trans_buf);
return ESP_OK;
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle, TickType_t ticks_to_wait)
{
SPI_CHECK(handle != NULL, "invalid dev handle", ESP_ERR_INVALID_ARG);
@ -960,7 +968,6 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle,
return ESP_OK;
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc)
{
esp_err_t ret;

View File

@ -102,13 +102,14 @@ esp_err_t spi_slave_hd_init(spi_host_device_t host_id, const spi_bus_config_t *b
spi_slave_hd_hal_config_t hal_config = {
.host_id = host_id,
.dma_in = SPI_LL_GET_HW(host_id),
.dma_out = SPI_LL_GET_HW(host_id),
.tx_lsbfirst = (config->flags & SPI_SLAVE_HD_RXBIT_LSBFIRST),
.rx_lsbfirst = (config->flags & SPI_SLAVE_HD_TXBIT_LSBFIRST),
.dma_chan = config->dma_chan,
.mode = config->mode,
.mode = config->mode
};
slave_hd_hal_init(&host->hal, &hal_config);
spi_slave_hd_hal_init(&host->hal, &hal_config);
if (config->dma_chan != 0) {
//See how many dma descriptors we need and allocate them

View File

@ -485,7 +485,7 @@ static inline void spi_ll_master_select_cs(spi_dev_t *hw, int cs_id)
* @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)
static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_clock_val_t *val)
{
hw->clock.val = *(uint32_t *)val;
}

View File

@ -590,7 +590,7 @@ static inline void spi_ll_master_select_cs(spi_dev_t *hw, int cs_id)
* @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)
static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_clock_val_t *val)
{
hw->clock.val = *(uint32_t *)val;
}

View File

@ -38,9 +38,24 @@
#include <esp_err.h>
#include "soc/lldesc.h"
/**
* Input parameters to the ``spi_hal_cal_clock_conf`` to calculate the timing configuration
*/
typedef struct {
uint32_t half_duplex; ///< Whether half duplex mode is used, device specific
uint32_t no_compensate; ///< No need to add dummy to compensate the timing, device specific
uint32_t clock_speed_hz; ///< Desired frequency.
uint32_t duty_cycle; ///< Desired duty cycle of SPI clock
uint32_t 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.
*/
bool use_gpio; ///< True if the GPIO matrix is used, otherwise false
} spi_hal_timing_param_t;
/**
* Timing configuration structure that should be calculated by
* ``spi_hal_setup_clock`` at initialization and hold. Filled into the
* ``spi_hal_cal_clock_conf`` at initialization and hold. Filled into the
* ``timing_conf`` member of the context of HAL before setup a device.
*/
typedef struct {
@ -50,12 +65,12 @@ typedef struct {
} spi_hal_timing_conf_t;
/**
* Context that should be maintained by both the driver and the HAL.
* DMA configuration structure
* Should be set by driver at initialization
*/
typedef struct {
/* configured by driver at initialization, don't touch */
spi_dev_t *hw; ///< Beginning address of the peripheral registers.
/* should be configured by driver at initialization */
spi_dma_dev_t *dma_in; ///< Input DMA(DMA -> RAM) peripheral register address
spi_dma_dev_t *dma_out; ///< Output DMA(RAM -> DMA) peripheral register address
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.
@ -65,36 +80,13 @@ typedef struct {
* 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
#ifdef SOC_SPI_SUPPORT_AS_CS
uint32_t as_cs : 1; ///< Whether to toggle the CS while the clock toggles, device specific
#endif
uint32_t positive_cs : 1; ///< Whether the postive CS feature is abled, device specific
};//boolean configurations
} spi_hal_dma_config_t;
/*
* Transaction specific (data), all these parameters will be updated to the
* peripheral every transaction.
/**
* Transaction configuration structure, this should be assigned by driver each time.
* All these parameters will be updated to the peripheral every transaction.
*/
typedef struct {
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
@ -105,16 +97,56 @@ typedef struct {
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
} spi_hal_trans_config_t;
/**
* Context that should be maintained by both the driver and the HAL.
*/
typedef struct {
/* Configured by driver at initialization, don't touch */
spi_dev_t *hw; ///< Beginning address of the peripheral registers.
spi_dma_dev_t *dma_in; ///< Address of the DMA peripheral registers which stores the data received from a peripheral into RAM (DMA -> RAM).
spi_dma_dev_t *dma_out; ///< Address of the DMA peripheral registers which transmits the data from RAM to a peripheral (RAM -> DMA).
bool dma_enabled; ///< Whether the DMA is enabled, do not update after initialization
spi_hal_dma_config_t dma_config; ///< DMA configuration
/* Internal parameters, don't touch */
spi_hal_trans_config_t trans_config; ///< Transaction configuration
} spi_hal_context_t;
/**
* Device configuration structure, this should be initialised by driver based on different devices respectively.
* 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``.
*/
typedef struct {
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; /**< This structure holds 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 no_compensate : 1; ///< No need to add dummy to compensate the timing, device specific
#ifdef SOC_SPI_SUPPORT_AS_CS
uint32_t as_cs : 1; ///< Whether to toggle the CS while the clock toggles, device specific
#endif
uint32_t positive_cs : 1; ///< Whether the postive CS feature is abled, device specific
};//boolean configurations
} spi_hal_dev_config_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);
void spi_hal_init(spi_hal_context_t *hal, uint32_t host_id, const spi_hal_dma_config_t *hal_dma_config);
/**
* Deinit the peripheral (and the context if needed).
@ -127,22 +159,27 @@ 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.
* @param hal_dev Device configuration
*/
void spi_hal_setup_device(const spi_hal_context_t *hal);
void spi_hal_setup_device(spi_hal_context_t *hal, const spi_hal_dev_config_t *hal_dev);
/**
* Setup transaction related configurations according to the settings in the context.
*
* @param hal Context of the HAL layer.
* @param hal_dev Device configuration
* @param hal_trans Transaction configuration
*/
void spi_hal_setup_trans(const spi_hal_context_t *hal);
void spi_hal_setup_trans(spi_hal_context_t *hal, const spi_hal_dev_config_t *hal_dev, const spi_hal_trans_config_t *hal_trans);
/**
* Prepare the data for the current transaction.
*
* @param hal Context of the HAL layer.
* @param hal_dev Device configuration
* @param hal_trans Transaction configuration
*/
void spi_hal_prepare_data(const spi_hal_context_t *hal);
void spi_hal_prepare_data(spi_hal_context_t *hal, const spi_hal_dev_config_t *hal_dev, const spi_hal_trans_config_t *hal_trans);
/**
* Trigger start a user-defined transaction.
@ -173,19 +210,13 @@ void spi_hal_fetch_result(const spi_hal_context_t *hal);
*
* 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 timing_param Input parameters to calculate timing configuration
* @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_cal_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);
esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, int *out_freq, spi_hal_timing_conf_t *timing_conf);
/**
* Get the frequency actual used.

View File

@ -22,7 +22,7 @@
* The HAL layer for SPI Slave HD mode, currently only segment mode is supported
*
* Usage:
* - Firstly, initialize the slave with `slave_hd_hal_init`
* - Firstly, initialize the slave with `spi_slave_hd_hal_init`
*
* - Event handling:
* - (Optional) Call ``spi_slave_hd_hal_enable_event_intr`` to enable the used interrupts
@ -59,23 +59,27 @@
/// Configuration of the HAL
typedef struct {
int host_id; ///< Host ID of the spi peripheral
int spics_io_num; ///< CS GPIO pin for this device
uint32_t host_id; ///< Host ID of the spi peripheral
dma_dev_t *dma_in; ///< Input DMA(DMA -> RAM) peripheral register address
dma_dev_t *dma_out; ///< Output DMA(RAM -> DMA) peripheral register address
uint32_t spics_io_num; ///< CS GPIO pin for this device
uint8_t mode; ///< SPI mode (0-3)
int command_bits; ///< command field bits, multiples of 8 and at least 8.
int address_bits; ///< address field bits, multiples of 8 and at least 8.
int dummy_bits; ///< dummy field bits, multiples of 8 and at least 8.
uint32_t command_bits; ///< command field bits, multiples of 8 and at least 8.
uint32_t address_bits; ///< address field bits, multiples of 8 and at least 8.
uint32_t dummy_bits; ///< dummy field bits, multiples of 8 and at least 8.
struct {
uint32_t tx_lsbfirst : 1;///< Whether TX data should be sent with LSB first.
uint32_t rx_lsbfirst : 1;///< Whether RX data should be read with LSB first.
uint32_t tx_lsbfirst : 1; ///< Whether TX data should be sent with LSB first.
uint32_t rx_lsbfirst : 1; ///< Whether RX data should be read with LSB first.
};
int dma_chan; ///< The dma channel used.
uint32_t dma_chan; ///< The dma channel used.
} spi_slave_hd_hal_config_t;
/// Context of the HAL, initialized by :cpp:func:`slave_hd_hal_init`.
/// Context of the HAL, initialized by :cpp:func:`spi_slave_hd_hal_init`.
typedef struct {
spi_dev_t* dev; ///< Beginning address of the peripheral registers.
spi_dev_t *dev; ///< Beginning address of the peripheral registers.
dma_dev_t *dma_in; ///< Address of the DMA peripheral registers which stores the data received from a peripheral into RAM.
dma_dev_t *dma_out; ///< Address of the DMA peripheral registers which transmits the data from RAM to a peripheral.
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.
@ -89,21 +93,20 @@ typedef struct {
uint32_t intr_not_triggered;
} spi_slave_hd_hal_context_t;
/**
* @brief Initialize the hardware and part of the context
*
* @param hal Context of the HAL layer
* @param config Configuration of the HAL
* @param hal_config Configuration of the HAL
*/
void slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_config_t *config);
void spi_slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_config_t *hal_config);
/**
* @brief Check and clear signal of one event
*
* @param hal Context of the HAL layer
* @param ev Event to check
* @return true if event triggered, otherwise false
* @return True if event triggered, otherwise false
*/
bool spi_slave_hd_hal_check_clear_event(spi_slave_hd_hal_context_t* hal, spi_event_t ev);
@ -116,7 +119,7 @@ bool spi_slave_hd_hal_check_clear_event(spi_slave_hd_hal_context_t* hal, spi_eve
*
* @param hal Context of the HAL layer
* @param ev Event to check and disable
* @return true if event triggered, otherwise false
* @return True if event triggered, otherwise false
*/
bool spi_slave_hd_hal_check_disable_event(spi_slave_hd_hal_context_t* hal, spi_event_t ev);

View File

@ -24,11 +24,12 @@ static const char SPI_HAL_TAG[] = "spi_hal";
return (ret_val); \
}
void spi_hal_init(spi_hal_context_t *hal, int host_id)
void spi_hal_init(spi_hal_context_t *hal, uint32_t host_id)
{
memset(hal, 0, sizeof(spi_hal_context_t));
spi_dev_t *hw = spi_periph_signal[host_id].hw;
spi_dev_t *hw = SPI_LL_GET_HW(host_id);
hal->hw = hw;
spi_ll_master_init(hw);
//Force a transaction done interrupt. This interrupt won't fire yet because
@ -38,6 +39,9 @@ void spi_hal_init(spi_hal_context_t *hal, int host_id)
spi_ll_enable_int(hw);
spi_ll_set_int_stat(hw);
spi_ll_set_mosi_delay(hw, 0, 0);
//Save the dma configuration in ``spi_hal_context_t``
memcpy(&hal->dma_config, dma_config, sizeof(spi_hal_dma_config_t));
}
void spi_hal_deinit(spi_hal_context_t *hal)
@ -49,20 +53,20 @@ void spi_hal_deinit(spi_hal_context_t *hal)
}
}
esp_err_t spi_hal_cal_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)
esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, 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);
int eff_clk_n = spi_ll_master_cal_clock(APB_CLK_FREQ, timing_param->clock_speed_hz, timing_param->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);
spi_hal_cal_timing(eff_clk_n, timing_param->use_gpio, timing_param->input_delay_ns, &temp_conf.timing_dummy, &temp_conf.timing_miso_delay);
#ifdef CONFIG_IDF_TARGET_ESP32
const int freq_limit = spi_hal_get_freq_limit(use_gpio, input_delay_ns);
const int freq_limit = spi_hal_get_freq_limit(timing_param->use_gpio, timing_param->input_delay_ns);
SPI_HAL_CHECK(hal->half_duplex || temp_conf.timing_dummy == 0 || hal->no_compensate,
SPI_HAL_CHECK(timing_param->half_duplex || temp_conf.timing_dummy == 0 || timing_param->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\

View File

@ -17,29 +17,29 @@
#include "hal/spi_hal.h"
void spi_hal_setup_device(const spi_hal_context_t *hal)
void spi_hal_setup_device(spi_hal_context_t *hal, const spi_hal_dev_config_t *dev)
{
//Configure clock settings
spi_dev_t *hw = hal->hw;
#ifdef SOC_SPI_SUPPORT_AS_CS
spi_ll_master_set_cksel(hw, hal->cs_pin_id, hal->as_cs);
spi_ll_master_set_cksel(hw, dev->cs_pin_id, dev->as_cs);
#endif
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);
spi_ll_master_set_pos_cs(hw, dev->cs_pin_id, dev->positive_cs);
spi_ll_master_set_clock_by_reg(hw, &dev->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);
spi_ll_set_rx_lsbfirst(hw, dev->rx_lsbfirst);
spi_ll_set_tx_lsbfirst(hw, dev->tx_lsbfirst);
spi_ll_master_set_mode(hw, dev->mode);
//Configure misc stuff
spi_ll_set_half_duplex(hw, hal->half_duplex);
spi_ll_set_sio_mode(hw, hal->sio);
spi_ll_set_half_duplex(hw, dev->half_duplex);
spi_ll_set_sio_mode(hw, dev->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);
spi_ll_master_set_cs_setup(hw, dev->cs_setup);
spi_ll_master_set_cs_hold(hw, dev->cs_hold);
spi_ll_master_select_cs(hw, dev->cs_pin_id);
}
void spi_hal_setup_trans(const spi_hal_context_t *hal)
void spi_hal_setup_trans(spi_hal_context_t *hal, const spi_hal_dev_config_t *dev, const spi_hal_trans_config_t *trans)
{
spi_dev_t *hw = hal->hw;
@ -48,23 +48,23 @@ void spi_hal_setup_trans(const spi_hal_context_t *hal)
//We should be done with the transmission.
assert(spi_ll_get_running_cmd(hw) == 0);
spi_ll_master_set_io_mode(hw, hal->io_mode);
spi_ll_master_set_io_mode(hw, trans->io_mode);
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;
if (trans->rcv_buffer && !dev->no_compensate && dev->half_duplex) {
extra_dummy = dev->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);
spi_ll_set_dummy(hw, extra_dummy + trans->dummy_bits);
uint32_t miso_delay_num = 0;
uint32_t miso_delay_mode = 0;
if (hal->timing_conf->timing_miso_delay < 0) {
if (dev->timing_conf.timing_miso_delay < 0) {
//if the data comes too late, delay half a SPI clock to improve reading
switch (hal->mode) {
switch (dev->mode) {
case 0:
miso_delay_mode = 2;
break;
@ -81,24 +81,24 @@ void spi_hal_setup_trans(const spi_hal_context_t *hal)
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_num = extra_dummy ? dev->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);
spi_ll_set_mosi_bitlen(hw, trans->tx_bitlen);
if (hal->half_duplex) {
spi_ll_set_miso_bitlen(hw, hal->rx_bitlen);
if (dev->half_duplex) {
spi_ll_set_miso_bitlen(hw, trans->rx_bitlen);
} else {
//rxlength is not used in full-duplex mode
spi_ll_set_miso_bitlen(hw, hal->tx_bitlen);
spi_ll_set_miso_bitlen(hw, trans->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) {
int cmdlen = trans->cmd_bits;
int addrlen = trans->addr_bits;
if (!dev->half_duplex && dev->cs_setup != 0) {
/* The command and address phase is not compatible with cs_ena_pretrans
* in full duplex mode.
*/
@ -109,45 +109,56 @@ void spi_hal_setup_trans(const spi_hal_context_t *hal)
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);
spi_ll_set_command(hw, trans->cmd, cmdlen, dev->tx_lsbfirst);
spi_ll_set_address(hw, trans->addr, addrlen, dev->tx_lsbfirst);
//Save the transaction attributes for internal usage.
memcpy(&hal->trans_config, trans, sizeof(spi_hal_trans_config_t));
}
void spi_hal_prepare_data(const spi_hal_context_t *hal)
void spi_hal_prepare_data(spi_hal_context_t *hal, const spi_hal_dev_config_t *dev, const spi_hal_trans_config_t *trans)
{
spi_dev_t *hw = hal->hw;
spi_ll_reset_dma(hw);
//Fill DMA descriptors
if (hal->rcv_buffer) {
if (trans->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);
lldesc_setup_link(hal->dma_config.dmadesc_rx, trans->rcv_buffer, ((trans->rx_bitlen + 7) / 8), true);
spi_dma_ll_rx_reset(hal->dma_in);
spi_ll_dma_rx_enable(hal->hw, 1);
spi_dma_ll_rx_start(hal->dma_in, hal->dma_config.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);
spi_dma_ll_rx_start(hal->dma_in, 0);
}
}
if (hal->send_buffer) {
if (trans->send_buffer) {
if (!hal->dma_enabled) {
//Need to copy data to registers manually
spi_ll_write_buffer(hw, hal->send_buffer, hal->tx_bitlen);
spi_ll_write_buffer(hw, trans->send_buffer, trans->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);
lldesc_setup_link(hal->dma_config.dmadesc_tx, trans->send_buffer, (trans->tx_bitlen + 7) / 8, false);
spi_dma_ll_tx_reset(hal->dma_out);
spi_ll_dma_tx_enable(hal->hw, 1);
spi_dma_ll_tx_start(hal->dma_out, hal->dma_config.dmadesc_tx);
}
}
//in ESP32 these registers should be configured after the DMA is set
if ((!hal->half_duplex && hal->rcv_buffer) || hal->send_buffer) {
if ((!dev->half_duplex && trans->rcv_buffer) || trans->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);
spi_ll_enable_miso(hw, (trans->rcv_buffer) ? 1 : 0);
}
void spi_hal_user_start(const spi_hal_context_t *hal)
@ -162,8 +173,10 @@ bool spi_hal_usr_is_done(const spi_hal_context_t *hal)
void spi_hal_fetch_result(const spi_hal_context_t *hal)
{
if (hal->rcv_buffer && !hal->dma_enabled) {
const spi_hal_trans_config_t *trans = &hal->trans_config;
if (trans->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);
spi_ll_read_buffer(hal->hw, trans->rcv_buffer, trans->rx_bitlen);
}
}

View File

@ -19,27 +19,25 @@
#include "esp_attr.h"
#include "esp_err.h"
#include "sdkconfig.h"
#include "soc/spi_periph.h"
#include "soc/lldesc.h"
#include "hal/spi_slave_hd_hal.h"
void slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_config_t *config)
void spi_slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_config_t *hal_config)
{
memset(hal, 0, sizeof(spi_slave_hd_hal_context_t));
spi_dev_t* hw = SPI_LL_GET_HW(config->host_id);
spi_dev_t* hw = SPI_LL_GET_HW(hal_config->host_id);
hal->dev = hw;
//Configure slave
spi_ll_slave_hd_init(hw);
spi_ll_set_addr_bitlen(hw, config->address_bits);
spi_ll_set_command_bitlen(hw, config->command_bits);
spi_ll_set_dummy(hw, config->dummy_bits);
spi_ll_set_rx_lsbfirst(hw, config->rx_lsbfirst);
spi_ll_set_tx_lsbfirst(hw, config->tx_lsbfirst);
spi_ll_slave_set_mode(hw, config->mode, (config->dma_chan != 0));
spi_ll_set_addr_bitlen(hw, hal_config->address_bits);
spi_ll_set_command_bitlen(hw, hal_config->command_bits);
spi_ll_set_dummy(hw, hal_config->dummy_bits);
spi_ll_set_rx_lsbfirst(hw, hal_config->rx_lsbfirst);
spi_ll_set_tx_lsbfirst(hw, hal_config->tx_lsbfirst);
spi_ll_slave_set_mode(hw, hal_config->mode, (hal_config->dma_chan != 0));
spi_ll_disable_intr(hw, UINT32_MAX);
spi_ll_clear_intr(hw, UINT32_MAX);
@ -66,7 +64,7 @@ void slave_hd_hal_init(spi_slave_hd_hal_context_t *hal, const spi_slave_hd_hal_c
SPI_LL_TRANS_LEN_COND_RDBUF |
SPI_LL_TRANS_LEN_COND_RDDMA);
spi_ll_slave_set_seg_mode(hw, true);
spi_ll_slave_set_seg_mode(hal->dev, true);
}
void spi_slave_hd_hal_rxdma(spi_slave_hd_hal_context_t *hal, uint8_t *out_buf, size_t len)