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/sdmmc_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/sdmmc_ref.c')
| -rw-r--r-- | src/oracle/sdmmc_ref.c | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/src/oracle/sdmmc_ref.c b/src/oracle/sdmmc_ref.c new file mode 100644 index 0000000..4cc1e59 --- /dev/null +++ b/src/oracle/sdmmc_ref.c @@ -0,0 +1,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); +} |
