Skip to content

Derive the SPI divider base from the active GPSPI clock source - #260

Merged
lovyan03 merged 3 commits into
m5stack:developfrom
ainyan03:spi_clock_source
Aug 19, 2026
Merged

Derive the SPI divider base from the active GPSPI clock source#260
lovyan03 merged 3 commits into
m5stack:developfrom
ainyan03:spi_clock_source

Conversation

@ainyan03

Copy link
Copy Markdown
Contributor

FreqToClockDiv() receives a base clock from getApbFrequency(), which reports a fixed 80 MHz on every target. That was true while the SPI peripheral was fed from APB, but the newer chips select a clock source and, on some of them, run it through pre-dividers first. Nothing configures that source unless the ESP-IDF driver acquires the bus, and Arduino builds take the Arduino bus mutex instead, so the source is left at whatever reset or the Arduino HAL happened to leave.

The dividers were therefore computed against a base clock that is not the real one, and the resulting SCLK could be either far below or well above the requested value.

Measured on hardware

Requested vs. actual SCLK before these changes:

Target Path Requested Actual
ESP32-S3 Arduino 40 MHz 80 MHz
ESP32-S3 Arduino, CPU scaled to 40 MHz 40 MHz 80 MHz
ESP32-C61 ESP-IDF without bus lock 40 MHz 80 MHz
ESP32-P4 Arduino 80 MHz 40 MHz
ESP32-C5 Arduino 40 MHz 24 MHz
ESP32-H2 Arduino 40 MHz 16 MHz

The overspeeding cases are the reason for the first two commits: a panel asked for 40 MHz and got twice that.

ESP32, ESP32-S2 and ESP32-C6 are unaffected and measured unchanged.

What the commits do

Clamp SPI clock dividers to their register widthsFreqToClockDiv() could produce a CLKDIV_PRE wider than its field, spilling into neighbouring bits; with a low enough request the spill reached SPI_CLK_EQU_SYSCLK and drove the bus at full speed when a slow clock was asked for. The limits now come from the register definitions, so each target keeps its own field width.

Use the active GPSPI source clock for divider calculations — the base clock is derived from the clock-source selector and pre-dividers that are actually programmed, instead of assuming 80 MHz. Bus acquisition moves ahead of the calculation because acquiring can change the source, and reading first would use a stale base for the first transaction. The crystal frequency is read at runtime rather than assumed, which matters on parts that ship with something other than 40 MHz.

Select faster GPSPI sources for Arduino transactions — where a better source exists, it is selected for the duration of a transaction and the saved fields are restored when it ends, so other users of the bus see the state they left. The takeover only runs while the bus lock is held, only when it produces a strictly closer write clock without overspeeding either requested rate, and is undone when the bus is released or destroyed.

Verification

  • Hardware: ESP32, ESP32-S3, ESP32-C6, ESP32-H2, ESP32-C5, ESP32-C61 and ESP32-P4, all measured with this branch. For every target the SCLK computed from the clock registers and the SCLK derived from transfer timing agree, and both match the requested rate within the limits of integer division.
  • ESP32-C3 and ESP32-C2 were reviewed but not measured. ESP32-C3 shares its decoding path with ESP32-S3.

One user-visible effect: an ESP32-C5 board whose panel is configured for 40 MHz was running at 24 MHz, and a full-screen fill went from 52.5 ms to 32.1 ms.

FreqToClockDiv could produce a CLKDIV_PRE value wider than the register field when the requested frequency was far below the base clock, spilling into adjacent fields. Clamp both divider components with the target register definitions so each SoC retains its supported range without corrupting neighboring bits.
Decode each supported ESP32 target's live GPSPI clock source and pre-divider instead of assuming an 80 MHz APB clock. Acquire the bus before reading that state because the driver may change it, allowing the existing cache to recalculate whenever another owner selects a different source. Unknown selectors use a safe upper bound so divider calculation does not accidentally overspeed the request.
On ESP32-C5, C61, C6, and P4 Arduino builds, temporarily select an 80 MHz GPSPI base only when it produces a strictly closer write clock without overspeeding either requested rate. Equal write results keep the current source; read throughput may decrease when write throughput improves. Save and restore only the owned clock fields inside the RCC atomic section, and clean up active ownership when a bus is released or destroyed. ESP-IDF transactions retain driver ownership, and the Arduino mutex does not serialize mixed API users.
@lovyan03
lovyan03 merged commit f59954a into m5stack:develop Aug 19, 2026
27 checks passed
@ainyan03
ainyan03 deleted the spi_clock_source branch August 19, 2026 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants