summaryrefslogtreecommitdiff
path: root/src/oracle/sdmmc_ref.c
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /src/oracle/sdmmc_ref.c
downloadesp32p4-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.c262
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);
+}