// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "esp32-hal-spi.h" #if SOC_GPSPI_SUPPORTED #include "esp32-hal.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "esp_attr.h" #include "soc/spi_reg.h" #include "soc/spi_struct.h" #include "soc/periph_defs.h" #include "soc/io_mux_reg.h" #include "soc/gpio_sig_map.h" #include "soc/rtc.h" #if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C61) #include "hal/clk_gate_ll.h" #endif #include "esp32-hal-periman.h" #include "esp_private/periph_ctrl.h" #include "esp_system.h" #include "esp_intr_alloc.h" #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "soc/dport_reg.h" #include "esp32/rom/ets_sys.h" #include "esp32/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32S2 #include "soc/dport_reg.h" #include "esp32s2/rom/ets_sys.h" #include "esp32s2/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32S3 #include "soc/dport_reg.h" #include "esp32s3/rom/ets_sys.h" #include "esp32s3/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32C2 #include "esp32c2/rom/ets_sys.h" #include "esp32c2/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32C3 #include "esp32c3/rom/ets_sys.h" #include "esp32c3/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32C6 #include "esp32c6/rom/ets_sys.h" #include "esp32c6/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32H2 #include "esp32h2/rom/ets_sys.h" #include "esp32h2/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32P4 #include "esp32p4/rom/ets_sys.h" #include "esp32p4/rom/gpio.h" #include "hal/spi_ll.h" #include "hal/clk_tree_ll.h" // ESP32P4 SPI clock source frequencies #define SPI_P4_SPLL_FREQ_HZ (CLK_LL_PLL_480M_FREQ_MHZ * MHZ) // System PLL base frequency (480 MHz) #define SPI_P4_MAX_FREQ_HZ 80000000 // SPI peripheral maximum frequency (80 MHz) #elif CONFIG_IDF_TARGET_ESP32C5 #include "esp32c5/rom/ets_sys.h" #include "esp32c5/rom/gpio.h" #elif CONFIG_IDF_TARGET_ESP32C61 #include "esp32c61/rom/ets_sys.h" #include "esp32c61/rom/gpio.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif struct spi_struct_t { volatile spi_dev_t *dev; #if !CONFIG_DISABLE_HAL_LOCKS SemaphoreHandle_t lock; #endif uint8_t num; int8_t sck; int8_t miso; int8_t mosi; int8_t ss; bool ss_invert; #if defined(CONFIG_IDF_TARGET_ESP32P4) uint8_t clk_src; // Clock source: 0=XTAL, 1=SPLL uint32_t last_clock_div; // Last clock divider calculated uint8_t last_clk_src; // Last clock source selected (0=XTAL, 1=SPLL) #endif }; #if CONFIG_IDF_TARGET_ESP32S2 // ESP32S2 #define SPI_COUNT (2) #define SPI_CLK_IDX(p) ((p == 0) ? FSPICLK_OUT_MUX_IDX : ((p == 1) ? SPI3_CLK_OUT_MUX_IDX : 0)) #define SPI_MISO_IDX(p) ((p == 0) ? FSPIQ_OUT_IDX : ((p == 1) ? SPI3_Q_OUT_IDX : 0)) #define SPI_MOSI_IDX(p) ((p == 0) ? FSPID_IN_IDX : ((p == 1) ? SPI3_D_IN_IDX : 0)) #define SPI_HSPI_SS_IDX(n) ((n == 0) ? SPI3_CS0_OUT_IDX : ((n == 1) ? SPI3_CS1_OUT_IDX : ((n == 2) ? SPI3_CS2_OUT_IDX : 0))) #define SPI_FSPI_SS_IDX(n) ((n == 0) ? FSPICS0_OUT_IDX : ((n == 1) ? FSPICS1_OUT_IDX : ((n == 2) ? FSPICS2_OUT_IDX : 0))) #define SPI_SS_IDX(p, n) ((p == 0) ? SPI_FSPI_SS_IDX(n) : ((p == 1) ? SPI_HSPI_SS_IDX(n) : 0)) #elif CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 #define SPI_COUNT (2) #define SPI_CLK_IDX(p) ((p == 0) ? FSPICLK_OUT_IDX : ((p == 1) ? SPI3_CLK_OUT_IDX : 0)) #define SPI_MISO_IDX(p) ((p == 0) ? FSPIQ_OUT_IDX : ((p == 1) ? SPI3_Q_OUT_IDX : 0)) #define SPI_MOSI_IDX(p) ((p == 0) ? FSPID_IN_IDX : ((p == 1) ? SPI3_D_IN_IDX : 0)) #define SPI_HSPI_SS_IDX(n) ((n == 0) ? SPI3_CS0_OUT_IDX : ((n == 1) ? SPI3_CS1_OUT_IDX : ((n == 2) ? SPI3_CS2_OUT_IDX : 0))) #define SPI_FSPI_SS_IDX(n) ((n == 0) ? FSPICS0_OUT_IDX : ((n == 1) ? FSPICS1_OUT_IDX : ((n == 2) ? FSPICS2_OUT_IDX : 0))) #define SPI_SS_IDX(p, n) ((p == 0) ? SPI_FSPI_SS_IDX(n) : ((p == 1) ? SPI_HSPI_SS_IDX(n) : 0)) #elif CONFIG_IDF_TARGET_ESP32P4 // ESP32P4 #define SPI_COUNT (2) // SPI2 and SPI3. SPI0 and SPI1 are reserved for flash and PSRAM #define SPI_CLK_IDX(p) ((p == 0) ? SPI2_CK_PAD_OUT_IDX : ((p == 1) ? SPI3_CK_PAD_OUT_IDX : 0)) #define SPI_MISO_IDX(p) ((p == 0) ? SPI2_Q_PAD_OUT_IDX : ((p == 1) ? SPI3_QO_PAD_OUT_IDX : 0)) #define SPI_MOSI_IDX(p) ((p == 0) ? SPI2_D_PAD_IN_IDX : ((p == 1) ? SPI3_D_PAD_IN_IDX : 0)) #define SPI_HSPI_SS_IDX(n) ((n == 0) ? SPI3_CS_PAD_OUT_IDX : ((n == 1) ? SPI3_CS1_PAD_OUT_IDX : ((n == 2) ? SPI3_CS2_PAD_OUT_IDX : 0))) #define SPI_FSPI_SS_IDX(n) \ ((n == 0) ? SPI2_CS_PAD_OUT_IDX \ : ((n == 1) ? SPI2_CS1_PAD_OUT_IDX \ : ((n == 2) ? SPI2_CS2_PAD_OUT_IDX \ : ((n == 3) ? SPI2_CS3_PAD_OUT_IDX : ((n == 4) ? SPI2_CS4_PAD_OUT_IDX : ((n == 5) ? SPI2_CS5_PAD_OUT_IDX : 0)))))) #define SPI_SS_IDX(p, n) ((p == 0) ? SPI_FSPI_SS_IDX(n) : ((p == 1) ? SPI_HSPI_SS_IDX(n) : 0)) #elif CONFIG_IDF_TARGET_ESP32 // ESP32 #define SPI_COUNT (4) #define SPI_CLK_IDX(p) ((p == 0) ? SPICLK_OUT_IDX : ((p == 1) ? SPICLK_OUT_IDX : ((p == 2) ? HSPICLK_OUT_IDX : ((p == 3) ? VSPICLK_OUT_IDX : 0)))) #define SPI_MISO_IDX(p) ((p == 0) ? SPIQ_OUT_IDX : ((p == 1) ? SPIQ_OUT_IDX : ((p == 2) ? HSPIQ_OUT_IDX : ((p == 3) ? VSPIQ_OUT_IDX : 0)))) #define SPI_MOSI_IDX(p) ((p == 0) ? SPID_IN_IDX : ((p == 1) ? SPID_IN_IDX : ((p == 2) ? HSPID_IN_IDX : ((p == 3) ? VSPID_IN_IDX : 0)))) #define SPI_SPI_SS_IDX(n) ((n == 0) ? SPICS0_OUT_IDX : ((n == 1) ? SPICS1_OUT_IDX : ((n == 2) ? SPICS2_OUT_IDX : SPICS0_OUT_IDX))) #define SPI_HSPI_SS_IDX(n) ((n == 0) ? HSPICS0_OUT_IDX : ((n == 1) ? HSPICS1_OUT_IDX : ((n == 2) ? HSPICS2_OUT_IDX : HSPICS0_OUT_IDX))) #define SPI_VSPI_SS_IDX(n) ((n == 0) ? VSPICS0_OUT_IDX : ((n == 1) ? VSPICS1_OUT_IDX : ((n == 2) ? VSPICS2_OUT_IDX : VSPICS0_OUT_IDX))) #define SPI_SS_IDX(p, n) ((p == 0) ? SPI_SPI_SS_IDX(n) : ((p == 1) ? SPI_SPI_SS_IDX(n) : ((p == 2) ? SPI_HSPI_SS_IDX(n) : ((p == 3) ? SPI_VSPI_SS_IDX(n) : 0)))) #else // ESP32C2, C3, C5, C6, C61, H2 #define SPI_COUNT (1) #define SPI_CLK_IDX(p) FSPICLK_OUT_IDX #define SPI_MISO_IDX(p) FSPIQ_OUT_IDX #define SPI_MOSI_IDX(p) FSPID_IN_IDX #define SPI_SPI_SS_IDX(n) ((n == 0) ? FSPICS0_OUT_IDX : ((n == 1) ? FSPICS1_OUT_IDX : ((n == 2) ? FSPICS2_OUT_IDX : FSPICS0_OUT_IDX))) #define SPI_SS_IDX(p, n) SPI_SPI_SS_IDX(n) #endif #if CONFIG_DISABLE_HAL_LOCKS #define SPI_MUTEX_LOCK() #define SPI_MUTEX_UNLOCK() // clang-format off static spi_t _spi_bus_array[] = { #if CONFIG_IDF_TARGET_ESP32S2 ||CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4 {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 1, -1, -1, -1, -1, false} #elif CONFIG_IDF_TARGET_ESP32 {(volatile spi_dev_t *)(DR_REG_SPI0_BASE), 0, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), 1, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 2, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 3, -1, -1, -1, -1, false} #else // ESP32C2, C3, C5, C6, C61, H2 {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1, false} #endif }; // clang-format on #else #define SPI_MUTEX_LOCK() \ do { \ } while (xSemaphoreTake(spi->lock, portMAX_DELAY) != pdPASS) #define SPI_MUTEX_UNLOCK() xSemaphoreGive(spi->lock) // clang-format off static spi_t _spi_bus_array[] = { #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4 {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 1, -1, -1, -1, -1, false} #elif CONFIG_IDF_TARGET_ESP32 {(volatile spi_dev_t *)(DR_REG_SPI0_BASE), NULL, 0, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), NULL, 1, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 2, -1, -1, -1, -1, false}, {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 3, -1, -1, -1, -1, false} #else // ESP32C2, C3, C5, C6, C61, H2 {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1, false} #endif }; // clang-format on #endif static bool spiDetachBus(void *bus) { uint8_t spi_num = (int)bus - 1; spi_t *spi = &_spi_bus_array[spi_num]; if (spi->dev->clock.val == 0) { log_d("SPI bus already stopped"); return true; } else if (spi->sck == -1 || (spi->miso == -1 && spi->mosi == -1)) { log_d("Stopping SPI bus"); spiStopBus(spi); spiDetachSCK(spi); spiDetachMISO(spi); spiDetachMOSI(spi); spiDetachSS(spi); spi = NULL; return true; } return true; } static bool spiDetachBus_SCK(void *bus) { uint8_t spi_num = (int)bus - 1; spi_t *spi = &_spi_bus_array[spi_num]; if (spi->sck != -1) { spiDetachSCK(spi); spiDetachBus(bus); } return true; } static bool spiDetachBus_MISO(void *bus) { uint8_t spi_num = (int)bus - 1; spi_t *spi = &_spi_bus_array[spi_num]; if (spi->miso != -1) { spiDetachMISO(spi); spiDetachBus(bus); } return true; } static bool spiDetachBus_MOSI(void *bus) { uint8_t spi_num = (int)bus - 1; spi_t *spi = &_spi_bus_array[spi_num]; if (spi->mosi != -1) { spiDetachMOSI(spi); spiDetachBus(bus); } return true; } static bool spiDetachBus_SS(void *bus) { uint8_t spi_num = (int)bus - 1; spi_t *spi = &_spi_bus_array[spi_num]; if (spi->ss != -1) { spiDetachSS(spi); spiDetachBus(bus); } return true; } bool spiAttachSCK(spi_t *spi, int8_t sck) { if (!spi || sck < 0) { return false; } void *bus = perimanGetPinBus(sck, ESP32_BUS_TYPE_SPI_MASTER_SCK); if (bus != NULL && !perimanClearPinBus(sck)) { return false; } pinMode(sck, OUTPUT); pinMatrixOutAttach(sck, SPI_CLK_IDX(spi->num), false, false); spi->sck = sck; if (!perimanSetPinBus(sck, ESP32_BUS_TYPE_SPI_MASTER_SCK, (void *)(spi->num + 1), spi->num, -1)) { spiDetachBus_SCK((void *)(spi->num + 1)); log_e("Failed to set pin bus to SPI for pin %d", sck); return false; } return true; } bool spiAttachMISO(spi_t *spi, int8_t miso) { if (!spi || miso < 0) { return false; } void *bus = perimanGetPinBus(miso, ESP32_BUS_TYPE_SPI_MASTER_MISO); if (bus != NULL && !perimanClearPinBus(miso)) { return false; } pinMode(miso, INPUT); pinMatrixInAttach(miso, SPI_MISO_IDX(spi->num), false); spi->miso = miso; if (!perimanSetPinBus(miso, ESP32_BUS_TYPE_SPI_MASTER_MISO, (void *)(spi->num + 1), spi->num, -1)) { spiDetachBus_MISO((void *)(spi->num + 1)); log_e("Failed to set pin bus to SPI for pin %d", miso); return false; } return true; } bool spiAttachMOSI(spi_t *spi, int8_t mosi) { if (!spi || mosi < 0) { return false; } void *bus = perimanGetPinBus(mosi, ESP32_BUS_TYPE_SPI_MASTER_MOSI); if (bus != NULL && !perimanClearPinBus(mosi)) { return false; } pinMode(mosi, OUTPUT); pinMatrixOutAttach(mosi, SPI_MOSI_IDX(spi->num), false, false); spi->mosi = mosi; if (!perimanSetPinBus(mosi, ESP32_BUS_TYPE_SPI_MASTER_MOSI, (void *)(spi->num + 1), spi->num, -1)) { spiDetachBus_MOSI((void *)(spi->num + 1)); log_e("Failed to set pin bus to SPI for pin %d", mosi); return false; } return true; } bool spiDetachSCK(spi_t *spi) { if (!spi) { return false; } int8_t sck = spi->sck; if (sck != -1) { pinMatrixOutDetach(sck, false, false); spi->sck = -1; perimanClearPinBus(sck); } return true; } bool spiDetachMISO(spi_t *spi) { if (!spi) { return false; } int8_t miso = spi->miso; if (miso != -1) { pinMatrixInDetach(SPI_MISO_IDX(spi->num), false, false); spi->miso = -1; perimanClearPinBus(miso); } return true; } bool spiDetachMOSI(spi_t *spi) { if (!spi) { return false; } int8_t mosi = spi->mosi; if (mosi != -1) { pinMatrixOutDetach(mosi, false, false); spi->mosi = -1; perimanClearPinBus(mosi); } return true; } bool spiAttachSS(spi_t *spi, uint8_t ss_num, int8_t ss) { if (!spi || ss < 0 || ss_num > 2) { return false; } void *bus = perimanGetPinBus(ss, ESP32_BUS_TYPE_SPI_MASTER_SS); if (bus != NULL && !perimanClearPinBus(ss)) { return false; } pinMode(ss, OUTPUT); pinMatrixOutAttach(ss, SPI_SS_IDX(spi->num, ss_num), spi->ss_invert, false); spiEnableSSPins(spi, (1 << ss_num)); spi->ss = ss; if (!perimanSetPinBus(ss, ESP32_BUS_TYPE_SPI_MASTER_SS, (void *)(spi->num + 1), spi->num, -1)) { spiDetachBus_SS((void *)(spi->num + 1)); log_e("Failed to set pin bus to SPI for pin %d", ss); return false; } return true; } bool spiDetachSS(spi_t *spi) { if (!spi) { return false; } int8_t ss = spi->ss; if (ss != -1) { pinMatrixOutDetach(ss, false, false); spi->ss = -1; perimanClearPinBus(ss); } return true; } void spiEnableSSPins(spi_t *spi, uint8_t ss_mask) { if (!spi) { return; } SPI_MUTEX_LOCK(); #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.val &= ~(ss_mask & SPI_SS_MASK_ALL); #else spi->dev->misc.val &= ~(ss_mask & SPI_SS_MASK_ALL); #endif SPI_MUTEX_UNLOCK(); } void spiDisableSSPins(spi_t *spi, uint8_t ss_mask) { if (!spi) { return; } SPI_MUTEX_LOCK(); #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.val |= (ss_mask & SPI_SS_MASK_ALL); #else spi->dev->misc.val |= (ss_mask & SPI_SS_MASK_ALL); #endif SPI_MUTEX_UNLOCK(); } void spiSSEnable(spi_t *spi) { if (!spi) { return; } SPI_MUTEX_LOCK(); spi->dev->user.cs_setup = 1; spi->dev->user.cs_hold = 1; SPI_MUTEX_UNLOCK(); } void spiSSDisable(spi_t *spi) { if (!spi) { return; } SPI_MUTEX_LOCK(); spi->dev->user.cs_setup = 0; spi->dev->user.cs_hold = 0; SPI_MUTEX_UNLOCK(); } void spiSSInvert(spi_t *spi, bool invert) { if (spi) { spi->ss_invert = invert; } } void spiSSSet(spi_t *spi) { if (!spi) { return; } SPI_MUTEX_LOCK(); #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.cs_keep_active = 1; #else spi->dev->misc.cs_keep_active = 1; #endif SPI_MUTEX_UNLOCK(); } void spiSSClear(spi_t *spi) { if (!spi) { return; } SPI_MUTEX_LOCK(); #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.cs_keep_active = 0; #else spi->dev->misc.cs_keep_active = 0; #endif SPI_MUTEX_UNLOCK(); } uint32_t spiGetClockDiv(spi_t *spi) { if (!spi) { return 0; } return spi->dev->clock.val; } #if defined(CONFIG_IDF_TARGET_ESP32P4) /** * @brief Calculate SPI frequency from divider value and source frequency * * @param divider The clock divider value (must be > 0) * @param source_freq The source clock frequency in Hz (e.g., 40MHz for XTAL, 480MHz for SPLL) * @return uint32_t The calculated SPI clock frequency in Hz, or 0 if divider is 0 * * @note ESP32P4-specific helper function. Calculates: frequency = source_freq / divider */ static inline uint32_t _dividerToFreq(uint32_t divider, uint32_t source_freq) { if (divider == 0) { return 0; // Safety check } return source_freq / divider; } /** * @brief Extract the divider value from a clockDiv register value * * @param clockDiv The SPI clock divider register value * @return uint32_t The calculated divider: (clkdiv_pre + 1) * (clkcnt_n + 1) * * @note ESP32P4-specific helper function. Extracts clkcnt_n (bits 12-17) and * clkdiv_pre (bits 18-21) from the register value using bit shifts. * For SPI_CLK_EQU_SYSCLK (0x80000000), this naturally returns 1 (no division). */ static inline uint32_t _clockDivToDivider(uint32_t clockDiv) { uint32_t clkcnt_n = (clockDiv >> 12) & 0x3F; uint32_t clkdiv_pre = (clockDiv >> 18) & 0xF; return ((clkdiv_pre + 1) * (clkcnt_n + 1)); } #endif /** * @brief Internal function to set SPI clock divider and handle ESP32P4 clock source switching * * @param spi Pointer to SPI bus structure * @param clockDiv The clock divider register value to set * * @note This function does NOT acquire the SPI mutex - it must be called from within * a context that already holds the mutex. * * @note Callers (all properly acquire mutex before calling): * - spiSetClockDiv() - acquires mutex via SPI_MUTEX_LOCK() before calling * - spiTransaction() - acquires mutex via SPI_MUTEX_LOCK() before calling * - _on_apb_change() - acquires mutex via SPI_MUTEX_LOCK() before calling (APB_AFTER_CHANGE case) * * @note ESP32P4-specific behavior: * - Determines the appropriate clock source (XTAL or SPLL) based on the divider value * - Uses stored per-instance clock source information if available (from last calculation) * - Otherwise infers clock source by checking which gives a valid frequency: * * XTAL is capped at 40MHz, so if calculated frequency > 40MHz, must use SPLL * * For <= 40MHz frequencies, prefers XTAL if valid * - Switches clock source if needed (with proper clock gating and delay) * - Updates per-instance tracking variables (last_clock_div, last_clk_src) */ static void _spiSetClockDivInternal(spi_t *spi, uint32_t clockDiv) { if (!spi) { return; } #if defined(CONFIG_IDF_TARGET_ESP32P4) // ESP32P4: Determine clock source from divider // The divider was calculated by spiFrequencyToClockDiv() which picks the best match // We store which source was selected per SPI instance, so use that if available. // Otherwise, infer from the divider by checking which gives a "more reasonable" frequency. uint32_t xtal_freq = getXtalFrequencyMhz() * 1000000; // Actual XTAL frequency (typically 40 MHz) uint32_t spll_freq = SPI_P4_SPLL_FREQ_HZ; uint8_t new_clk_src; if (clockDiv == spi->last_clock_div && spi->last_clock_div != 0) { new_clk_src = spi->last_clk_src; } else { uint32_t divider = _clockDivToDivider(clockDiv); uint32_t freq_with_xtal = _dividerToFreq(divider, xtal_freq); uint32_t freq_with_spll = _dividerToFreq(divider, spll_freq); // Infer: Prefer XTAL whenever it yields a valid <= 40MHz SPI clock, // and fall back to SPLL only when XTAL cannot produce a valid frequency. if (freq_with_xtal > 0 && freq_with_xtal <= xtal_freq) { new_clk_src = 0; // XTAL } else if (freq_with_spll > 0) { new_clk_src = 1; // SPLL } else { // Both inferred frequencies are invalid; keep current source to avoid unnecessary switching. new_clk_src = spi->clk_src; } } // Store the divider and source for this SPI instance spi->last_clock_div = clockDiv; spi->last_clk_src = new_clk_src; if (spi->clk_src != new_clk_src) { // Determine SPI host ID once to avoid duplicate conditionals int host = (spi->num == FSPI) ? SPI2_HOST : SPI3_HOST; PERIPH_RCC_ATOMIC() { spi_ll_enable_clock(host, false); spi_ll_set_clk_source(spi->dev, new_clk_src ? SPI_CLK_SRC_SPLL : SPI_CLK_SRC_XTAL); spi_ll_enable_clock(host, true); } spi->clk_src = new_clk_src; ets_delay_us(10); } #endif spi->dev->clock.val = clockDiv; } void spiSetClockDiv(spi_t *spi, uint32_t clockDiv) { if (!spi) { return; } SPI_MUTEX_LOCK(); _spiSetClockDivInternal(spi, clockDiv); SPI_MUTEX_UNLOCK(); } uint8_t spiGetDataMode(spi_t *spi) { if (!spi) { return 0; } #if CONFIG_IDF_TARGET_ESP32 bool idleEdge = spi->dev->pin.ck_idle_edge; #else bool idleEdge = spi->dev->misc.ck_idle_edge; #endif bool outEdge = spi->dev->user.ck_out_edge; if (idleEdge) { if (outEdge) { return SPI_MODE2; } return SPI_MODE3; } if (outEdge) { return SPI_MODE1; } return SPI_MODE0; } void spiSetDataMode(spi_t *spi, uint8_t dataMode) { if (!spi) { return; } SPI_MUTEX_LOCK(); switch (dataMode) { case SPI_MODE1: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 0; #else spi->dev->misc.ck_idle_edge = 0; #endif spi->dev->user.ck_out_edge = 1; break; case SPI_MODE2: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 1; #else spi->dev->misc.ck_idle_edge = 1; #endif spi->dev->user.ck_out_edge = 1; break; case SPI_MODE3: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 1; #else spi->dev->misc.ck_idle_edge = 1; #endif spi->dev->user.ck_out_edge = 0; break; case SPI_MODE0: default: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 0; #else spi->dev->misc.ck_idle_edge = 0; #endif spi->dev->user.ck_out_edge = 0; break; } SPI_MUTEX_UNLOCK(); } uint8_t spiGetBitOrder(spi_t *spi) { if (!spi) { return 0; } return (spi->dev->ctrl.wr_bit_order | spi->dev->ctrl.rd_bit_order) == 0; } void spiSetBitOrder(spi_t *spi, uint8_t bitOrder) { if (!spi) { return; } SPI_MUTEX_LOCK(); if (SPI_MSBFIRST == bitOrder) { spi->dev->ctrl.wr_bit_order = 0; spi->dev->ctrl.rd_bit_order = 0; } else if (SPI_LSBFIRST == bitOrder) { spi->dev->ctrl.wr_bit_order = 1; spi->dev->ctrl.rd_bit_order = 1; } SPI_MUTEX_UNLOCK(); } static void _on_apb_change(void *arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb) { spi_t *spi = (spi_t *)arg; if (ev_type == APB_BEFORE_CHANGE) { SPI_MUTEX_LOCK(); while (spi->dev->cmd.usr); } else { #if defined(CONFIG_IDF_TARGET_ESP32P4) // ESP32P4: Use the stored clock source to determine base frequency uint32_t base_freq = (spi->clk_src == 1) ? SPI_P4_SPLL_FREQ_HZ : (getXtalFrequencyMhz() * 1000000); uint32_t current_freq = base_freq / ((spi->dev->clock.clkdiv_pre + 1) * (spi->dev->clock.clkcnt_n + 1)); uint32_t new_clockDiv = spiFrequencyToClockDiv(spi, current_freq); // Use _spiSetClockDivInternal to ensure clock source is updated if needed _spiSetClockDivInternal(spi, new_clockDiv); #else spi->dev->clock.val = spiFrequencyToClockDiv(spi, old_apb / ((spi->dev->clock.clkdiv_pre + 1) * (spi->dev->clock.clkcnt_n + 1))); #endif SPI_MUTEX_UNLOCK(); } } static void spiInitBus(spi_t *spi) { #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->slave.trans_done = 0; #endif spi->dev->slave.val = 0; #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.val = 0; #else spi->dev->misc.val = 0; #endif spi->dev->user.val = 0; spi->dev->user1.val = 0; spi->dev->ctrl.val = 0; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->ctrl1.val = 0; spi->dev->ctrl2.val = 0; #else spi->dev->clk_gate.val = 0; spi->dev->dma_conf.val = 0; spi->dev->dma_conf.rx_afifo_rst = 1; spi->dev->dma_conf.buf_afifo_rst = 1; #endif spi->dev->clock.val = 0; } void spiStopBus(spi_t *spi) { if (!spi) { return; } removeApbChangeCallback(spi, _on_apb_change); SPI_MUTEX_LOCK(); spiInitBus(spi); SPI_MUTEX_UNLOCK(); } spi_t *spiStartBus(uint8_t spi_num, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder) { if (spi_num >= SPI_COUNT) { log_e("SPI bus index %d is out of range", spi_num); return NULL; } perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_SCK, spiDetachBus_SCK); perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_MISO, spiDetachBus_MISO); perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_MOSI, spiDetachBus_MOSI); perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_SS, spiDetachBus_SS); spi_t *spi = &_spi_bus_array[spi_num]; #if !CONFIG_DISABLE_HAL_LOCKS if (spi->lock == NULL) { spi->lock = xSemaphoreCreateMutex(); if (spi->lock == NULL) { return NULL; } } #endif #if CONFIG_IDF_TARGET_ESP32S2 if (spi_num == FSPI) { DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST); } else if (spi_num == HSPI) { DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST); } #elif CONFIG_IDF_TARGET_ESP32S3 if (spi_num == FSPI) { periph_ll_reset(PERIPH_SPI2_MODULE); periph_ll_enable_clk_clear_rst(PERIPH_SPI2_MODULE); } else if (spi_num == HSPI) { periph_ll_reset(PERIPH_SPI3_MODULE); periph_ll_enable_clk_clear_rst(PERIPH_SPI3_MODULE); } #elif CONFIG_IDF_TARGET_ESP32 if (spi_num == HSPI) { DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST); } else if (spi_num == VSPI) { DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST); } else { DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI01_CLK_EN); DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI01_RST); } #elif CONFIG_IDF_TARGET_ESP32P4 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" if (spi_num == FSPI) { PERIPH_RCC_ACQUIRE_ATOMIC(PERIPH_GPSPI2_MODULE, ref_count) { if (ref_count == 0) { PERIPH_RCC_ATOMIC() { spi_ll_enable_bus_clock(SPI2_HOST, true); spi_ll_reset_register(SPI2_HOST); spi_ll_enable_clock(SPI2_HOST, true); } } } } else if (spi_num == HSPI) { PERIPH_RCC_ACQUIRE_ATOMIC(PERIPH_GPSPI3_MODULE, ref_count) { if (ref_count == 0) { PERIPH_RCC_ATOMIC() { spi_ll_enable_bus_clock(SPI3_HOST, true); spi_ll_reset_register(SPI3_HOST); spi_ll_enable_clock(SPI3_HOST, true); } } } } #pragma GCC diagnostic pop #elif defined(__PERIPH_CTRL_ALLOW_LEGACY_API) periph_ll_reset(PERIPH_SPI2_MODULE); periph_ll_enable_clk_clear_rst(PERIPH_SPI2_MODULE); #endif SPI_MUTEX_LOCK(); spiInitBus(spi); #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->clk_gate.clk_en = 1; spi->dev->clk_gate.mst_clk_sel = 1; spi->dev->clk_gate.mst_clk_active = 1; #if defined(CONFIG_IDF_TARGET_ESP32P4) // Initialize clock source to XTAL (will be changed to SPLL if needed) spi->clk_src = 0; // 0 = XTAL, 1 = SPLL spi->last_clock_div = 0; // Initialize per-instance storage spi->last_clk_src = 0; // Initialize per-instance storage #endif #if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) spi->dev->dma_conf.tx_seg_trans_clr_en = 1; spi->dev->dma_conf.rx_seg_trans_clr_en = 1; spi->dev->dma_conf.dma_seg_trans_en = 0; #endif #endif spi->dev->user.usr_mosi = 1; spi->dev->user.usr_miso = 1; spi->dev->user.doutdin = 1; int i; for (i = 0; i < 16; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = 0x00000000; #else spi->dev->data_buf[i].val = 0x00000000; #endif } SPI_MUTEX_UNLOCK(); spiSetDataMode(spi, dataMode); spiSetBitOrder(spi, bitOrder); spiSetClockDiv(spi, clockDiv); addApbChangeCallback(spi, _on_apb_change); return spi; } void spiWaitReady(spi_t *spi) { if (!spi) { return; } while (spi->dev->cmd.usr); } #if CONFIG_IDF_TARGET_ESP32S2 #define usr_mosi_dbitlen usr_mosi_bit_len #define usr_miso_dbitlen usr_miso_bit_len #elif !defined(CONFIG_IDF_TARGET_ESP32) #define usr_mosi_dbitlen ms_data_bitlen #define usr_miso_dbitlen ms_data_bitlen #define mosi_dlen ms_dlen #define miso_dlen ms_dlen #endif void spiWrite(spi_t *spi, const uint32_t *data, uint8_t len) { if (!spi) { return; } int i; if (len > 16) { len = 16; } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = (len * 32) - 1; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif for (i = 0; i < len; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = data[i]; #else spi->dev->data_buf[i].val = data[i]; #endif } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); SPI_MUTEX_UNLOCK(); } void spiTransfer(spi_t *spi, uint32_t *data, uint8_t len) { if (!spi) { return; } int i; if (len > 16) { len = 16; } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = (len * 32) - 1; spi->dev->miso_dlen.usr_miso_dbitlen = (len * 32) - 1; for (i = 0; i < len; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = data[i]; #else spi->dev->data_buf[i].val = data[i]; #endif } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); for (i = 0; i < len; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data[i] = spi->dev->data_buf[i]; #else data[i] = spi->dev->data_buf[i].val; #endif } SPI_MUTEX_UNLOCK(); } void spiWriteByte(spi_t *spi, uint8_t data) { if (!spi) { return; } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = 7; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); SPI_MUTEX_UNLOCK(); } uint8_t spiTransferByte(spi_t *spi, uint8_t data) { if (!spi) { return 0; } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = 7; spi->dev->miso_dlen.usr_miso_dbitlen = 7; #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0] & 0xFF; #else data = spi->dev->data_buf[0].val & 0xFF; #endif SPI_MUTEX_UNLOCK(); return data; } static uint32_t __spiTranslate32(uint32_t data) { union { uint32_t l; uint8_t b[4]; } out; out.l = data; return out.b[3] | (out.b[2] << 8) | (out.b[1] << 16) | (out.b[0] << 24); } void spiWriteWord(spi_t *spi, uint16_t data) { if (!spi) { return; } if (!spi->dev->ctrl.wr_bit_order) { data = (data >> 8) | (data << 8); } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = 15; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); SPI_MUTEX_UNLOCK(); } uint16_t spiTransferWord(spi_t *spi, uint16_t data) { if (!spi) { return 0; } if (!spi->dev->ctrl.wr_bit_order) { data = (data >> 8) | (data << 8); } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = 15; spi->dev->miso_dlen.usr_miso_dbitlen = 15; #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0]; #else data = spi->dev->data_buf[0].val; #endif SPI_MUTEX_UNLOCK(); if (!spi->dev->ctrl.rd_bit_order) { data = (data >> 8) | (data << 8); } return data; } void spiWriteLong(spi_t *spi, uint32_t data) { if (!spi) { return; } if (!spi->dev->ctrl.wr_bit_order) { data = __spiTranslate32(data); } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = 31; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); SPI_MUTEX_UNLOCK(); } uint32_t spiTransferLong(spi_t *spi, uint32_t data) { if (!spi) { return 0; } if (!spi->dev->ctrl.wr_bit_order) { data = __spiTranslate32(data); } SPI_MUTEX_LOCK(); spi->dev->mosi_dlen.usr_mosi_dbitlen = 31; spi->dev->miso_dlen.usr_miso_dbitlen = 31; #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0]; #else data = spi->dev->data_buf[0].val; #endif SPI_MUTEX_UNLOCK(); if (!spi->dev->ctrl.rd_bit_order) { data = __spiTranslate32(data); } return data; } static void __spiTransferBytes(spi_t *spi, const uint8_t *data, uint8_t *out, uint32_t bytes) { if (!spi) { return; } uint32_t i; if (bytes > 64) { bytes = 64; } uint32_t words = (bytes + 3) / 4; //16 max uint32_t wordsBuf[16] = { 0, }; uint8_t *bytesBuf = (uint8_t *)wordsBuf; if (data) { memcpy(bytesBuf, data, bytes); //copy data to buffer } else { memset(bytesBuf, 0xFF, bytes); } spi->dev->mosi_dlen.usr_mosi_dbitlen = ((bytes * 8) - 1); spi->dev->miso_dlen.usr_miso_dbitlen = ((bytes * 8) - 1); for (i = 0; i < words; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = wordsBuf[i]; //copy buffer to spi fifo #else spi->dev->data_buf[i].val = wordsBuf[i]; //copy buffer to spi fifo #endif } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); if (out) { for (i = 0; i < words; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 wordsBuf[i] = spi->dev->data_buf[i]; //copy spi fifo to buffer #else wordsBuf[i] = spi->dev->data_buf[i].val; //copy spi fifo to buffer #endif } memcpy(out, bytesBuf, bytes); //copy buffer to output } } void spiTransferBytes(spi_t *spi, const uint8_t *data, uint8_t *out, uint32_t size) { if (!spi) { return; } SPI_MUTEX_LOCK(); while (size) { if (size > 64) { __spiTransferBytes(spi, data, out, 64); size -= 64; if (data) { data += 64; } if (out) { out += 64; } } else { __spiTransferBytes(spi, data, out, size); size = 0; } } SPI_MUTEX_UNLOCK(); } void spiTransferBits(spi_t *spi, uint32_t data, uint32_t *out, uint8_t bits) { if (!spi) { return; } SPI_MUTEX_LOCK(); spiTransferBitsNL(spi, data, out, bits); SPI_MUTEX_UNLOCK(); } /* * Manual Lock Management * */ #define MSB_32_SET(var, val) \ { \ uint8_t *d = (uint8_t *)&(val); \ (var) = d[3] | (d[2] << 8) | (d[1] << 16) | (d[0] << 24); \ } #define MSB_24_SET(var, val) \ { \ uint8_t *d = (uint8_t *)&(val); \ (var) = d[2] | (d[1] << 8) | (d[0] << 16); \ } #define MSB_16_SET(var, val) \ { (var) = (((val) & 0xFF00) >> 8) | (((val) & 0xFF) << 8); } #define MSB_PIX_SET(var, val) \ { \ uint8_t *d = (uint8_t *)&(val); \ (var) = d[1] | (d[0] << 8) | (d[3] << 16) | (d[2] << 24); \ } void spiTransaction(spi_t *spi, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder) { if (!spi) { return; } SPI_MUTEX_LOCK(); // Set clock divider (handles ESP32P4 clock source selection if needed) _spiSetClockDivInternal(spi, clockDiv); switch (dataMode) { case SPI_MODE1: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 0; #else spi->dev->misc.ck_idle_edge = 0; #endif spi->dev->user.ck_out_edge = 1; break; case SPI_MODE2: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 1; #else spi->dev->misc.ck_idle_edge = 1; #endif spi->dev->user.ck_out_edge = 1; break; case SPI_MODE3: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 1; #else spi->dev->misc.ck_idle_edge = 1; #endif spi->dev->user.ck_out_edge = 0; break; case SPI_MODE0: default: #if CONFIG_IDF_TARGET_ESP32 spi->dev->pin.ck_idle_edge = 0; #else spi->dev->misc.ck_idle_edge = 0; #endif spi->dev->user.ck_out_edge = 0; break; } if (SPI_MSBFIRST == bitOrder) { spi->dev->ctrl.wr_bit_order = 0; spi->dev->ctrl.rd_bit_order = 0; } else if (SPI_LSBFIRST == bitOrder) { spi->dev->ctrl.wr_bit_order = 1; spi->dev->ctrl.rd_bit_order = 1; } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) // Sync new config with hardware, fixes https://github.com/espressif/arduino-esp32/issues/9221 spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif } void spiSimpleTransaction(spi_t *spi) { if (!spi) { return; } SPI_MUTEX_LOCK(); } void spiEndTransaction(spi_t *spi) { if (!spi) { return; } SPI_MUTEX_UNLOCK(); } void ARDUINO_ISR_ATTR spiWriteByteNL(spi_t *spi, uint8_t data) { if (!spi) { return; } spi->dev->mosi_dlen.usr_mosi_dbitlen = 7; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); } uint8_t spiTransferByteNL(spi_t *spi, uint8_t data) { if (!spi) { return 0; } spi->dev->mosi_dlen.usr_mosi_dbitlen = 7; spi->dev->miso_dlen.usr_miso_dbitlen = 7; #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0] & 0xFF; #else data = spi->dev->data_buf[0].val & 0xFF; #endif return data; } void ARDUINO_ISR_ATTR spiWriteShortNL(spi_t *spi, uint16_t data) { if (!spi) { return; } if (!spi->dev->ctrl.wr_bit_order) { MSB_16_SET(data, data); } spi->dev->mosi_dlen.usr_mosi_dbitlen = 15; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); } uint16_t spiTransferShortNL(spi_t *spi, uint16_t data) { if (!spi) { return 0; } if (!spi->dev->ctrl.wr_bit_order) { MSB_16_SET(data, data); } spi->dev->mosi_dlen.usr_mosi_dbitlen = 15; spi->dev->miso_dlen.usr_miso_dbitlen = 15; #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0] & 0xFFFF; #else data = spi->dev->data_buf[0].val & 0xFFFF; #endif if (!spi->dev->ctrl.rd_bit_order) { MSB_16_SET(data, data); } return data; } void ARDUINO_ISR_ATTR spiWriteLongNL(spi_t *spi, uint32_t data) { if (!spi) { return; } if (!spi->dev->ctrl.wr_bit_order) { MSB_32_SET(data, data); } spi->dev->mosi_dlen.usr_mosi_dbitlen = 31; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); } uint32_t spiTransferLongNL(spi_t *spi, uint32_t data) { if (!spi) { return 0; } if (!spi->dev->ctrl.wr_bit_order) { MSB_32_SET(data, data); } spi->dev->mosi_dlen.usr_mosi_dbitlen = 31; spi->dev->miso_dlen.usr_miso_dbitlen = 31; #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0]; #else data = spi->dev->data_buf[0].val; #endif if (!spi->dev->ctrl.rd_bit_order) { MSB_32_SET(data, data); } return data; } void spiWriteNL(spi_t *spi, const void *data_in, uint32_t len) { if (!spi) { return; } size_t longs = len >> 2; if (len & 3) { longs++; } uint32_t *data = (uint32_t *)data_in; size_t c_len = 0, c_longs = 0; while (len) { c_len = (len > 64) ? 64 : len; c_longs = (longs > 16) ? 16 : longs; spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len * 8) - 1; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif for (size_t i = 0; i < c_longs; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = data[i]; #else spi->dev->data_buf[i].val = data[i]; #endif } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); data += c_longs; longs -= c_longs; len -= c_len; } } void spiTransferBytesNL(spi_t *spi, const void *data_in, uint8_t *data_out, uint32_t len) { if (!spi) { return; } size_t longs = len >> 2; if (len & 3) { longs++; } uint32_t *data = (uint32_t *)data_in; uint32_t *result = (uint32_t *)data_out; size_t c_len = 0, c_longs = 0; while (len) { c_len = (len > 64) ? 64 : len; c_longs = (longs > 16) ? 16 : longs; spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len * 8) - 1; spi->dev->miso_dlen.usr_miso_dbitlen = (c_len * 8) - 1; if (data) { for (size_t i = 0; i < c_longs; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = data[i]; #else spi->dev->data_buf[i].val = data[i]; #endif } } else { for (size_t i = 0; i < c_longs; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = 0xFFFFFFFF; #else spi->dev->data_buf[i].val = 0xFFFFFFFF; #endif } } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); if (result) { if (c_len & 3) { for (size_t i = 0; i < (c_longs - 1); i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 result[i] = spi->dev->data_buf[i]; #else result[i] = spi->dev->data_buf[i].val; #endif } #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 uint32_t last_data = spi->dev->data_buf[c_longs - 1]; #else uint32_t last_data = spi->dev->data_buf[c_longs - 1].val; #endif uint8_t *last_out8 = (uint8_t *)&result[c_longs - 1]; uint8_t *last_data8 = (uint8_t *)&last_data; for (size_t i = 0; i < (c_len & 3); i++) { last_out8[i] = last_data8[i]; } } else { for (size_t i = 0; i < c_longs; i++) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 result[i] = spi->dev->data_buf[i]; #else result[i] = spi->dev->data_buf[i].val; #endif } } } if (data) { data += c_longs; } if (result) { result += c_longs; } longs -= c_longs; len -= c_len; } } void spiTransferBitsNL(spi_t *spi, uint32_t data, uint32_t *out, uint8_t bits) { if (!spi) { return; } if (bits > 32) { bits = 32; } uint32_t bytes = (bits + 7) / 8; //64 max uint32_t mask = (((uint64_t)1 << bits) - 1) & 0xFFFFFFFF; data = data & mask; if (!spi->dev->ctrl.wr_bit_order) { if (bytes == 2) { MSB_16_SET(data, data); } else if (bytes == 3) { MSB_24_SET(data, data); } else { MSB_32_SET(data, data); } } spi->dev->mosi_dlen.usr_mosi_dbitlen = (bits - 1); spi->dev->miso_dlen.usr_miso_dbitlen = (bits - 1); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[0] = data; #else spi->dev->data_buf[0].val = data; #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 data = spi->dev->data_buf[0]; #else data = spi->dev->data_buf[0].val; #endif if (out) { *out = data; if (!spi->dev->ctrl.rd_bit_order) { if (bytes == 2) { MSB_16_SET(*out, data); } else if (bytes == 3) { MSB_24_SET(*out, data); } else { MSB_32_SET(*out, data); } } } } void ARDUINO_ISR_ATTR spiWritePixelsNL(spi_t *spi, const void *data_in, uint32_t len) { size_t longs = len >> 2; if (len & 3) { longs++; } bool msb = !spi->dev->ctrl.wr_bit_order; uint32_t *data = (uint32_t *)data_in; size_t c_len = 0, c_longs = 0, l_bytes = 0; while (len) { c_len = (len > 64) ? 64 : len; c_longs = (longs > 16) ? 16 : longs; l_bytes = (c_len & 3); spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len * 8) - 1; #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32 spi->dev->miso_dlen.usr_miso_dbitlen = 0; #endif for (size_t i = 0; i < c_longs; i++) { if (msb) { if (l_bytes && i == (c_longs - 1)) { if (l_bytes == 2) { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 MSB_16_SET(spi->dev->data_buf[i], data[i]); #else MSB_16_SET(spi->dev->data_buf[i].val, data[i]); #endif } else { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = data[i] & 0xFF; #else spi->dev->data_buf[i].val = data[i] & 0xFF; #endif } } else { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 MSB_PIX_SET(spi->dev->data_buf[i], data[i]); #else MSB_PIX_SET(spi->dev->data_buf[i].val, data[i]); #endif } } else { #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 spi->dev->data_buf[i] = data[i]; #else spi->dev->data_buf[i].val = data[i]; #endif } } #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) spi->dev->cmd.update = 1; while (spi->dev->cmd.update); #endif spi->dev->cmd.usr = 1; while (spi->dev->cmd.usr); data += c_longs; longs -= c_longs; len -= c_len; } } /* * Clock Calculators * * */ typedef union { uint32_t value; struct { uint32_t clkcnt_l : 6; /*it must be equal to spi_clkcnt_N.*/ uint32_t clkcnt_h : 6; /*it must be floor((spi_clkcnt_N+1)/2-1).*/ uint32_t clkcnt_n : 6; /*it is the divider of spi_clk. So spi_clk frequency is system/(spi_clkdiv_pre+1)/(spi_clkcnt_N+1)*/ #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) uint32_t clkdiv_pre : 4; /*it is pre-divider of spi_clk.*/ uint32_t reserved : 9; /*reserved*/ #else uint32_t clkdiv_pre : 13; /*it is pre-divider of spi_clk.*/ #endif uint32_t clk_equ_sysclk : 1; /*1: spi_clk is equal to system 0: spi_clk is divided from system clock.*/ }; } spiClk_t; #define ClkRegToFreq(reg) (apb_freq / (((reg)->clkdiv_pre + 1) * ((reg)->clkcnt_n + 1))) uint32_t spiClockDivToFrequency(spi_t *spi, uint32_t clockDiv) { uint32_t apb_freq = getApbFrequency(); #if defined(CONFIG_IDF_TARGET_ESP32P4) // ESP32P4: Use the actual clock source being used by this SPI instance if (spi && spi->clk_src == 1) { // SPLL is being used apb_freq = SPI_P4_SPLL_FREQ_HZ; } else { // XTAL is being used (default or if spi is NULL) apb_freq = getXtalFrequencyMhz() * 1000000; } #else // For non-ESP32P4 targets, ignore spi parameter; always use system APB frequency. (void)spi; #endif spiClk_t reg = {clockDiv}; return ClkRegToFreq(®); } /** * @brief Calculate SPI clock divider register value for a given frequency and clock source * * @param freq Desired SPI clock frequency in Hz * @param source_freq Source clock frequency in Hz (e.g., 40MHz for XTAL, 480MHz for SPLL) * @return uint32_t Clock divider register value, or SPI_CLK_EQU_SYSCLK if freq >= source_freq * * @note This function calculates the optimal divider values (clkdiv_pre and clkcnt_n) to achieve * the desired frequency from the given source. It searches for the best match that produces * a frequency <= the desired frequency (never exceeding it). * * @note If the desired frequency is >= source_freq, returns SPI_CLK_EQU_SYSCLK (0x80000000) * which indicates the clock should equal the source without division. * * @note If the desired frequency is below the minimum achievable, returns the minimum divider * register value (0x7FFFF000). * * @note Used by spiFrequencyToClockDiv() to calculate dividers for both XTAL and SPLL sources * on ESP32P4, allowing selection of the source that gives the closest match. */ static uint32_t _spiFrequencyToClockDivWithSource(uint32_t freq, uint32_t source_freq) { if (freq >= source_freq) { return SPI_CLK_EQU_SYSCLK; } const spiClk_t minFreqReg = {0x7FFFF000}; // Calculate minFreq using the provided source frequency uint32_t minFreq = source_freq / (((minFreqReg.clkdiv_pre + 1) * (minFreqReg.clkcnt_n + 1))); if (freq < minFreq) { return minFreqReg.value; } uint8_t calN = 1; spiClk_t bestReg = {0}; uint32_t bestFreq = 0; while (calN <= 0x3F) { spiClk_t reg = {0}; uint32_t calFreq; int32_t calPre; int8_t calPreVari = -2; reg.clkcnt_n = calN; while (calPreVari++ <= 1) { calPre = (((source_freq / (reg.clkcnt_n + 1)) / freq) - 1) + calPreVari; #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) if (calPre > 0xF) { reg.clkdiv_pre = 0xF; #else if (calPre > 0x1FFF) { reg.clkdiv_pre = 0x1FFF; #endif } else if (calPre <= 0) { reg.clkdiv_pre = 0; } else { reg.clkdiv_pre = calPre; } reg.clkcnt_l = ((reg.clkcnt_n + 1) / 2); // Calculate frequency directly using source_freq instead of ClkRegToFreq macro calFreq = source_freq / (((reg.clkdiv_pre + 1) * (reg.clkcnt_n + 1))); if (calFreq == freq) { memcpy(&bestReg, ®, sizeof(bestReg)); break; } else if (calFreq < freq) { if (bestFreq == 0 || (freq - calFreq) < (freq - bestFreq)) { bestFreq = calFreq; memcpy(&bestReg, ®, sizeof(bestReg)); } } } if (calFreq == (int32_t)freq) { break; } calN++; } return bestReg.value; } uint32_t spiFrequencyToClockDiv(spi_t *spi, uint32_t freq) { #if defined(CONFIG_IDF_TARGET_ESP32P4) // ESP32P4: Limit frequency to SPI peripheral maximum if (freq > SPI_P4_MAX_FREQ_HZ) { freq = SPI_P4_MAX_FREQ_HZ; } // Try both clock sources and pick the one that gives frequency closest to desired uint32_t xtal_freq = getXtalFrequencyMhz() * 1000000; // Actual XTAL frequency (typically 40 MHz) uint32_t spll_freq = SPI_P4_SPLL_FREQ_HZ; // Calculate dividers for both sources uint32_t div_xtal = _spiFrequencyToClockDivWithSource(freq, xtal_freq); uint32_t div_spll = _spiFrequencyToClockDivWithSource(freq, spll_freq); // Calculate actual frequencies each divider would produce uint32_t divider_xtal = _clockDivToDivider(div_xtal); uint32_t divider_spll = _clockDivToDivider(div_spll); uint32_t freq_xtal = _dividerToFreq(divider_xtal, xtal_freq); uint32_t freq_spll = _dividerToFreq(divider_spll, spll_freq); // Pick the one closest to desired frequency uint32_t diff_xtal = (freq > freq_xtal) ? (freq - freq_xtal) : (freq_xtal - freq); uint32_t diff_spll = (freq > freq_spll) ? (freq - freq_spll) : (freq_spll - freq); // Pick the one with closest difference to desired frequency // If both are valid (XTAL capped at its actual frequency) and differences are equal, prefer XTAL uint8_t best_is_spll; if (diff_spll < diff_xtal) { best_is_spll = 1; // SPLL is closer } else if (diff_xtal < diff_spll) { best_is_spll = 0; // XTAL is closer } else { // Equal differences: prefer XTAL if it's valid (freq_xtal <= xtal_freq), otherwise use SPLL best_is_spll = (freq_xtal <= xtal_freq) ? 0 : 1; } uint32_t best_div = best_is_spll ? div_spll : div_xtal; // Store the divider and source for this SPI instance (if spi is provided) if (spi) { spi->last_clock_div = best_div; spi->last_clk_src = best_is_spll; } // Return divider for the clock source that gives closest match return best_div; #else // Non-ESP32P4: Only use APB clock, spi parameter unused. (void)spi; // Suppress unused parameter warning return _spiFrequencyToClockDivWithSource(freq, getApbFrequency()); #endif } #endif /* SOC_GPSPI_SUPPORTED */