diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-25 12:40:53 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-25 12:46:51 -0300 |
| commit | f5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch) | |
| tree | 2731a3ed4e51cae09e184e25778eded5fc37d1f5 /src/oracle/ledc_ref.c | |
| download | esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.tar.gz esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.zip | |
zig-p4: pure-Zig ESP32-P4 toolchain
build.zig generates the linker script and drives Zig's own LLD; tools/image.zig
turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask
ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker.
src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers;
src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip;
src/oracle/ diffs this HAL against ESP-IDF's on the die.
Diffstat (limited to 'src/oracle/ledc_ref.c')
| -rw-r--r-- | src/oracle/ledc_ref.c | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/src/oracle/ledc_ref.c b/src/oracle/ledc_ref.c new file mode 100644 index 0000000..6b1403a --- /dev/null +++ b/src/oracle/ledc_ref.c @@ -0,0 +1,210 @@ +/* LEDC's reference implementation: ESP-IDF's own LL, given external linkage. + * + * There is no logic here except where a comment says otherwise, and there is exactly one such + * place - `oracle_ledc_divisor` - because the divider arithmetic lives in a `static inline` inside + * `esp_driver_ledc/src/ledc.c` and is therefore unreachable from a header. It is transcribed + * character for character, with the line number, so that the on-die comparison covers the + * arithmetic and not only the store that follows it. + */ + +/* IDF's clock and reset LL functions are shadowed by a wrapper macro that references + * `__DECLARE_RCC_ATOMIC_ENV`, an identifier IDF never defines anywhere; its purpose is to make an + * unguarded call fail to compile, because the only legal caller holds a spinlock. There is no + * FreeRTOS here and core 1 is held in reset at power-on, so declaring the name is exactly as safe + * as the spinlock would be - and it is what IDF's own bootloader does. */ +static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); + +/* `ledc_ll_set_slow_clk_sel` and `ledc_ll_get_slow_clk_sel` call `abort()` in the default arm of + * their switch (ledc_ll.h:238, :273). Freestanding, nothing declares it; the arms below are all + * reached with constants, so the call folds away and no definition is needed. */ +void abort(void); + +#include <stdint.h> + +#include "hal/ledc_ll.h" +#include "hal/gpio_ll.h" +#include "soc/gpio_struct.h" +#include "soc/gpio_sig_map.h" + +/* P4 has one speed mode: low. `ledc_ll.h` still takes the parameter because the LL is shared with + * parts that have two. */ +#define MODE LEDC_LOW_SPEED_MODE + +/* ---------------------------------------------------------------- clocks, outside the LEDC block */ + +void oracle_ledc_enable_bus_clock(int enable) +{ + ledc_ll_enable_bus_clock(enable != 0); +} + +void oracle_ledc_enable_function_clock(int enable) +{ + ledc_ll_enable_clock(LEDC_LL_GET_HW(), enable != 0); +} + +/* `sel` is this project's `ClockSource` enum, which is HP_SYS_CLKRST's own encoding: 0 XTAL, + * 1 RC_FAST, 2 PLL_DIV. Split into three constant calls so that IDF's switch folds and its + * `abort()` arm never reaches the linker. */ +void oracle_ledc_set_clock_source(unsigned sel) +{ + switch (sel) { + case 0: + ledc_ll_set_slow_clk_sel(LEDC_LL_GET_HW(), LEDC_SLOW_CLK_XTAL); + break; + case 1: + ledc_ll_set_slow_clk_sel(LEDC_LL_GET_HW(), LEDC_SLOW_CLK_RC_FAST); + break; + case 2: + ledc_ll_set_slow_clk_sel(LEDC_LL_GET_HW(), LEDC_SLOW_CLK_PLL_DIV); + break; + default: + break; + } +} + +/* ---------------------------------------------------------------------------- divider arithmetic */ + +/* Verbatim from esp_driver_ledc/src/ledc.c:468-497 (v6.0.2), which is `static inline` in a .c file + * and so cannot be called. The 32-bit wrap of `freq_hz * precision` and the truncation of the + * 64-bit quotient into `uint32_t` are IDF's, and are the whole reason this exists: they are what + * src/hal/ledc.zig's `divisor` has to reproduce. */ +uint32_t oracle_ledc_divisor(uint32_t src_clk_freq, int freq_hz, uint32_t precision) +{ + return (((uint64_t) src_clk_freq << LEDC_LL_FRACTIONAL_BITS) + freq_hz * precision / 2) + / (freq_hz * precision); +} + +/* ------------------------------------------------------------------------------------- timers */ + +void oracle_ledc_set_clock_divider(unsigned timer, uint32_t div) +{ + ledc_ll_set_clock_divider(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer, div); +} + +void oracle_ledc_set_duty_resolution(unsigned timer, uint32_t bits) +{ + ledc_ll_set_duty_resolution(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer, bits); +} + +void oracle_ledc_commit_timer(unsigned timer) +{ + ledc_ll_ls_timer_update(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); +} + +void oracle_ledc_reset_timer(unsigned timer) +{ + ledc_ll_timer_rst(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); +} + +void oracle_ledc_pause_timer(unsigned timer) +{ + ledc_ll_timer_pause(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); +} + +void oracle_ledc_resume_timer(unsigned timer) +{ + ledc_ll_timer_resume(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); +} + +/* `ledc_set_timer_params` (ledc.c:244-261) followed by the resume/reset pair `ledc_timer_config` + * does on success (ledc.c:816-818). The clock-source step of `ledc_set_timer_params` is absent on + * purpose: on the P4 there is no timer-specific mux (SOC_LEDC_HAS_TIMER_SPECIFIC_MUX is unset), so + * that step compiles out of IDF too. */ +void oracle_ledc_configure_timer(unsigned timer, uint32_t src_hz, int freq_hz, uint32_t resolution) +{ + uint32_t div = oracle_ledc_divisor(src_hz, freq_hz, 1u << resolution); + ledc_ll_set_clock_divider(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer, div); + ledc_ll_set_duty_resolution(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer, resolution); + ledc_ll_ls_timer_update(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); + ledc_ll_timer_resume(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); + ledc_ll_timer_rst(LEDC_LL_GET_HW(), MODE, (ledc_timer_t)timer); +} + +/* ------------------------------------------------------------------------------------ channels */ + +void oracle_ledc_bind_timer(unsigned channel, unsigned timer) +{ + ledc_ll_bind_channel_timer(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, (ledc_timer_t)timer); +} + +void oracle_ledc_set_hpoint(unsigned channel, uint32_t hpoint) +{ + ledc_ll_set_hpoint(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, hpoint); +} + +/* `ledc_duty_config` (ledc.c:263-280) with `hpoint_val` left alone: the duty integer part, then the + * degenerate one-step fade in gamma RAM entry 0 that a constant duty needs on this die, then the + * range count. `ledc_hal_clear_left_off_fade_param` is deliberately not called - it zeroes ranges + * 1..15, which only matters once real fades are in scope. */ +void oracle_ledc_set_duty(unsigned channel, uint32_t duty) +{ + ledc_ll_set_duty_int_part(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, duty); + ledc_ll_set_fade_param_range(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, 0, 1, 1, 0, 1); + ledc_ll_set_range_number(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, 1); +} + +void oracle_ledc_set_output_enabled(unsigned channel, int enable) +{ + ledc_ll_set_sig_out_en(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, enable != 0); +} + +void oracle_ledc_set_idle_level(unsigned channel, uint32_t level) +{ + ledc_ll_set_idle_level(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, level); +} + +void oracle_ledc_start_fade(unsigned channel) +{ + ledc_ll_set_duty_start(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); +} + +void oracle_ledc_commit_channel(unsigned channel) +{ + ledc_ll_ls_channel_update(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); +} + +/* `_ledc_update_duty`, ledc.c:1021-1026. */ +void oracle_ledc_start(unsigned channel) +{ + ledc_ll_set_sig_out_en(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, true); + ledc_ll_set_duty_start(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); + ledc_ll_ls_channel_update(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); +} + +/* `ledc_stop`, ledc.c:1039-1050: idle level staged before the output is disabled, one commit. */ +void oracle_ledc_stop(unsigned channel, uint32_t idle_level) +{ + ledc_ll_set_idle_level(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, idle_level); + ledc_ll_set_sig_out_en(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, false); + ledc_ll_ls_channel_update(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); +} + +/* The register half of `ledc_channel_config` (ledc.c:869-1019): stage timer, hpoint, duty, idle + * level and output enable, hand the duty over, commit once. */ +void oracle_ledc_configure_channel(unsigned channel, unsigned timer, uint32_t duty, uint32_t hpoint, + uint32_t idle_level, int output_enabled) +{ + ledc_ll_bind_channel_timer(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, (ledc_timer_t)timer); + ledc_ll_set_hpoint(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, hpoint); + ledc_ll_set_duty_int_part(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, duty); + ledc_ll_set_fade_param_range(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, 0, 1, 1, 0, 1); + ledc_ll_set_range_number(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, 1); + ledc_ll_set_idle_level(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, idle_level); + ledc_ll_set_sig_out_en(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel, output_enabled != 0); + ledc_ll_set_duty_start(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); + ledc_ll_ls_channel_update(LEDC_LL_GET_HW(), MODE, (ledc_channel_t)channel); +} + +/* ---------------------------------------------------------------------------------- pin routing */ + +/* The hardware effect of `ledc_set_pin` (ledc.c:823-836). `gpio_matrix_output` is + * `gpio_hal_matrix_out` (gpio_hal.c:60-69): pad function, matrix source, then the output-enable + * control last "to avoid undesired level change". The signal index is + * `ledc_periph_signal[0].sig_out0_idx + channel`, and that field is initialised to + * `LEDC_LS_SIG_OUT_PAD_OUT0_IDX` in esp_hal_ledc/esp32p4/ledc_periph.c:14-18. */ +void oracle_ledc_set_pin(unsigned pin, unsigned channel) +{ + gpio_ll_func_sel(&GPIO, pin, PIN_FUNC_GPIO); + gpio_ll_set_output_signal_matrix_source(&GPIO, pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + channel, false); + gpio_ll_set_output_enable_ctrl(&GPIO, pin, true, false); +} |
