/* 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 #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); }