Skip to content

Instantly share code, notes, and snippets.

@sethp
Created April 30, 2023 13:45
Show Gist options
  • Save sethp/29bc45b2d5629b4b3a9d43c27683cd9a to your computer and use it in GitHub Desktop.
Save sethp/29bc45b2d5629b4b3a9d43c27683cd9a to your computer and use it in GitHub Desktop.
esp32c3 timing research

deltas

esp-rs idf notes
spi::Instance::init_half_duplex(...) spi_setup_device
hw->dma_int_raw.trans_done = 0
HAL_ASSERT(spi_ll_get_running_cmd(hw) == 0) when assertions are off, this still appears to load the hw->cmd.val register, it just ignores the result
spi::Instance::init_spi_data_mode spi_ll_master_set_line_mode
cmd, address setup
write usr_dummy_cyclelen spi_ll_set_dummy they've got a notion of rx_compensation here too that I don't think applies to the half-duplex tx case
spi_ll_set_mosi_bitlen
spi_ll_set_miso_bitlen
spi_ll_set_addr_bitlen
spi_ll_set_command_bitlen
spi_ll_set_command
spi_ll_set_address
spi_ll_master_keep_cs esp-rs takes care of this in init_half_duplex
spi::Instance::configure_datalen
tx.is_done() this loads the DMA channel's out_total_eof bit (and ignores the result)
spi::InstanceDma::enable_dma sets both dma_conf.dma_tx_ena and dma_conf.dma_rx_ena
spi::Instance::update this doesn't happen in the idf until the very end, just before setting hw->cmd.user = 1
prepare descriptors lldesc_setup_link I didn't go in depth to see if there's any differences here
<gdma::Channel0 as dma::RegisterAccess>::clear_out_interrupts out_eof
out_dscr_err
out_done
out_total_eof
outfifo_ovf
outfifo_udf
(via dma_int_clr)
<gdma::Channel0 as dma::RegisterAccess>::reset_out spi_dma_ll_tx_reset toggles the channel's out_conf0.out_rst bit
spi_ll_dma_tx_fifo_reset toggles the dma_conf.dma_afifo_rst bit
<gdma::Channel0 as dma::RegisterAccess>::set_out_descriptors
spi_ll_outfifo_empty_clr hw->dma_int_clr.outfifo_empty_err = 1
spi_ll_dma_tx_enable sets dma_conf.dma_tx_ena
gdma_ll_tx_set_desc_addr
<gdma::Channel0 as dma::RegisterAccess>::start_out gdma_ll_tx_start
check <gdma::Channel0 as dma::RegisterAccess>::has_out_descriptor_error
spi_ll_enable_mosi(.., 1)
spi_ll_enable_miso(.., 0)
per the comment in spi_hal_prepare_data , this is here because the ESP32 needs it set up after the DMA is started, but it's not #ifdef'd or anything so every chip is going to perform this step here; esp-rs/esp-hal does it much earlier as part of init_half_duplex
spi::Instance::clear_dma_interrupts dma_infifo_full_err_int_clr
dma_outfifo_empty_err_int_clr
trans_done_int_clr
mst_rx_afifo_wfull_err_int_clr
mst_tx_afifo_rempty_err_int_clr
(via dma_int_clr)
spi::reset_dma_before_usr_cmd toggles the dma_conf.dma_afifo_rst bit
spi_hal_user_start... the equivalent of spi::Instance::update occurs here
.. w.usr().set_bit() .. ...spi_hal_user_start sets the usr.cmd bit to 1

esp-rs definitions

Starting from: spi_new_trans

// 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)
{
    host->cur_cs = dev->id;

    //Reconfigure according to device settings, the function only has effect when the dev_id is changed.
    spi_setup_device(dev);
spi_setup_device

spi_setup_device (wish I could link from the summary)

// 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 SPI_MASTER_ISR_ATTR void spi_setup_device(spi_device_t *dev) {
    spi_bus_lock_dev_handle_t dev_lock = dev->dev_lock;
    spi_hal_context_t *hal = &dev->host->hal;
    spi_hal_dev_config_t *hal_dev = &(dev->hal_dev);

    if (spi_bus_lock_touch(dev_lock)) {
        /* Configuration has not been applied yet. */
        spi_hal_setup_device(hal, hal_dev);
    }
}

Looks like it's a lazy initialization situation that stores back to the dev handle that I'd hold on to and pass back in when I want to use the SPI.

https://github.com/espressif/esp-idf/blob/df9310ada26123d8d478bcfa203f874d8c21d654/components/hal/spi_hal_iram.c#L31C1-L51

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;
#if SOC_SPI_AS_CS_SUPPORTED
    spi_ll_master_set_cksel(hw, dev->cs_pin_id, dev->as_cs);
#endif
    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);
    spi_ll_set_clk_source(hw, dev->timing_conf.clock_source);
    //Configure bit order
    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, dev->half_duplex);
    spi_ll_set_sio_mode(hw, dev->sio);
    //Configure CS pin and timing
    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);
}

