summaryrefslogtreecommitdiff
path: root/src/oracle/ledc_ref.c
blob: 6b1403afbb3284372e421d12406b04ae87ca6ffb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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);
}