From 39c85269cc75af5c0910978a3aa35feaf550e952 Mon Sep 17 00:00:00 2001 From: wanckl Date: Sun, 26 Jan 2025 20:32:27 +0800 Subject: [PATCH] feat(spi_master): add transaction config for dynamic clock speed update Closes https://github.com/espressif/esp-idf/issues/13753 --- .../include/driver/spi_master.h | 39 ++++---- .../esp_driver_spi/src/gpspi/spi_master.c | 32 +++++-- .../spi_bench_mark/include/spi_performance.h | 6 +- .../test_apps/master/main/test_spi_master.c | 38 ++++++++ components/hal/esp32/include/hal/spi_ll.h | 2 + components/hal/esp32c2/include/hal/spi_ll.h | 2 + components/hal/esp32c3/include/hal/spi_ll.h | 2 + components/hal/esp32c5/include/hal/spi_ll.h | 2 + components/hal/esp32c6/include/hal/spi_ll.h | 2 + components/hal/esp32c61/include/hal/spi_ll.h | 2 + components/hal/esp32h2/include/hal/spi_ll.h | 2 + components/hal/esp32p4/include/hal/spi_ll.h | 2 + components/hal/esp32s2/include/hal/spi_ll.h | 2 + components/hal/esp32s3/include/hal/spi_ll.h | 2 + components/hal/include/hal/spi_hal.h | 2 + components/hal/spi_hal.c | 95 ------------------- components/hal/spi_hal_iram.c | 89 +++++++++++++++++ .../api-reference/peripherals/spi_master.rst | 8 +- .../api-reference/peripherals/spi_master.rst | 6 +- tools/idf_py_actions/hints.yml | 4 + 20 files changed, 211 insertions(+), 128 deletions(-) diff --git a/components/esp_driver_spi/include/driver/spi_master.h b/components/esp_driver_spi/include/driver/spi_master.h index ecc5debb1d..a432e62334 100644 --- a/components/esp_driver_spi/include/driver/spi_master.h +++ b/components/esp_driver_spi/include/driver/spi_master.h @@ -137,6 +137,7 @@ struct spi_transaction_t { */ size_t length; ///< Total data length, in bits size_t rxlength; ///< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``). + uint32_t override_freq_hz; ///< New freq speed value will override for current device, remain `0` to skip update. Need about 30us each time void *user; ///< User-defined variable. Can be used to store eg transaction ID. union { const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase @@ -205,7 +206,7 @@ esp_err_t spi_bus_remove_device(spi_device_handle_t handle); * @note Normally a device cannot start (queue) polling and interrupt * transactions simultaneously. * - * @param handle Device handle obtained using spi_host_add_dev + * @param handle Device handle obtained using spi_bus_add_device() * @param trans_desc Description of transaction to execute * @param ticks_to_wait Ticks to wait until there's room in the queue; use portMAX_DELAY to * never time out. @@ -228,7 +229,7 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t * * completed transaction so software can inspect the result and e.g. free the memory or * reuse the buffers. * - * @param handle Device handle obtained using spi_host_add_dev + * @param handle Device handle obtained using spi_bus_add_device() * @param trans_desc Pointer to variable able to contain a pointer to the description of the transaction that is executed. The descriptor should not be modified until the descriptor is returned by spi_device_get_trans_result. @@ -252,7 +253,7 @@ esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transactio * Normally a device cannot start (queue) polling and interrupt * transactions simutanuously. * - * @param handle Device handle obtained using spi_host_add_dev + * @param handle Device handle obtained using spi_bus_add_device() * @param trans_desc Description of transaction to execute * @return * - ESP_ERR_INVALID_ARG if parameter is invalid @@ -267,7 +268,7 @@ esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *tra * transactions simutanuously. Moreover, a device cannot start a new polling * transaction if another polling transaction is not finished. * - * @param handle Device handle obtained using spi_host_add_dev + * @param handle Device handle obtained using spi_bus_add_device() * @param trans_desc Description of transaction to execute * @param ticks_to_wait Ticks to wait until there's room in the queue; * currently only portMAX_DELAY is supported. @@ -290,7 +291,7 @@ esp_err_t spi_device_polling_start(spi_device_handle_t handle, spi_transaction_t * successfully completed. The task is not blocked, but actively busy-spins for * the transaction to be completed. * - * @param handle Device handle obtained using spi_host_add_dev + * @param handle Device handle obtained using spi_bus_add_device() * @param ticks_to_wait Ticks to wait until there's a returned item; use portMAX_DELAY to never time out. * @return @@ -310,7 +311,7 @@ esp_err_t spi_device_polling_end(spi_device_handle_t handle, TickType_t ticks_to * Normally a device cannot start (queue) polling and interrupt * transactions simutanuously. * - * @param handle Device handle obtained using spi_host_add_dev + * @param handle Device handle obtained using spi_bus_add_device() * @param trans_desc Description of transaction to execute * @return * - ESP_ERR_INVALID_ARG if parameter is invalid @@ -368,19 +369,19 @@ esp_err_t spi_device_get_actual_freq(spi_device_handle_t handle, int *freq_khz); int spi_get_actual_clock(int fapb, int hz, int duty_cycle) __attribute__((deprecated("Please use spi_device_get_actual_freq instead"))); /** - * @brief Calculate the timing settings of specified frequency and settings. - * - * @param gpio_is_used True if using GPIO matrix, or False if iomux pins are used. - * @param input_delay_ns Input delay from SCLK launch edge to MISO data valid. - * @param eff_clk Effective clock frequency (in Hz) from `spi_get_actual_clock()`. - * @param dummy_o Address of dummy bits used output. Set to NULL if not needed. - * @param cycles_remain_o Address of cycles remaining (after dummy bits are used) output. - * - -1 If too many cycles remaining, suggest to compensate half a clock. - * - 0 If no remaining cycles or dummy bits are not used. - * - positive value: cycles suggest to compensate. - * - * @note If **dummy_o* is not zero, it means dummy bits should be applied in half duplex mode, and full duplex mode may not work. - */ + * @brief Calculate the timing settings of specified frequency and settings. + * + * @param gpio_is_used True if using GPIO matrix, or False if iomux pins are used. + * @param input_delay_ns Input delay from SCLK launch edge to MISO data valid. + * @param eff_clk Effective clock frequency (in Hz) from `spi_get_actual_clock()`. + * @param dummy_o Address of dummy bits used output. Set to NULL if not needed. + * @param cycles_remain_o Address of cycles remaining (after dummy bits are used) output. + * - -1 If too many cycles remaining, suggest to compensate half a clock. + * - 0 If no remaining cycles or dummy bits are not used. + * - positive value: cycles suggest to compensate. + * + * @note If **dummy_o* is not zero, it means dummy bits should be applied in half duplex mode, and full duplex mode may not work. + */ void spi_get_timing(bool gpio_is_used, int input_delay_ns, int eff_clk, int *dummy_o, int *cycles_remain_o); /** diff --git a/components/esp_driver_spi/src/gpspi/spi_master.c b/components/esp_driver_spi/src/gpspi/spi_master.c index 47cd38262f..01eaad4000 100644 --- a/components/esp_driver_spi/src/gpspi/spi_master.c +++ b/components/esp_driver_spi/src/gpspi/spi_master.c @@ -487,6 +487,7 @@ esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interfa SPI_CHECK(ret == ESP_OK, "assigned clock speed not supported", ret); temp_timing_conf.clock_source = clk_src; temp_timing_conf.source_pre_div = clock_source_div; + temp_timing_conf.source_real_freq = clock_source_hz; temp_timing_conf.rx_sample_point = dev_config->sample_point; if (temp_timing_conf.rx_sample_point == SPI_SAMPLING_POINT_PHASE_1) { SPI_CHECK(spi_ll_master_is_rx_std_sample_supported(), "SPI_SAMPLING_POINT_PHASE_1 is not supported on this chip", ESP_ERR_NOT_SUPPORTED); @@ -626,14 +627,33 @@ int spi_get_actual_clock(int fapb, int hz, int 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 SPI_MASTER_ISR_ATTR void spi_setup_device(spi_device_t *dev) +// changes or timing change is required. +static SPI_MASTER_ISR_ATTR void spi_setup_device(spi_device_t *dev, spi_trans_priv_t *trans_buf) { 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)) { + bool clock_changed = false; + // check if timing config update is required + if (trans_buf && (trans_buf->trans->override_freq_hz > 0) && (hal_dev->timing_conf.expect_freq != trans_buf->trans->override_freq_hz)) { + spi_hal_timing_param_t timing_param = { + .expected_freq = trans_buf->trans->override_freq_hz, + .clk_src_hz = dev->hal_dev.timing_conf.source_real_freq, + .duty_cycle = dev->cfg.duty_cycle_pos, + .input_delay_ns = dev->cfg.input_delay_ns, + .half_duplex = dev->hal_dev.half_duplex, + .use_gpio = !(dev->host->bus_attr->flags | SPICOMMON_BUSFLAG_IOMUX_PINS), + }; + + if (ESP_OK == spi_hal_cal_clock_conf(&timing_param, &dev->hal_dev.timing_conf)) { + clock_changed = true; + } else { + ESP_EARLY_LOGW(SPI_TAG, "assigned clock speed %d not supported", trans_buf->trans->override_freq_hz); + } + } + + if (spi_bus_lock_touch(dev_lock) || clock_changed) { /* Configuration has not been applied yet. */ spi_hal_setup_device(hal, hal_dev); SPI_MASTER_PERI_CLOCK_ATOMIC() { @@ -781,7 +801,7 @@ static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_ dev->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(dev, trans_buf); //set the transaction specific configuration each time before a transaction setup spi_hal_trans_config_t hal_trans = {}; @@ -856,7 +876,7 @@ static void SPI_MASTER_ISR_ATTR spi_new_sct_trans(spi_device_t *dev, spi_sct_tra dev->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(dev, NULL); #if !CONFIG_IDF_TARGET_ESP32S2 // s2 update this seg_gap_clock_len by dma from conf_buffer @@ -1310,7 +1330,7 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_acquire_bus(spi_device_t *device, TickT esp_pm_lock_acquire(host->bus_attr->pm_lock); #endif //configure the device ahead so that we don't need to do it again in the following transactions - spi_setup_device(host->device[device->id]); + spi_setup_device(host->device[device->id], NULL); //the DMA is also occupied by the device, all the slave devices that using DMA should wait until bus released. #if CONFIG_IDF_TARGET_ESP32 diff --git a/components/esp_driver_spi/test_apps/components/spi_bench_mark/include/spi_performance.h b/components/esp_driver_spi/test_apps/components/spi_bench_mark/include/spi_performance.h index 2b68f9d040..bd734ba8c1 100644 --- a/components/esp_driver_spi/test_apps/components/spi_bench_mark/include/spi_performance.h +++ b/components/esp_driver_spi/test_apps/components/spi_bench_mark/include/spi_performance.h @@ -54,21 +54,21 @@ #endif #elif CONFIG_IDF_TARGET_ESP32C6 -#define IDF_PERFORMANCE_MAX_SPI_CLK_FREQ 26*1000*1000 +#define IDF_PERFORMANCE_MAX_SPI_CLK_FREQ 26666*1000 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING 35 //TODO: IDF-9551, check perform #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING 17 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING_NO_DMA 32 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING_NO_DMA 15 #elif CONFIG_IDF_TARGET_ESP32H2 -#define IDF_PERFORMANCE_MAX_SPI_CLK_FREQ 26*1000*1000 +#define IDF_PERFORMANCE_MAX_SPI_CLK_FREQ 24*1000*1000 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING 32 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING_NO_DMA 25 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING 61 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING_NO_DMA 54 #elif CONFIG_IDF_TARGET_ESP32P4 -#define IDF_PERFORMANCE_MAX_SPI_CLK_FREQ 26*1000*1000 +#define IDF_PERFORMANCE_MAX_SPI_CLK_FREQ 20*1000*1000 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING 44 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_POLLING 28 #define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING_NO_DMA 26 diff --git a/components/esp_driver_spi/test_apps/master/main/test_spi_master.c b/components/esp_driver_spi/test_apps/master/main/test_spi_master.c index f3d284d69e..8ad3864feb 100644 --- a/components/esp_driver_spi/test_apps/master/main/test_spi_master.c +++ b/components/esp_driver_spi/test_apps/master/main/test_spi_master.c @@ -181,6 +181,44 @@ TEST_CASE("SPI Master clk_source and divider accuracy", "[spi]") TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST)); } +TEST_CASE("test_device_dynamic_freq_update", "[spi]") +{ + spi_device_handle_t dev0; + int master_send; + + spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG(); + spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG(); + devcfg.flags |= SPI_DEVICE_HALFDUPLEX; + TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); + TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev0)); + + spi_transaction_t trans_cfg = { + .tx_buffer = &master_send, + .length = sizeof(master_send) * 8, + }; + + trans_cfg.override_freq_hz = IDF_PERFORMANCE_MAX_SPI_CLK_FREQ; + for (int i = 1; i < 15; i++) { + TEST_ESP_OK(spi_device_transmit(dev0, &trans_cfg)); + spi_device_get_actual_freq(dev0, &master_send); + printf("override %5ld k real freq %5d k\n", trans_cfg.override_freq_hz / 1000, master_send); + if (trans_cfg.override_freq_hz) { + TEST_ASSERT_EQUAL_UINT32(trans_cfg.override_freq_hz / 1000, master_send); + } + if (i > 10) { + //test without override freq + trans_cfg.override_freq_hz = 0; + continue; + } + if (!(i % 2)) { + //test override freq every 2 trans + trans_cfg.override_freq_hz /= 2; + } + } + TEST_ESP_OK(spi_bus_remove_device(dev0)); + TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST)); +} + static spi_device_handle_t setup_spi_bus_loopback(int clkspeed, bool dma) { spi_bus_config_t buscfg = { diff --git a/components/hal/esp32/include/hal/spi_ll.h b/components/hal/esp32/include/hal/spi_ll.h index 50b43a70cb..dbcbb22d69 100644 --- a/components/hal/esp32/include/hal/spi_ll.h +++ b/components/hal/esp32/include/hal/spi_ll.h @@ -652,6 +652,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -667,6 +668,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) 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 = {.val = 0}; diff --git a/components/hal/esp32c2/include/hal/spi_ll.h b/components/hal/esp32c2/include/hal/spi_ll.h index 0247195561..68cac82ca7 100644 --- a/components/hal/esp32c2/include/hal/spi_ll.h +++ b/components/hal/esp32c2/include/hal/spi_ll.h @@ -735,6 +735,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -750,6 +751,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32c3/include/hal/spi_ll.h b/components/hal/esp32c3/include/hal/spi_ll.h index df01a819b8..998e19c5cf 100644 --- a/components/hal/esp32c3/include/hal/spi_ll.h +++ b/components/hal/esp32c3/include/hal/spi_ll.h @@ -737,6 +737,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -752,6 +753,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32c5/include/hal/spi_ll.h b/components/hal/esp32c5/include/hal/spi_ll.h index 44363e920a..30f0ac56f6 100644 --- a/components/hal/esp32c5/include/hal/spi_ll.h +++ b/components/hal/esp32c5/include/hal/spi_ll.h @@ -735,6 +735,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -750,6 +751,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32c6/include/hal/spi_ll.h b/components/hal/esp32c6/include/hal/spi_ll.h index 2eaa037b27..c52ec2f26c 100644 --- a/components/hal/esp32c6/include/hal/spi_ll.h +++ b/components/hal/esp32c6/include/hal/spi_ll.h @@ -728,6 +728,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -743,6 +744,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32c61/include/hal/spi_ll.h b/components/hal/esp32c61/include/hal/spi_ll.h index 70fcfe9d70..1ad870726c 100644 --- a/components/hal/esp32c61/include/hal/spi_ll.h +++ b/components/hal/esp32c61/include/hal/spi_ll.h @@ -748,6 +748,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -763,6 +764,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32h2/include/hal/spi_ll.h b/components/hal/esp32h2/include/hal/spi_ll.h index ab8712ff3b..fade1a565e 100644 --- a/components/hal/esp32h2/include/hal/spi_ll.h +++ b/components/hal/esp32h2/include/hal/spi_ll.h @@ -730,6 +730,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -745,6 +746,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32p4/include/hal/spi_ll.h b/components/hal/esp32p4/include/hal/spi_ll.h index 5adb3c3750..f2ccadf0d7 100644 --- a/components/hal/esp32p4/include/hal/spi_ll.h +++ b/components/hal/esp32p4/include/hal/spi_ll.h @@ -784,6 +784,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -799,6 +800,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32s2/include/hal/spi_ll.h b/components/hal/esp32s2/include/hal/spi_ll.h index 235770ab03..68f0834f79 100644 --- a/components/hal/esp32s2/include/hal/spi_ll.h +++ b/components/hal/esp32s2/include/hal/spi_ll.h @@ -727,6 +727,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -742,6 +743,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/esp32s3/include/hal/spi_ll.h b/components/hal/esp32s3/include/hal/spi_ll.h index 5427bf6c68..c3db5cbd26 100644 --- a/components/hal/esp32s3/include/hal/spi_ll.h +++ b/components/hal/esp32s3/include/hal/spi_ll.h @@ -756,6 +756,7 @@ static inline void spi_ll_master_set_clock_by_reg(spi_dev_t *hw, const spi_ll_cl * * @return Frequency of given dividers. */ +__attribute__((always_inline)) static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) { return (fapb / (pre * n)); @@ -771,6 +772,7 @@ static inline int spi_ll_freq_for_pre_n(int fapb, int pre, int n) * * @return Actual (nearest) frequency. */ +__attribute__((always_inline)) static inline int spi_ll_master_cal_clock(int fapb, int hz, int duty_cycle, spi_ll_clock_val_t *out_reg) { typeof(GPSPI2.clock) reg = {.val = 0}; diff --git a/components/hal/include/hal/spi_hal.h b/components/hal/include/hal/spi_hal.h index c984f9dc19..1b7a80dbf5 100644 --- a/components/hal/include/hal/spi_hal.h +++ b/components/hal/include/hal/spi_hal.h @@ -73,6 +73,8 @@ typedef struct { spi_ll_clock_val_t clock_reg; ///< Register value used by the LL layer spi_clock_source_t clock_source; ///< Clock source of each device used by LL layer uint32_t source_pre_div; ///< Pre divider before enter SPI peripheral + uint32_t source_real_freq; ///< source freq after pre-divider, this will enter peripheral + int expect_freq; ///< User expected desired freq config int real_freq; ///< Output of the actual frequency int timing_dummy; ///< Extra dummy needed to compensate the timing int timing_miso_delay; ///< Extra miso delay clocks to compensate the timing diff --git a/components/hal/spi_hal.c b/components/hal/spi_hal.c index 64e5c0ae7c..c1865ab3af 100644 --- a/components/hal/spi_hal.c +++ b/components/hal/spi_hal.c @@ -7,21 +7,9 @@ // The HAL layer for SPI (common part) #include "hal/spi_hal.h" -#include "hal/log.h" -#include "hal/assert.h" -#include "hal/gpio_ll.h" //for GPIO_LL_MATRIX_DELAY_NS #include "soc/soc_caps.h" #include "soc/clk_tree_defs.h" -/* The tag may be unused if log level is set to NONE */ -static const __attribute__((unused)) 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, uint32_t host_id) { memset(hal, 0, sizeof(spi_hal_context_t)); @@ -77,90 +65,7 @@ void spi_hal_sct_deinit(spi_hal_context_t *hal) } #endif //#if SOC_SPI_SCT_SUPPORTED -esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, spi_hal_timing_conf_t *timing_conf) -{ - spi_hal_timing_conf_t temp_conf = {}; - - int eff_clk_n = spi_ll_master_cal_clock(timing_param->clk_src_hz, timing_param->expected_freq, 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(timing_param->clk_src_hz, eff_clk_n, timing_param->use_gpio, timing_param->input_delay_ns, &temp_conf.timing_dummy, &temp_conf.timing_miso_delay); - -#if SPI_LL_SUPPORT_TIME_TUNING - const int freq_limit = spi_hal_get_freq_limit(timing_param->use_gpio, timing_param->input_delay_ns); - - 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\ -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); -#endif - - temp_conf.real_freq = eff_clk_n; - if (timing_conf) { - *timing_conf = temp_conf; - } - 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 source_freq_hz, int eff_clk, bool gpio_is_used, int input_delay_ns, int *dummy_n, int *miso_delay_n) -{ - const int apbclk_kHz = source_freq_hz / 1000; - //how many apb clocks a period has - const int spiclk_apb_n = source_freq_hz / eff_clk; - int gpio_delay_ns = 0; -#if GPIO_LL_MATRIX_DELAY_NS - gpio_delay_ns = gpio_is_used ? GPIO_LL_MATRIX_DELAY_NS : 0; -#endif - - //how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. - int delay_apb_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000; - if (delay_apb_n < 0) { - delay_apb_n = 0; - } - - int dummy_required = delay_apb_n / spiclk_apb_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) * spiclk_apb_n - delay_apb_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 (delay_apb_n * 4 <= spiclk_apb_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 / (delay_apb_n + 1), delay_apb_n, dummy_required, miso_delay); -} - -#if SPI_LL_SUPPORT_TIME_TUNING -//TODO: IDF-6578 -int spi_hal_get_freq_limit(bool gpio_is_used, int input_delay_ns) -{ - const int apbclk_kHz = APB_CLK_FREQ / 1000; - int gpio_delay_ns = 0; -#if GPIO_LL_MATRIX_DELAY_NS - gpio_delay_ns = gpio_is_used ? GPIO_LL_MATRIX_DELAY_NS : 0; -#endif - - //how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. - int delay_apb_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000; - if (delay_apb_n < 0) { - delay_apb_n = 0; - } - - return APB_CLK_FREQ / (delay_apb_n + 1); -} -#endif diff --git a/components/hal/spi_hal_iram.c b/components/hal/spi_hal_iram.c index 90d0f072d0..1b38660a58 100644 --- a/components/hal/spi_hal_iram.c +++ b/components/hal/spi_hal_iram.c @@ -8,10 +8,15 @@ // make these functions in a separate file to make sure all LL functions are in the IRAM. #include "hal/spi_hal.h" +#include "hal/log.h" #include "hal/assert.h" +#include "hal/gpio_ll.h" //for GPIO_LL_MATRIX_DELAY_NS #include "soc/ext_mem_defs.h" #include "soc/soc_caps.h" +/* The tag may be unused if log level is set to NONE */ +static __attribute__((unused)) char SPI_HAL_TAG[] = "spi_hal"; + void spi_hal_setup_device(spi_hal_context_t *hal, const spi_hal_dev_config_t *dev) { //Configure clock settings @@ -35,6 +40,90 @@ void spi_hal_setup_device(spi_hal_context_t *hal, const spi_hal_dev_config_t *de spi_ll_master_select_cs(hw, dev->cs_pin_id); } +esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, spi_hal_timing_conf_t *timing_conf) +{ + spi_ll_clock_val_t reg_val; + int dummy, miso_delay; + int eff_clk_n = spi_ll_master_cal_clock(timing_param->clk_src_hz, timing_param->expected_freq, timing_param->duty_cycle, ®_val); + + //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(timing_param->clk_src_hz, eff_clk_n, timing_param->use_gpio, timing_param->input_delay_ns, &dummy, &miso_delay); + +#if SPI_LL_SUPPORT_TIME_TUNING + const int freq_limit = spi_hal_get_freq_limit(timing_param->use_gpio, timing_param->input_delay_ns); + + if (!(timing_param->half_duplex || dummy == 0 || timing_param->no_compensate)) { + // This only a short log used as a "key" of the idf hint system, see `hints.yml` + HAL_EARLY_LOGE(SPI_HAL_TAG,"The clock_speed_hz should less than %d", freq_limit); + return ESP_ERR_NOT_SUPPORTED; + } +#endif + + if (timing_conf) { + timing_conf->clock_reg = reg_val; + timing_conf->expect_freq = timing_param->expected_freq; + timing_conf->real_freq = eff_clk_n; + timing_conf->timing_dummy = dummy; + timing_conf->timing_miso_delay = miso_delay; + } + return ESP_OK; +} + +void spi_hal_cal_timing(int source_freq_hz, int eff_clk, bool gpio_is_used, int input_delay_ns, int *dummy_n, int *miso_delay_n) +{ + const int apbclk_kHz = source_freq_hz / 1000; + //how many apb clocks a period has + const int spiclk_apb_n = source_freq_hz / eff_clk; + int gpio_delay_ns = 0; +#if GPIO_LL_MATRIX_DELAY_NS + gpio_delay_ns = gpio_is_used ? GPIO_LL_MATRIX_DELAY_NS : 0; +#endif + + //how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. + int delay_apb_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000; + if (delay_apb_n < 0) { + delay_apb_n = 0; + } + + int dummy_required = delay_apb_n / spiclk_apb_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) * spiclk_apb_n - delay_apb_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 (delay_apb_n * 4 <= spiclk_apb_n) { + miso_delay = -1; + } + } + *dummy_n = dummy_required; + *miso_delay_n = miso_delay; + HAL_EARLY_LOGD(SPI_HAL_TAG, "eff: %d, limit: %dk(/%d), %d dummy, %d delay", eff_clk / 1000, apbclk_kHz / (delay_apb_n + 1), delay_apb_n, dummy_required, miso_delay); +} + +#if SPI_LL_SUPPORT_TIME_TUNING +//TODO: IDF-6578 +int spi_hal_get_freq_limit(bool gpio_is_used, int input_delay_ns) +{ + const int apbclk_kHz = APB_CLK_FREQ / 1000; + int gpio_delay_ns = 0; +#if GPIO_LL_MATRIX_DELAY_NS + gpio_delay_ns = gpio_is_used ? GPIO_LL_MATRIX_DELAY_NS : 0; +#endif + + //how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off. + int delay_apb_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000; + if (delay_apb_n < 0) { + delay_apb_n = 0; + } + + return APB_CLK_FREQ / (delay_apb_n + 1); +} +#endif + 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; diff --git a/docs/en/api-reference/peripherals/spi_master.rst b/docs/en/api-reference/peripherals/spi_master.rst index 09da799736..dcaafa32b9 100644 --- a/docs/en/api-reference/peripherals/spi_master.rst +++ b/docs/en/api-reference/peripherals/spi_master.rst @@ -557,11 +557,13 @@ Note that these data are tested with :ref:`CONFIG_SPI_MASTER_ISR_IN_IRAM` enable SPI Clock Frequency ^^^^^^^^^^^^^^^^^^^ -The clock source of the GPSPI peripherals can be selected by setting :cpp:member:`spi_device_handle_t::cfg::clock_source`. You can refer to :cpp:type:`spi_clock_source_t` to know the supported clock sources. +The clock source of the GPSPI peripherals can be selected by setting :cpp:member:`spi_device_interface_config_t::clock_source`. You can refer to :cpp:type:`spi_clock_source_t` to know the supported clock sources. -By default driver sets :cpp:member:`spi_device_handle_t::cfg::clock_source` to ``SPI_CLK_SRC_DEFAULT``. This usually stands for the highest frequency among GPSPI clock sources. Its value is different among chips. +By default driver sets clock source to ``SPI_CLK_SRC_DEFAULT``. This usually stands for the highest frequency among GPSPI supported clock sources. Its value is different among chips. -The actual clock frequency of a Device may not be exactly equal to the number you set, it is re-calculated by the driver to the nearest hardware-compatible number, and not larger than the clock frequency of the clock source. You can call :cpp:func:`spi_device_get_actual_freq` to know the actual frequency computed by the driver. +The actual clock frequency of a device may not be exactly equal to the number you set, it is re-calculated by the driver to the nearest hardware-compatible number, and no more than the frequency of selected clock source. You can call :cpp:func:`spi_device_get_actual_freq` to know the actual frequency computed by the driver. + +The clock frequency of the device can be changed during transmission by setting :cpp:member:`spi_transaction_t::override_freq_hz`. This operation will use new clock frequency for the device's current and later transmissions. If the expected clock frequency cannot be achieved, the driver will print an warning and continue to use the previous clock frequency for transmission. The theoretical maximum transfer speed of the Write or Read phase can be calculated according to the table below: diff --git a/docs/zh_CN/api-reference/peripherals/spi_master.rst b/docs/zh_CN/api-reference/peripherals/spi_master.rst index e647f015bb..facc27c9f5 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_master.rst +++ b/docs/zh_CN/api-reference/peripherals/spi_master.rst @@ -557,12 +557,14 @@ GPIO 矩阵与 IO_MUX 管脚 SPI 时钟频率 ^^^^^^^^^^^^^^^^^^^ -GPSPI 外设的时钟源可以通过设置 :cpp:member:`spi_device_handle_t::cfg::clock_source` 选择,可用的时钟源请参阅 :cpp:type:`spi_clock_source_t`。 +GPSPI 外设的时钟源可以通过设置 :cpp:member:`spi_device_interface_config_t::clock_source` 选择,可用的时钟源请参阅 :cpp:type:`spi_clock_source_t`。 -默认情况下,驱动程序将把 :cpp:member:`spi_device_handle_t::cfg::clock_source` 设置为 ``SPI_CLK_SRC_DEFAULT``。这往往代表 GPSPI 时钟源中的最高频率,在不同的芯片中这一数值会有所不同。 +默认情况下,驱动程序将把时钟源设置为 ``SPI_CLK_SRC_DEFAULT``。这往往代表 GPSPI 可选时钟源中的最高频率,在不同的芯片上这一数值会有所不同。 设备的实际时钟频率可能不完全等于所设置的数字,驱动会将其重新计算为与硬件兼容的最接近的数字,并且不超过时钟源的时钟频率。调用函数 :cpp:func:`spi_device_get_actual_freq` 以了解驱动计算的实际频率。 +设备的时钟频率可在传输过程中实时更改,可以通过设置 :cpp:member:`spi_transaction_t::override_freq_hz` 实现,此操作将为该设备的该次及以后的传输使用新的时钟频率。若某次期望设置的时钟频率无法实现,驱动将打印警告并继续使用之前的时钟频率进行传输。 + 写入或读取阶段的理论最大传输速度可根据下表计算: .. only:: not SOC_SPI_SUPPORT_OCT diff --git a/tools/idf_py_actions/hints.yml b/tools/idf_py_actions/hints.yml index 46b415e566..dd0ae1d8df 100644 --- a/tools/idf_py_actions/hints.yml +++ b/tools/idf_py_actions/hints.yml @@ -465,3 +465,7 @@ re: "undefined reference to `(socketpair|gai_strerror|gethostname|getnameinfo|pipe|getifaddrs|freeifaddrs)'" hint: "{}() is not supported in IDF.\nTo use a simplified implementation of this function, add a dependency to sock_utils library 'idf.py add-dependency espressif/sock_utils'" match_to_output: True + +- + re: "spi_hal: The clock_speed_hz should less than" + hint: "When operating in full-duplex mode at high frequencies, the device may not read data correctly.\nTry using IOMUX pins to increase the frequency limit or switch to half-duplex mode.\nNote that the SPI master can only operate at divisors of 80 MHz, and the driver always selects the closest available frequency to your configuration.\nSpecify SPI_DEVICE_NO_DUMMY to bypass this check. This allows higher output speeds but may result in unreliable data reads."