https://github.com/espressif/esp-idf/blob/df9310ada26123d8d478bcfa203f874d8c21d654/components/hal/spi_hal_iram.c#L31-L52

maybe this is interesting?

static inline void spi_ll_set_half_duplex(spi_dev_t *hw, bool half_duplex)
{
    hw->user.doutdin = !half_duplex;
}

...
 * SIO is a mode which MOSI and MISO share a line. The device MUST work in half-duplexmode.
...
static inline void spi_ll_set_sio_mode(spi_dev_t *hw, int sio_mode)
{
    hw->user.sio = sio_mode;
}

https://github.com/espressif/esp-idf/blob/df9310ada26123d8d478bcfa203f874d8c21d654/components/hal/esp32c3/include/hal/spi_ll.h#L546-L568

    //set the transaction specific configuration each time before a transaction setup
    spi_hal_trans_config_t hal_trans = {};
    ...
hal_trans setup
    //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;
    hal_trans.cs_keep_active = (trans->flags & SPI_TRANS_CS_KEEP_ACTIVE) ? 1 : 0;

    //Set up OIO/QIO/DIO if needed
    hal_trans.line_mode.data_lines = (trans->flags & SPI_TRANS_MODE_DIO) ? 2 :
        (trans->flags & SPI_TRANS_MODE_QIO) ? 4 : 1;
#if SOC_SPI_SUPPORT_OCT
    if (trans->flags & SPI_TRANS_MODE_OCT) {
        hal_trans.line_mode.data_lines = 8;
    }
#endif
    hal_trans.line_mode.addr_lines = (trans->flags & SPI_TRANS_MULTILINE_ADDR) ? hal_trans.line_mode.data_lines : 1;
    hal_trans.line_mode.cmd_lines = (trans->flags & SPI_TRANS_MULTILINE_CMD) ? hal_trans.line_mode.data_lines : 1;

    if (trans->flags & SPI_TRANS_VARIABLE_CMD) {
        hal_trans.cmd_bits = ((spi_transaction_ext_t *)trans)->command_bits;
    } else {
        hal_trans.cmd_bits = dev->cfg.command_bits;
    }
    if (trans->flags & SPI_TRANS_VARIABLE_ADDR) {
        hal_trans.addr_bits = ((spi_transaction_ext_t *)trans)->address_bits;
    } else {
        hal_trans.addr_bits = dev->cfg.address_bits;
    }
    if (trans->flags & SPI_TRANS_VARIABLE_DUMMY) {
        hal_trans.dummy_bits = ((spi_transaction_ext_t *)trans)->dummy_bits;
    } else {
        hal_trans.dummy_bits = dev->cfg.dummy_bits;
    }

https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/driver/spi/gpspi/spi_master.c#L598-L633

    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);
    //Kick off transfer
    spi_hal_user_start(hal);

