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

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