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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
/* SDMMC's reference half: ESP-IDF's own code, compiled into this image.
*
* Most of what follows is a one-line wrapper over a `sdmmc_ll_*` function, for the same reason
* `gpio_ref.c`'s are: the LL functions are `static inline`, so Zig cannot call them until
* something gives them external linkage, and anything clever here would be a third implementation
* to doubt.
*
* Three of them are not wrappers, and it is worth being explicit about which and why.
*
* 1. `oracle_sdmmc_set_fifo_threshold` writes SDHOST_FIFOTH through `SDMMC.fifoth`. There is no
* `sdmmc_ll` function for that register - ESP-IDF never writes it, on any target - so there
* is nothing to wrap. Writing it through IDF's own bitfield union still makes the bit
* positions IDF's, which is the property the comparison needs.
*
* 2. `oracle_sdmmc_stage_command` transcribes `make_hw_cmd` (sd_trans_sdmmc.c:190-229) and the
* three fields `sd_host_slot_start_command` adds afterwards (sd_host_sdmmc.c:859-881).
* `make_hw_cmd` is `static` in a `.c` file and unreachable from a header, so this is the one
* place the reference is a transcription rather than a call. It is a transcription *into
* IDF's `sdmmc_hw_cmd_t`*, so every bit position still comes from
* `soc/sdmmc_struct.h:354-485` and not from this file; what is being compared is whether the
* Zig side's `Field.of` shifts land in the same places, which is exactly the kind of
* transcription error the oracle exists to catch.
*
* It stages the word with `start_command` cleared. Bit 31 is what launches a command, so a
* staged word is inert: the register can be photographed without the CIU trying to talk to a
* radio that is still in reset.
*
* 3. `oracle_sdmmc_configure_controller` and `oracle_sdmmc_module_reset` are short LL sequences,
* in the order `sd_host_sdmmc.c` performs them. Sequences are the part of a driver that
* register macros cannot express, so a reference for one has to be a sequence too -
* `gpio_ref.c`'s `oracle_gpio_matrix_out` is the same shape.
*/
/* IDF's clock-and-reset LL functions are shadowed by a 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. Same as gpio_ref.c and clkrst_ref.c. */
static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused));
/* `sdmmc_ll_set_command` (sdmmc_ll.h:693-696) calls `memcpy`, and this build's `string.h` is an
* empty stand-in - the register headers need the name to exist, not its contents. Declaring it
* here is enough; the symbol comes from compiler_rt at link time, and at -O2 clang turns a 4-byte
* copy into a single store anyway. */
#include <stdlib.h> /* the shim's `size_t` */
void *memcpy(void *dst, const void *src, size_t n);
#include "hal/sdmmc_ll.h"
#include "soc/sdmmc_struct.h"
/* ------------------------------------------------------------------ clocks and reset */
void oracle_sdmmc_bus_clock(int enable)
{
sdmmc_ll_enable_bus_clock(0, enable != 0);
}
void oracle_sdmmc_reset_register(void)
{
sdmmc_ll_reset_register(0);
}
void oracle_sdmmc_set_host_clock_div(unsigned div)
{
sdmmc_ll_set_clock_div(&SDMMC, div);
}
void oracle_sdmmc_select_clk_source_pll160m(void)
{
sdmmc_ll_select_clk_source(&SDMMC, SDMMC_CLK_SRC_PLL160M);
}
void oracle_sdmmc_init_phase_delay(void)
{
sdmmc_ll_init_phase_delay(&SDMMC);
}
void oracle_sdmmc_set_card_clock_div(unsigned slot, unsigned div)
{
sdmmc_ll_set_card_clock_div(&SDMMC, slot, div);
}
void oracle_sdmmc_enable_card_clock(unsigned slot, int enable)
{
sdmmc_ll_enable_card_clock(&SDMMC, slot, enable != 0);
}
void oracle_sdmmc_enable_card_clock_low_power(unsigned slot, int enable)
{
sdmmc_ll_enable_card_clock_low_power(&SDMMC, slot, enable != 0);
}
/* ------------------------------------------------------------------ controller resets */
void oracle_sdmmc_reset_controller(void)
{
sdmmc_ll_reset_controller(&SDMMC);
}
void oracle_sdmmc_reset_dma(void)
{
sdmmc_ll_reset_dma(&SDMMC);
}
void oracle_sdmmc_reset_fifo(void)
{
sdmmc_ll_reset_fifo(&SDMMC);
}
/* `s_module_reset` plus its completion poll, sd_host_sdmmc.c:917-950. The poll is what makes this
* comparable with the Zig side, which also waits: without it the two could be photographed at
* different points in a self-clearing bit's life. */
void oracle_sdmmc_module_reset(void)
{
sdmmc_ll_reset_controller(&SDMMC);
sdmmc_ll_reset_dma(&SDMMC);
sdmmc_ll_reset_fifo(&SDMMC);
while (!(sdmmc_ll_is_controller_reset_done(&SDMMC) &&
sdmmc_ll_is_dma_reset_done(&SDMMC) &&
sdmmc_ll_is_fifo_reset_done(&SDMMC))) {
/* bounded by the caller: the harness runs this with the bus clock on, where the three bits
* clear in a handful of cycles. */
}
}
/* ------------------------------------------------------------------ transfer geometry */
void oracle_sdmmc_set_card_width(unsigned slot, unsigned width)
{
sdmmc_ll_set_card_width(&SDMMC, slot,
width == 4 ? SD_BUS_WIDTH_4_BIT : SD_BUS_WIDTH_1_BIT);
}
void oracle_sdmmc_set_block_size(unsigned size)
{
sdmmc_ll_set_block_size(&SDMMC, size);
}
void oracle_sdmmc_set_data_transfer_len(unsigned len)
{
sdmmc_ll_set_data_transfer_len(&SDMMC, len);
}
void oracle_sdmmc_set_timeouts(unsigned data_cycles, unsigned response_cycles)
{
sdmmc_ll_set_data_timeout(&SDMMC, data_cycles);
sdmmc_ll_set_response_timeout(&SDMMC, response_cycles);
}
/* No `sdmmc_ll` function exists for this register; see note 1 at the head of the file. */
void oracle_sdmmc_set_fifo_threshold(unsigned rx_wmark, unsigned tx_wmark, unsigned msize)
{
SDMMC.fifoth.rx_wmark = rx_wmark;
SDMMC.fifoth.tx_wmark = tx_wmark;
SDMMC.fifoth.dma_multiple_transaction_size = msize;
}
/* ------------------------------------------------------------------ interrupts and DMA */
/* sd_host_sdmmc.c:120-124, in order: clear everything, mask everything, global off, unmask the
* default set, global on - and then the one thing this project does that ESP-IDF does not: mask
* and clear card detect.
*
* That last pair is a deliberate deviation, so it is expressed here through IDF's own LL rather
* than left to differ. There is no card-detect pin on this board; `configurePins` ties the signal
* to a matrix constant, the transition latches RINTSTS.cd, and nothing in the command path clears
* bit 0 - so an unmasked cd holds the controller's line into the CLIC high forever. Comparing an
* IDF sequence that leaves it unmasked against a Zig one that does not would report a difference
* that is the point rather than a bug; comparing the same intent on both sides still catches a
* wrong bit, a wrong register or a wrong order. */
void oracle_sdmmc_configure_interrupts(void)
{
sdmmc_ll_clear_interrupt(&SDMMC, 0xffffffff);
sdmmc_ll_enable_interrupt(&SDMMC, 0xffffffff, false);
sdmmc_ll_enable_global_interrupt(&SDMMC, false);
sdmmc_ll_enable_interrupt(&SDMMC, SDMMC_LL_EVENT_DEFAULT, true);
sdmmc_ll_enable_interrupt(&SDMMC, SDMMC_LL_EVENT_CD, false);
sdmmc_ll_clear_interrupt(&SDMMC, SDMMC_LL_EVENT_CD);
sdmmc_ll_enable_global_interrupt(&SDMMC, true);
}
void oracle_sdmmc_init_dma(void)
{
sdmmc_ll_init_dma(&SDMMC);
}
void oracle_sdmmc_enable_dma(int enable)
{
sdmmc_ll_enable_dma(&SDMMC, enable != 0);
}
void oracle_sdmmc_set_desc_addr(unsigned addr)
{
sdmmc_ll_set_desc_addr(&SDMMC, addr);
}
void oracle_sdmmc_enable_sdio_interrupt(unsigned slot, int enable)
{
sdmmc_ll_enable_interrupt(&SDMMC, slot == 0 ? SDMMC_LL_EVENT_IO_SLOT0 : SDMMC_LL_EVENT_IO_SLOT1,
enable != 0);
}
/* ------------------------------------------------------------------ the command word */
/* make_hw_cmd (sd_trans_sdmmc.c:190-229) + sd_host_slot_start_command's three additions
* (sd_host_sdmmc.c:859-881), staged with start_command cleared. See note 2 at the head of the
* file for why this one is a transcription.
*
* `data` is 0 for none, 1 for read, 2 for write - the same three-way choice `cmd->data` and
* `SCF_CMD_READ` encode between them. */
void oracle_sdmmc_stage_command(unsigned index, int response_long, int response_expect,
int check_crc, int data, int send_init, int wait_prvdata,
int update_clk, unsigned slot)
{
sdmmc_hw_cmd_t res = { 0 };
res.cmd_index = index;
if (send_init) {
res.send_init = 1;
}
if (wait_prvdata) {
res.wait_complete = 1;
}
if (response_expect) {
res.response_expect = 1;
if (response_long) {
res.response_long = 1;
}
}
if (check_crc) {
res.check_response_crc = 1;
}
if (data) {
res.data_expected = 1;
if (data == 2) {
res.rw = 1;
}
}
if (update_clk) {
res.update_clk_reg = 1;
}
/* sd_host_slot_start_command: "Outputs should be synchronized to cclk_out". */
res.use_hold_reg = 1;
res.card_num = slot;
/* Deliberately *not* res.start_command = 1: staging, not sending. */
res.start_command = 0;
sdmmc_ll_set_command(&SDMMC, res);
}
/* ------------------------------------------------------------------ observation */
unsigned oracle_sdmmc_version_id(void)
{
return sdmmc_ll_get_version_id(&SDMMC);
}
unsigned oracle_sdmmc_hw_config(void)
{
return sdmmc_ll_get_hw_config_info(&SDMMC);
}
|