Flattening:

    // spi_hal_setup_trans(hal, hal_dev, &hal_trans);
    { 
        hw->dma_int_raw.trans_done = 0; // spi_ll_clear_int_stat
        ((void)(hw->cmd.val == 0)); // via HAL_ASSERT(spi_ll_get_running_cmd(hw) == 0)
        // spi_ll_master_set_line_mode
        { 
            hw->ctrl.val &= ~(SPI_FREAD_QUAD | SPI_FREAD_DUAL | SPI_FCMD_QUAD | SPI_FCMD_DUAL | SPI_FADDR_QUAD | SPI_FADDR_DUAL);
            hw->user.val &= ~(SPI_FWRITE_QUAD | SPI_FWRITE_DUAL);
            hw->ctrl.fcmd_dual = (line_mode.cmd_lines == 2);
            hw->ctrl.fcmd_quad = (line_mode.cmd_lines == 4);
            hw->ctrl.faddr_dual = (line_mode.addr_lines == 2);
            hw->ctrl.faddr_quad = (line_mode.addr_lines == 4);
            hw->ctrl.fread_dual = (line_mode.data_lines == 2);
            hw->user.fwrite_dual = (line_mode.data_lines == 2);
            hw->ctrl.fread_quad = (line_mode.data_lines == 4);
            hw->user.fwrite_quad = (line_mode.data_lines == 4);
        } // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L576C20-L588


        // extra_dummy applies to rx, not tx, ignoring...
        // spi_ll_set_dummy(hw, (extra_dummy = 0) + trans->dummy_bits);
        {
            hw->user.usr_dummy = dummy_n ? 1 : 0;
            HAL_FORCE_MODIFY_U32_REG_FIELD(hw->user1, usr_dummy_cyclelen, dummy_n - 1);
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L950

        // uint32_t miso_delay_num = 0;
        // uint32_t miso_delay_mode = 0;
        // ...
        // spi_ll_set_miso_delay(...)
        // skip this whole section, since the esp32c3 doesn't do anything with it
        {
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L775-L778

        // spi_ll_set_mosi_bitlen(hw, trans->tx_bitlen);
        {
            if (bitlen > 0) {
                hw->ms_dlen.ms_data_bitlen = bitlen - 1;
            }
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L816

        // if (dev->half_duplex) { .. } else { .. }
        // both branches are 
        // spi_ll_set_miso_bitlen(...) 
        {
            if (bitlen > 0) {
                hw->ms_dlen.ms_data_bitlen = bitlen - 1;
            }
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L830-L834

        // kind of skipping these, since we're not using them
        // but they still do write to registers, so they take time
        // ...
        // spi_ll_set_addr_bitlen(..)
        // spi_ll_set_command_bitlen(..)
        hw->user1.usr_addr_bitlen = bitlen - 1;
        hw->user.usr_addr = bitlen ? 1 : 0;
        hw->user2.usr_command_bitlen = bitlen - 1;
        hw->user.usr_command = bitlen ? 1 : 0;
        // spi_ll_set_command(..)
        // spi_ll_set_address(..)
        HAL_FORCE_MODIFY_U32_REG_FIELD(hw->user2, usr_command_value, /* cmd */);
        hw->addr = /* addr */;
        // spi_ll_master_keep_cs
        hw->misc.cs_keep_active = (keep_active != 0) ? 1 : 0;
        //Save the transaction attributes for internal usage.
        memcpy(&hal->trans_config, trans, sizeof(spi_hal_trans_config_t));
    }
    // ­— https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/spi_hal_iram.c#L54


    // spi_hal_prepare_data(hal, hal_dev, &hal_trans);
    {
        // skipping the rcv_buffer thing and CONFIG_IDF_TARGET_ESP32
        // if (trans->rcv_buffer) { ... } 

        // if (trans->send_buffer) { if (!hal->dma_enabled) { ... } else { ... } }
        // assuming (hal->dma_enabled) ...

        // lldesc_setup_link(hal->dmadesc_tx, trans->send_buffer, (trans->tx_bitlen + 7) / 8, false);
        // -> lldesc_setup_link_constrained(out_desc_array, buffer, size, LLDESC_MAX_NUM_PER_DESC, isrx)
        { /* populates hal->dmadesc_tx */ }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/soc/lldesc.c#L3

        // spi_dma_ll_tx_reset(hal->dma_out, hal->tx_dma_chan);
        // -> gdma_ll_tx_reset_channel(&GDMA, chan);
        {
            dev->channel[channel].out.out_conf0.out_rst = 1;
            dev->channel[channel].out.out_conf0.out_rst = 0;
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/gdma_ll.h#L351-L354

        // spi_ll_dma_tx_fifo_reset(hal->hw);
        {
            hw->dma_conf.dma_afifo_rst = 1;
            hw->dma_conf.dma_afifo_rst = 0;
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L277-L280
        // spi_ll_outfifo_empty_clr(hal->hw);
        {
            hw->dma_int_clr.outfifo_empty_err = 1;
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L309-L311C2
        // spi_ll_dma_tx_enable(hal->hw, 1);
        {
            hw->dma_conf.dma_tx_ena = 1;
        }
        // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#LL334C1-L337C1
        // spi_dma_ll_tx_start(hal->dma_out, hal->tx_dma_chan, hal->dmadesc_tx);
        // ->   gdma_ll_tx_set_desc_addr(&GDMA, chan, (uint32_t)addr); 
        //      gdma_ll_tx_start(&GDMA, chan);
        {
            dev->channel[channel].out.out_link.addr = addr; // gdma_ll_tx_set_desc_addr
            dev->channel[channel].out.out_link.start = 1;   // gdma_ll_tx_start
        }

        // if (.. || trans->send_buffer) { .. } else { ... }        
        hw->user.usr_mosi = 1; // spi_ll_enable_mosi(hw, 1);
        hw->user.usr_miso = 0; // spi_ll_enable_miso(hw, 0);
    }
    // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/spi_hal_iram.c#L170-L174

    // dev->cfg.pre_cb is user-supplied and not mandatory; ignoring it
    // if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans);

    // spi_hal_user_start(hal)
    // -> spi_ll_master_user_start(hal->hw)
    {
        hw->cmd.update = 1;
        while (hw->cmd.update);
        hw->cmd.usr = 1;
    }
    // — https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/esp32c3/include/hal/spi_ll.h#L205C20-L210

HAL_ASSERT: https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/platform_port/include/hal/assert.h#L35-L41

looks like it always evaluates the expression, just throws it away instead of checking when asserts are off

HAL_FORCE_MODIFY_U32_REG_FIELD: https://github.com/espressif/esp-idf/blob/56123c52aaa08f1b53350c7af30c91320b352ef4/components/hal/platform_port/include/hal/misc.h#L30

Macro to force a 32-bit read, modify, then write on a peripheral register (b/c of gcc compiler bug, looks like)

dma -> gdma mapping: looks like the esp32 lacked GDMA so it's got SPI-specific DMA stuff going on

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment