diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp index aa2d0c9..c08fb59 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.cpp @@ -39,6 +39,17 @@ Original Source: #include #include +#if defined (ARDUINO) && (defined (CONFIG_IDF_TARGET_ESP32P4) \ + || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C6) \ + || defined (CONFIG_IDF_TARGET_ESP32C61)) + #define LGFX_SPI_CLOCK_TAKEOVER + #if defined (CONFIG_IDF_TARGET_ESP32P4) + #include + #else + #include + #endif +#endif + #if __has_include () #include #else @@ -149,6 +160,199 @@ namespace lgfx static __attribute__ ((always_inline)) inline void writereg(uint32_t addr, uint32_t value) { *(volatile uint32_t*)addr = value; } #pragma GCC diagnostic pop +#if defined (LGFX_SPI_CLOCK_TAKEOVER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + struct spi_clock_state_t + { + uint32_t saved0 = 0; + uint32_t saved1 = 0; + const Bus_SPI* owner = nullptr; + bool active = false; + }; + + static spi_clock_state_t spi_clock_state[SOC_SPI_PERIPH_NUM]; + + static bool spi_clock_host_supported(int spi_host) + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + return spi_host == SPI2_HOST || spi_host == SPI3_HOST; +#else + return spi_host == SPI2_HOST; +#endif + } + + static uint32_t spi_clock_output_frequency(uint32_t source_hz, uint32_t requested_hz) + { + const uint32_t clock_div = FreqToClockDiv(source_hz, requested_hz); + if (clock_div & SPI_CLK_EQU_SYSCLK) { return source_hz; } + const uint32_t pre = VALUE_GET_FIELD(clock_div, SPI_CLKDIV_PRE) + 1u; + const uint32_t n = VALUE_GET_FIELD(clock_div, SPI_CLKCNT_N) + 1u; + return source_hz / pre / n; + } + + static uint32_t spi_clock_error(uint32_t actual_hz, uint32_t requested_hz) + { + return actual_hz > requested_hz ? actual_hz - requested_hz : requested_hz - actual_hz; + } + + struct spi_clock_target_t + { + uint32_t base_hz; + uint32_t source_div; + }; + + static spi_clock_target_t spi_clock_find_target(uint32_t requested_hz) + { +#if defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + (void)requested_hz; + // The 80 MHz root preserves both common write and read rates (for example + // 40 and 16 MHz), while a 40 MHz root would make 16 MHz inexact. + return { 80000000u, 2u }; +#else + (void)requested_hz; + return { 80000000u, 1u }; +#endif + } + + static bool spi_clock_acquire(const Bus_SPI* owner, int spi_host + , uint32_t write_hz, uint32_t read_hz) + { + if (!spi_clock_host_supported(spi_host) || write_hz == 0 || read_hz == 0) { return false; } + + const auto target = spi_clock_find_target(std::max(write_hz, read_hz)); + const uint32_t current_hz = getSpiClockFrequency(spi_host); + const uint32_t current_write = spi_clock_output_frequency(current_hz, write_hz); + const uint32_t target_write = spi_clock_output_frequency(target.base_hz, write_hz); + const uint32_t target_read = spi_clock_output_frequency(target.base_hz, read_hz); + const uint32_t current_write_error = spi_clock_error(current_write, write_hz); + const uint32_t target_write_error = spi_clock_error(target_write, write_hz); + // Prefer write throughput: a slower read clock is acceptable, but neither + // clock may exceed its request. Equal write results leave the current owner + // untouched to avoid an unnecessary source change. + if (target_write > write_hz || target_read > read_hz + || target_write_error >= current_write_error) + { + return false; + } + + auto& state = spi_clock_state[spi_host]; + if (state.active) { return false; } // Host-scoped ownership is not nestable. + + // The Arduino bus mutex does not serialize ESP-IDF SPI driver users on the + // same host; mixing the two APIs cannot provide transaction-wide exclusion. + PERIPH_RCC_ATOMIC() + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + if (spi_host == SPI2_HOST) + { + constexpr uint32_t mask = HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL_M + | HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM_M; + constexpr uint32_t target_value = (4u << HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL_S) + | (2u << HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM_S) + | (1u << HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM_S); + const uint32_t current = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + state.saved0 = current & mask; + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (current & ~mask) | target_value); + } + else + { + constexpr uint32_t source_mask = HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL_M; + constexpr uint32_t source_target = 4u << HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL_S; + constexpr uint32_t divider_mask = HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM_M; + constexpr uint32_t divider_target = (2u << HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM_S) + | (1u << HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM_S); + const uint32_t ctrl116 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + const uint32_t ctrl117 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG); + state.saved0 = ctrl116 & source_mask; + state.saved1 = ctrl117 & divider_mask; + // Install safe dividers before selecting the 480 MHz source. + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG, (ctrl117 & ~divider_mask) | divider_target); + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (ctrl116 & ~source_mask) | source_target); + } +#elif defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M | PCR_SPI2_CLKM_DIV_NUM_M; + const uint32_t target_value = (1u << PCR_SPI2_CLKM_SEL_S) + | ((target.source_div - 1u) << PCR_SPI2_CLKM_DIV_NUM_S); + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + state.saved0 = current & mask; + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | target_value); +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M; + constexpr uint32_t target_value = 1u << PCR_SPI2_CLKM_SEL_S; + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + state.saved0 = current & mask; + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | target_value); +#endif + state.owner = owner; + state.active = true; + } + return true; + } + + static bool spi_clock_owned_by(const Bus_SPI* owner, int spi_host) + { + if (!spi_clock_host_supported(spi_host)) { return false; } + const auto& state = spi_clock_state[spi_host]; + return state.active && state.owner == owner; + } + + static bool spi_clock_restore(const Bus_SPI* owner, int spi_host) + { + if (!spi_clock_host_supported(spi_host)) { return false; } + + auto& state = spi_clock_state[spi_host]; + if (!state.active || state.owner != owner) { return false; } + + PERIPH_RCC_ATOMIC() + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + if (spi_host == SPI2_HOST) + { + constexpr uint32_t mask = HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL_M + | HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM_M; + const uint32_t current = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (current & ~mask) | state.saved0); + } + else + { + constexpr uint32_t source_mask = HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL_M; + constexpr uint32_t divider_mask = HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM_M + | HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM_M; + const uint32_t ctrl116 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + const uint32_t ctrl117 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG); + // Leave the 480 MHz source before restoring potentially smaller dividers. + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG, (ctrl116 & ~source_mask) | state.saved0); + REG_WRITE(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG, (ctrl117 & ~divider_mask) | state.saved1); + } +#elif defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M | PCR_SPI2_CLKM_DIV_NUM_M; + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | state.saved0); +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + constexpr uint32_t mask = PCR_SPI2_CLKM_SEL_M; + const uint32_t current = REG_READ(PCR_SPI2_CLKM_CONF_REG); + REG_WRITE(PCR_SPI2_CLKM_CONF_REG, (current & ~mask) | state.saved0); +#endif + state.active = false; + state.owner = nullptr; + } + return true; + } +#pragma GCC diagnostic pop + + Bus_SPI::~Bus_SPI(void) + { + if (spi_clock_owned_by(this, _cfg.spi_host)) + { + release(); + } + } +#endif + void Bus_SPI::config(const config_t& cfg) { _cfg = cfg; @@ -262,6 +466,18 @@ namespace lgfx void Bus_SPI::release(void) { //ESP_LOGI("LGFX","Bus_SPI::release"); +#if defined (LGFX_SPI_CLOCK_TAKEOVER) + if (spi_clock_owned_by(this, _cfg.spi_host)) + { + // An active takeover implies this instance still owns the Arduino bus + // mutex. Finish the transfer and restore the host before releasing it. + dc_control(true); + if (spi_clock_restore(this, _cfg.spi_host)) + { + spi::endTransaction(_cfg.spi_host); + } + } +#endif if (!_inited) return; _inited = false; spi::release(_cfg.spi_host); @@ -288,7 +504,15 @@ namespace lgfx void Bus_SPI::beginTransaction(void) { //ESP_LOGI("LGFX","Bus_SPI::beginTransaction"); - uint32_t freq_apb = getApbFrequency(); + // Bus acquisition can change the SPI source or its pre-dividers. + if (_cfg.use_lock) + { + spi::beginTransaction(_cfg.spi_host); +#if defined (LGFX_SPI_CLOCK_TAKEOVER) + spi_clock_acquire(this, _cfg.spi_host, _cfg.freq_write, _cfg.freq_read); +#endif + } + uint32_t freq_apb = getSpiClockFrequency(_cfg.spi_host); uint32_t clkdiv_write = _clkdiv_write; if (_last_freq_apb != freq_apb) { @@ -323,8 +547,6 @@ namespace lgfx #endif ; - if (_cfg.use_lock) spi::beginTransaction(_cfg.spi_host); - *_spi_user_reg = _user_reg; auto spi_port = _spi_port; (void)spi_port; @@ -340,6 +562,9 @@ namespace lgfx dc_control(true); #if defined ( LGFX_SPIDMA_WORKAROUND ) if (_dma_ch) { spicommon_dmaworkaround_idle(_dma_ch); } +#endif +#if defined (LGFX_SPI_CLOCK_TAKEOVER) + if (_cfg.use_lock) { spi_clock_restore(this, _cfg.spi_host); } #endif if (_cfg.use_lock) spi::endTransaction(_cfg.spi_host); #if defined (ARDUINO) // Arduino ESP32 diff --git a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp index 996c826..c144b7b 100644 --- a/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp +++ b/src/lgfx/v1/platforms/esp32/Bus_SPI.hpp @@ -103,6 +103,11 @@ namespace lgfx }; constexpr Bus_SPI(void) = default; +#if defined (ARDUINO) && (defined (CONFIG_IDF_TARGET_ESP32P4) \ + || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C6) \ + || defined (CONFIG_IDF_TARGET_ESP32C61)) + ~Bus_SPI(void) override; +#endif const config_t& config(void) const { return _cfg; } diff --git a/src/lgfx/v1/platforms/esp32/common.cpp b/src/lgfx/v1/platforms/esp32/common.cpp index 8a944f6..41ed701 100644 --- a/src/lgfx/v1/platforms/esp32/common.cpp +++ b/src/lgfx/v1/platforms/esp32/common.cpp @@ -38,6 +38,17 @@ Original Source: #include #include #include +#if defined ( CONFIG_IDF_TARGET_ESP32P4 ) + #include +#elif defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C6 ) \ + || defined ( CONFIG_IDF_TARGET_ESP32C61 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) + #include +#endif +#if __has_include() && __has_include() + #include + #include + #define LGFX_HAS_ESP_CLK_TREE +#endif #include #include #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) @@ -268,12 +279,139 @@ namespace lgfx #endif } + uint32_t getSpiClockFrequency(int spi_host) + { +#if defined ( CONFIG_IDF_TARGET_ESP32 ) || defined ( CONFIG_IDF_TARGET_ESP32S2 ) \ + || !defined ( CONFIG_IDF_TARGET ) + (void)spi_host; + return getApbFrequency(); +#else + const auto get_xtal_frequency = []() -> uint32_t + { + return static_cast(rtc_clk_xtal_freq_get()) * 1000000u; + }; + const auto get_rc_fast_frequency = []() -> uint32_t + { +#if defined ( LGFX_HAS_ESP_CLK_TREE ) && defined ( SOC_MOD_CLK_RC_FAST ) + uint32_t frequency = 0; + if (ESP_OK == esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_RC_FAST + , ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &frequency) && frequency) + { + return frequency; + } +#endif +#if defined ( SOC_CLK_RC_FAST_FREQ_APPROX ) + return SOC_CLK_RC_FAST_FREQ_APPROX; +#else + return 17500000u; +#endif + }; + +#if defined ( CONFIG_IDF_TARGET_ESP32S3 ) || defined ( CONFIG_IDF_TARGET_ESP32C2 ) \ + || defined ( CONFIG_IDF_TARGET_ESP32C3 ) + static_assert(SPI_MST_CLK_SEL_V == 1u, "SPI clock source selector must be one bit"); + // SPI_MST_CLK_SEL is a shifted mask in the older SPI register headers; + // decode with the explicit value mask rather than VALUE_GET_FIELD. + const uint32_t source_sel = (REG_READ(SPI_CLK_GATE_REG(spi_host + 1)) + >> SPI_MST_CLK_SEL_S) & SPI_MST_CLK_SEL_V; + if (source_sel == 0) { return get_xtal_frequency(); } + #if defined ( CONFIG_IDF_TARGET_ESP32C2 ) + return 40000000u; + #else + return 80000000u; // PLL_F80M is independent of CPU/APB frequency scaling. + #endif + +#elif defined ( CONFIG_IDF_TARGET_ESP32C5 ) || defined ( CONFIG_IDF_TARGET_ESP32C61 ) + if (spi_host != SPI2_HOST) { return 160000000u; } + const uint32_t clkm = REG_READ(PCR_SPI2_CLKM_CONF_REG); + const uint32_t source_sel = VALUE_GET_FIELD(clkm, PCR_SPI2_CLKM_SEL); + const uint32_t source_div = VALUE_GET_FIELD(clkm, PCR_SPI2_CLKM_DIV_NUM) + 1u; + uint32_t source_hz; + switch (source_sel) + { + case 0: source_hz = get_xtal_frequency(); break; + case 1: source_hz = 160000000u; break; + case 2: source_hz = get_rc_fast_frequency(); break; +#if defined ( CONFIG_IDF_TARGET_ESP32C5 ) + case 3: source_hz = 120000000u; break; +#endif + default: source_hz = 160000000u; break; // Safe upper bound for an unknown source. + } + return source_hz / source_div; + +#elif defined ( CONFIG_IDF_TARGET_ESP32C6 ) || defined ( CONFIG_IDF_TARGET_ESP32H2 ) + if (spi_host != SPI2_HOST) + { + #if defined ( CONFIG_IDF_TARGET_ESP32C6 ) + return 80000000u; + #else + return 48000000u; + #endif + } + const uint32_t clkm = REG_READ(PCR_SPI2_CLKM_CONF_REG); + const uint32_t source_sel = VALUE_GET_FIELD(clkm, PCR_SPI2_CLKM_SEL); + switch (source_sel) + { + case 0: return get_xtal_frequency(); + #if defined ( CONFIG_IDF_TARGET_ESP32C6 ) + case 1: return 80000000u; + #else + case 1: return 48000000u; + #endif + case 2: return get_rc_fast_frequency(); + default: + #if defined ( CONFIG_IDF_TARGET_ESP32C6 ) + return 80000000u; // Safe upper bound for an unknown source. + #else + return 48000000u; // Safe upper bound for an unknown source. + #endif + } + +#elif defined ( CONFIG_IDF_TARGET_ESP32P4 ) + const uint32_t ctrl116 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL116_REG); + uint32_t source_sel; + uint32_t hs_div; + uint32_t mst_div; + if (spi_host == SPI2_HOST) + { + source_sel = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI2_CLK_SRC_SEL); + hs_div = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI2_HS_CLK_DIV_NUM); + mst_div = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI2_MST_CLK_DIV_NUM); + } + else if (spi_host == SPI3_HOST) + { + const uint32_t ctrl117 = REG_READ(HP_SYS_CLKRST_PERI_CLK_CTRL117_REG); + source_sel = VALUE_GET_FIELD(ctrl116, HP_SYS_CLKRST_REG_GPSPI3_CLK_SRC_SEL); + hs_div = VALUE_GET_FIELD(ctrl117, HP_SYS_CLKRST_REG_GPSPI3_HS_CLK_DIV_NUM); + mst_div = VALUE_GET_FIELD(ctrl117, HP_SYS_CLKRST_REG_GPSPI3_MST_CLK_DIV_NUM); + } + else + { + return getApbFrequency(); + } + + uint32_t source_hz; + switch (source_sel) + { + case 0: source_hz = get_xtal_frequency(); break; + case 1: source_hz = get_rc_fast_frequency(); break; + case 4: source_hz = 480000000u; break; // SPLL + default: source_hz = 480000000u; break; // Safe upper bound for an unknown source. + } + return source_hz / (hs_div + 1) / (mst_div + 1); +#else + (void)spi_host; + return getApbFrequency(); +#endif +#endif + } + uint32_t FreqToClockDiv(uint32_t fapb, uint32_t hz) { if (fapb <= hz) return SPI_CLK_EQU_SYSCLK; uint32_t div_num = fapb / (1 + hz); - uint32_t pre = div_num / 64u; - div_num = div_num / (pre+1); + uint32_t pre = std::min(div_num / (SPI_CLKCNT_N_V + 1u), SPI_CLKDIV_PRE_V); + div_num = std::min(div_num / (pre+1), SPI_CLKCNT_N_V); return div_num << 12 | ((div_num-1)>>1) << 6 | div_num | pre << 18; } @@ -891,7 +1029,9 @@ namespace lgfx } uint32_t spi_port = (spi_host + 1); (void)spi_port; - uint32_t clkdiv = FreqToClockDiv(getApbFrequency(), freq); + // Bus acquisition may select a different source or pre-divider. + beginTransaction(spi_host); + uint32_t clkdiv = FreqToClockDiv(getSpiClockFrequency(spi_host), freq); uint32_t user = SPI_USR_MOSI | SPI_USR_MISO | SPI_DOUTDIN; if (spi_mode == 1 || spi_mode == 2) user |= SPI_CK_OUT_EDGE; @@ -917,8 +1057,6 @@ namespace lgfx #endif ; - beginTransaction(spi_host); - writereg(SPI_USER_REG(spi_port), user); #if defined (SPI_PIN_REG) writereg(SPI_PIN_REG(spi_port), pin); diff --git a/src/lgfx/v1/platforms/esp32/common.hpp b/src/lgfx/v1/platforms/esp32/common.hpp index fa8b4c8..8ce48ad 100644 --- a/src/lgfx/v1/platforms/esp32/common.hpp +++ b/src/lgfx/v1/platforms/esp32/common.hpp @@ -192,6 +192,7 @@ namespace lgfx static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_lo: %d", pin); } uint32_t getApbFrequency(void); + uint32_t getSpiClockFrequency(int spi_host); uint32_t FreqToClockDiv(uint32_t fapb, uint32_t hz); /// for I2S and LCD_CAM peripheral clock