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