//! SDMMC's side of the differential test. //! //! Two windows, because this peripheral's state is not contiguous. The controller's own register //! block is at 0x50083000; its *host* clock generator - source mux, two-stage divider, sampling //! phase - is not in it at all, but in HP_SYS_CLKRST, where the P4 moved it. That is the same //! split I2C has (`i2c.clock_suite`), and for the same reason: a driver that programmed every //! register inside the block perfectly and the divider not at all would run the bus at the wrong //! frequency and pass every case in the first suite. //! //! **No case sends a command to the card.** The five `cmd_word_*` cases write the command register //! with `start_command` (bit 31) cleared, which is what makes them safe: bit 31 is the launch, and //! a word without it is inert. The C6 is in reset for the whole of a differ run - GPIO54 is never //! released - so a real CMD52 would sit out its response timeout and prove nothing. What is being //! compared is the encoding, and the encoding is entirely visible in the staged word. //! //! **Restore is the peripheral's own reset**, LP_AON_CLKRST bit 28, which is legitimate here and //! not merely convenient: this block is full of self-clearing and write-1-to-clear bits (the three //! reset bits in CTRL, every bit of RINTSTS, the IDMAC's software reset), and writing a snapshot //! back would trigger a reset rather than undo one. Nothing in this file restores through the code //! under test; the only Zig the harness runs between the two halves is `mmio`. const std = @import("std"); const hal = @import("hal"); const regs = @import("regs"); const mmio = @import("mmio"); const types = @import("differ_types.zig"); extern fn oracle_sdmmc_bus_clock(enable: c_int) void; extern fn oracle_sdmmc_reset_register() void; extern fn oracle_sdmmc_set_host_clock_div(div: c_uint) void; extern fn oracle_sdmmc_select_clk_source_pll160m() void; extern fn oracle_sdmmc_init_phase_delay() void; extern fn oracle_sdmmc_set_card_clock_div(slot: c_uint, div: c_uint) void; extern fn oracle_sdmmc_enable_card_clock(slot: c_uint, enable: c_int) void; extern fn oracle_sdmmc_enable_card_clock_low_power(slot: c_uint, enable: c_int) void; extern fn oracle_sdmmc_reset_controller() void; extern fn oracle_sdmmc_reset_dma() void; extern fn oracle_sdmmc_reset_fifo() void; extern fn oracle_sdmmc_module_reset() void; extern fn oracle_sdmmc_set_card_width(slot: c_uint, width: c_uint) void; extern fn oracle_sdmmc_set_block_size(size: c_uint) void; extern fn oracle_sdmmc_set_data_transfer_len(len: c_uint) void; extern fn oracle_sdmmc_set_timeouts(data_cycles: c_uint, response_cycles: c_uint) void; extern fn oracle_sdmmc_set_fifo_threshold(rx: c_uint, tx: c_uint, msize: c_uint) void; extern fn oracle_sdmmc_configure_interrupts() void; extern fn oracle_sdmmc_init_dma() void; extern fn oracle_sdmmc_enable_dma(enable: c_int) void; extern fn oracle_sdmmc_set_desc_addr(addr: c_uint) void; extern fn oracle_sdmmc_enable_sdio_interrupt(slot: c_uint, enable: c_int) void; extern fn oracle_sdmmc_stage_command( index: c_uint, response_long: c_int, response_expect: c_int, check_crc: c_int, data: c_int, send_init: c_int, wait_prvdata: c_int, update_clk: c_int, slot: c_uint, ) void; extern fn oracle_sdmmc_version_id() c_uint; extern fn oracle_sdmmc_hw_config() c_uint; /// Printed by the harness's caller, so a run records which controller it was talking to. A version /// ID of 0 or 0xffffffff means the block is gated or absent and every result below is noise. pub fn versionId() u32 { return oracle_sdmmc_version_id(); } pub fn hwConfig() u32 { return oracle_sdmmc_hw_config(); } // Force the whole of `hal/sdmmc.zig` through the compiler for the *chip*. // // Zig analyses a function only when something references it, and this is the only build that // compiles that file for riscv32 at all - the plain application never mentions SDMMC, and the // host test root reaches only the pure encoding functions (it cannot reach the rest: reading the // `cycle` CSR does not assemble for x86). So without this list, `cmd53Read`, `cardInit` and the // whole transfer path would be text that has never been type-checked against the target, which is // a bad thing to discover on a board. // // A `-Doracle` build failing here is the intended behaviour: it means the driver does not // compile, and it says so before anything is flashed. comptime { _ = &hal.sdmmc.init; _ = &hal.sdmmc.cardInit; _ = &hal.sdmmc.cmd52Read; _ = &hal.sdmmc.cmd52Write; _ = &hal.sdmmc.cmd53Read; _ = &hal.sdmmc.cmd53Write; _ = &hal.sdmmc.slaveInterruptPending; _ = &hal.sdmmc.clearSlaveInterrupt; _ = &hal.sdmmc.setSlaveInterruptEnabled; _ = &hal.sdmmc.rca; _ = &hal.sdmmc.configurePins; _ = &hal.sdmmc.setBusClock; _ = &hal.sdmmc.dividersFor; _ = &hal.sdmmc.cmd52Arg; _ = &hal.sdmmc.cmd53Arg; _ = hal.sdmmc.interrupt_source; _ = hal.sdmmc.bounce_len; _ = hal.sdmmc.c6_pins; } /// The slot under test. Slot 1 is where the ESP32-C6 is; slot 0's pads are the P4's own flash /// interface on this board and are never touched. const slot: u1 = 1; const cmd_reg = mmio.Reg.atAddress(@intCast(regs.SDHOST_CMD_REG)); /// Stage the word our HAL would send, with the launch bit removed. `hal.sdmmc.commandWord` is the /// code under test; the store is one line and is not. fn stage(c: hal.sdmmc.Command) void { cmd_reg.writeRaw(hal.sdmmc.commandWord(c) & ~(@as(u32, 1) << 31)); } // -------------------------------------------------------------------------- the register block pub const suite: types.Suite = .{ .descriptor = .{ .name = "sdmmc", .base = @intCast(regs.SDHOST_CTRL_REG), // offset 0 of the block // 0x000 through ENSHIFT at +0x110. The window deliberately stops short of BUFFIFO at // +0x200: that is the data FIFO, and a snapshot loop that read it would pop received // words - the same hazard `UART_FIFO_REG` poses at offset 0 of every UART. The three // registers above it (CLK_EDGE_SEL, RAW_INTS, DLL_CLK_CONF at +0x800) belong to the // high-speed delay-line path this driver does not use. .words = 69, .volatile_words = &.{ (0x40 - 0x00) / 4, // MINTSTS - the C6 can raise its SDIO interrupt at any moment (0x44 - 0x00) / 4, // RINTSTS - likewise, and write-1-to-clear (0x48 - 0x00) / 4, // STATUS - FIFO count, FSM state, live DAT levels (0x50 - 0x00) / 4, // CDETECT - a live input (0x54 - 0x00) / 4, // WRTPRT - a live input (0x5c - 0x00) / 4, // TCBCNT - transferred card byte count (0x60 - 0x00) / 4, // TBBCNT - transferred host byte count (0x8c - 0x00) / 4, // IDSTS - IDMAC status, write-1-to-clear (0x94 - 0x00) / 4, // DSCADDR - the IDMAC's current descriptor pointer (0x98 - 0x00) / 4, // BUFADDR - the IDMAC's current buffer pointer }, // Unlike most of this chip, SDMMC powers up with its bus clock *off* // (HP_SYS_CLKRST SOC_CLK_CTRL1 REG_SDMMC_SYS_CLK_EN, default 0), so this check is the one // that catches a setup that silently did not happen: a gated block returns the last value // latched, not zeros, and two such snapshots compare equal while describing nothing. .clock = .{ .reg = @intCast(regs.HP_SYS_CLKRST_SOC_CLK_CTRL1_REG), .bit = @intCast(regs.HP_SYS_CLKRST_REG_SDMMC_SYS_CLK_EN_S), }, // LP_AON_CLKRST.hp_sdmmc_emac_rst_ctrl.rst_en_sdmmc - `sdmmc_ll.h:158-163`. Not in // HP_SYS_CLKRST with almost every other peripheral's reset, which is the single most // surprising fact about this block's clock and reset wiring. .restore = .{ .reset_bit = .{ .reg = @intCast(regs.LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG), .bit = @intCast(regs.LP_CLKRST_RST_EN_SDMMC_S), } }, }, .cases = &.{ // --- resets. Each of the three bits is self-clearing, so what these compare is mostly // that the *other* bits of CTRL come out the same: a reset function that wrote bit 5 // (dma_enable) instead of bit 2 (dma_reset) would leave a trace, and that is exactly the // kind of slip the two undocumented CTRL bits invite. .{ .name = "reset_controller", .idf = idfResetCtl, .ours = ourResetCtl }, .{ .name = "reset_fifo", .idf = idfResetFifo, .ours = ourResetFifo }, .{ .name = "reset_dma", .idf = idfResetDma, .ours = ourResetDma }, .{ .name = "module_reset", .idf = idfModuleReset, .ours = ourModuleReset }, // --- the card clock: CLKDIV, CLKSRC, CLKENA. Divider 0 is bypass (40 MHz through the // host divider alone); divider 20 is the 400 kHz probing setting. .{ .name = "card_clock_div", .arg = 0, .idf = idfCardDiv0, .ours = ourCardDiv0 }, .{ .name = "card_clock_div", .arg = 20, .idf = idfCardDiv20, .ours = ourCardDiv20 }, .{ .name = "card_clock_enable", .arg = 1, .idf = idfCclkOn, .ours = ourCclkOn }, .{ .name = "card_clock_low_power", .arg = 0, .idf = idfLpOff, .ours = ourLpOff }, .{ .name = "card_clock_low_power", .arg = 1, .idf = idfLpOn, .ours = ourLpOn }, // --- bus width. The measured working dump has ctype=0x00000002, i.e. bit 1: slot 1 in // 4-bit mode, which is what `bus_width(4)` must produce and nothing else. .{ .name = "bus_width", .arg = 4, .idf = idfWidth4, .ours = ourWidth4 }, .{ .name = "bus_width", .arg = 1, .idf = idfWidth1, .ours = ourWidth1 }, // --- transfer geometry. .{ .name = "block_size", .arg = 512, .idf = idfBlk512, .ours = ourBlk512 }, .{ .name = "block_size", .arg = 4, .idf = idfBlk4, .ours = ourBlk4 }, .{ .name = "timeouts", .idf = idfTimeouts, .ours = ourTimeouts }, // A deliberately non-default watermark set, so the case is not "both wrote the reset // value". ESP-IDF has no LL function for FIFOTH at all and never writes the register on // any target, so the reference here goes through IDF's `SDMMC.fifoth` bitfields instead - // which is still IDF's definition of where those three fields sit. .{ .name = "fifo_threshold", .arg = 255, .idf = idfFifoth, .ours = ourFifoth }, .{ .name = "fifo_threshold_default", .arg = 511, .idf = idfFifothDefault, .ours = ourFifothDefault }, // --- interrupts and DMA. .{ .name = "configure_interrupts", .idf = idfIntrs, .ours = ourIntrs }, .{ .name = "sdio_interrupt", .arg = 1, .idf = idfSdioIntOn, .ours = ourSdioIntOn }, .{ .name = "init_dma", .idf = idfInitDma, .ours = ourInitDma }, .{ .name = "desc_addr", .idf = idfDescAddr, .ours = ourDescAddr }, // --- command-word encodings. The five commands `cardInit` sends, plus both directions of // CMD53 and the clock update command that is not a command at all. .{ .name = "cmd_word_cmd0", .arg = 0, .idf = idfCmd0, .ours = ourCmd0 }, .{ .name = "cmd_word_cmd5", .arg = 5, .idf = idfCmd5, .ours = ourCmd5 }, .{ .name = "cmd_word_cmd3", .arg = 3, .idf = idfCmd3, .ours = ourCmd3 }, .{ .name = "cmd_word_cmd7", .arg = 7, .idf = idfCmd7, .ours = ourCmd7 }, .{ .name = "cmd_word_cmd52_read", .arg = 52, .idf = idfCmd52R, .ours = ourCmd52R }, .{ .name = "cmd_word_cmd52_write", .arg = 52, .idf = idfCmd52W, .ours = ourCmd52W }, .{ .name = "cmd_word_cmd53_read", .arg = 53, .idf = idfCmd53R, .ours = ourCmd53R }, .{ .name = "cmd_word_cmd53_write", .arg = 53, .idf = idfCmd53W, .ours = ourCmd53W }, .{ .name = "cmd_word_clock_update", .idf = idfCmdClk, .ours = ourCmdClk }, // --- the peripheral reset itself, which is in LP_AON_CLKRST and observable here only by // its effect: configure the block distinctively through IDF's LL on both sides, then let // each implementation reset it. A `resetPeripheral(.sdmmc)` that wrote the wrong bit - // there is no HP_SYS_CLKRST reset for SDMMC, so writing one is the obvious mistake - would // leave the configuration standing. .{ .name = "peripheral_reset", .idf = idfPeriphReset, .ours = ourPeriphReset }, }, .setup = setup, }; /// Bring the block up far enough that its registers are live, and settle the HAL's idea of which /// slot it is driving. /// /// The clock and the reset go through ESP-IDF's LL, not ours: setup runs once, before any case, /// and a setup written with the code under test would hide a broken `clkrst.init(.sdmmc)` behind /// its own success. `hal.sdmmc.init` runs afterwards for a different reason - it is the only way /// to tell the HAL that this is slot 1, and running it here means a bring-up that hangs shows up /// as a stalled suite rather than as a wrong register somewhere later. Its result is discarded: /// every case restores the block by resetting it, so nothing init leaves behind is load-bearing, /// and a card that never answers must not stop the register comparison from running. fn setup() void { oracle_sdmmc_bus_clock(1); oracle_sdmmc_reset_register(); hal.sdmmc.init(.{ .slot = slot, .width = .four, .khz = 40_000 }) catch {}; } fn idfResetCtl() void { oracle_sdmmc_reset_controller(); } fn ourResetCtl() void { mmio.Reg.atAddress(@intCast(regs.SDHOST_CTRL_REG)).modify(.{ mmio.Field.of(regs.SDHOST_CONTROLLER_RESET_S, regs.SDHOST_CONTROLLER_RESET_V).is(1), }); } fn idfResetFifo() void { oracle_sdmmc_reset_fifo(); } fn ourResetFifo() void { mmio.Reg.atAddress(@intCast(regs.SDHOST_CTRL_REG)).modify(.{ mmio.Field.of(regs.SDHOST_FIFO_RESET_S, regs.SDHOST_FIFO_RESET_V).is(1), }); } fn idfResetDma() void { oracle_sdmmc_reset_dma(); } fn ourResetDma() void { mmio.Reg.atAddress(@intCast(regs.SDHOST_CTRL_REG)).modify(.{ mmio.Field.of(regs.SDHOST_DMA_RESET_S, regs.SDHOST_DMA_RESET_V).is(1), }); } fn idfModuleReset() void { oracle_sdmmc_module_reset(); } fn ourModuleReset() void { hal.sdmmc.resetController() catch {}; } fn idfCardDiv0() void { oracle_sdmmc_set_card_clock_div(slot, 0); } fn ourCardDiv0() void { hal.sdmmc.setCardClockDiv(0); } fn idfCardDiv20() void { oracle_sdmmc_set_card_clock_div(slot, 20); } fn ourCardDiv20() void { hal.sdmmc.setCardClockDiv(20); } fn idfCclkOn() void { oracle_sdmmc_enable_card_clock(slot, 1); } fn ourCclkOn() void { hal.sdmmc.setCardClockEnabled(true); } fn idfLpOff() void { oracle_sdmmc_enable_card_clock_low_power(slot, 0); } fn ourLpOff() void { hal.sdmmc.setCardClockLowPower(false); } fn idfLpOn() void { oracle_sdmmc_enable_card_clock_low_power(slot, 1); } fn ourLpOn() void { hal.sdmmc.setCardClockLowPower(true); } fn idfWidth4() void { oracle_sdmmc_set_card_width(slot, 4); } fn ourWidth4() void { hal.sdmmc.setBusWidth(.four); } fn idfWidth1() void { oracle_sdmmc_set_card_width(slot, 1); } fn ourWidth1() void { hal.sdmmc.setBusWidth(.one); } fn idfBlk512() void { oracle_sdmmc_set_block_size(512); oracle_sdmmc_set_data_transfer_len(512); } fn ourBlk512() void { hal.sdmmc.setBlockSize(512); hal.sdmmc.setDataTransferLen(512); } fn idfBlk4() void { // The geometry the measured working dump was taken at: blksiz=4 bytcnt=4, the four-byte // register read ESP-Hosted does to find out how much the slave has queued. oracle_sdmmc_set_block_size(4); oracle_sdmmc_set_data_transfer_len(4); } fn ourBlk4() void { hal.sdmmc.setBlockSize(4); hal.sdmmc.setDataTransferLen(4); } fn idfTimeouts() void { // 100 ms of card clocks at 40 MHz, and the maximum response timeout - `sd_host_sdmmc.c:531-535`. oracle_sdmmc_set_timeouts(100 * 40_000, 255); } fn ourTimeouts() void { hal.sdmmc.setTimeouts(100 * 40_000, 255); } fn idfFifoth() void { oracle_sdmmc_set_fifo_threshold(255, 8, 2); } fn ourFifoth() void { hal.sdmmc.setFifoThreshold(255, 8, 2); } fn idfFifothDefault() void { oracle_sdmmc_set_fifo_threshold(511, 0, 0); } fn ourFifothDefault() void { hal.sdmmc.setFifoThreshold( hal.sdmmc.default_rx_watermark, hal.sdmmc.default_tx_watermark, hal.sdmmc.default_dma_msize, ); } fn idfIntrs() void { oracle_sdmmc_configure_interrupts(); } fn ourIntrs() void { hal.sdmmc.configureInterrupts(); } fn idfSdioIntOn() void { oracle_sdmmc_enable_sdio_interrupt(slot, 1); } fn ourSdioIntOn() void { hal.sdmmc.setSlaveInterruptEnabled(true); } fn idfInitDma() void { oracle_sdmmc_init_dma(); oracle_sdmmc_enable_dma(1); } fn ourInitDma() void { hal.sdmmc.initDma(); hal.sdmmc.setDmaEnabled(true); } /// An address in L2MEM with the low bits set to something a bug would round away: DBADDR ignores /// bits [1:0] internally but stores what is written. const test_desc_addr: u32 = 0x4ff1_0140; fn idfDescAddr() void { oracle_sdmmc_set_desc_addr(test_desc_addr); } fn ourDescAddr() void { hal.sdmmc.setDescriptorAddr(test_desc_addr); } // The command words. Each pair is the same command expressed twice: once through ESP-IDF's // `sdmmc_hw_cmd_t` bitfields, once through this project's `commandWord`. fn idfCmd0() void { oracle_sdmmc_stage_command(0, 0, 0, 0, 0, 1, 0, 0, slot); } fn ourCmd0() void { stage(.{ .index = 0, .send_init = true, .wait_prvdata = false, .slot = slot }); } fn idfCmd5() void { oracle_sdmmc_stage_command(5, 0, 1, 0, 0, 0, 1, 0, slot); } fn ourCmd5() void { stage(.{ .index = 5, .response = .short, .check_crc = false, .slot = slot }); } fn idfCmd3() void { oracle_sdmmc_stage_command(3, 0, 1, 1, 0, 0, 1, 0, slot); } fn ourCmd3() void { stage(.{ .index = 3, .response = .short, .check_crc = true, .slot = slot }); } fn idfCmd7() void { oracle_sdmmc_stage_command(7, 0, 1, 1, 0, 0, 1, 0, slot); } fn ourCmd7() void { stage(.{ .index = 7, .response = .short, .check_crc = true, .slot = slot }); } fn idfCmd52R() void { oracle_sdmmc_stage_command(52, 0, 1, 1, 0, 0, 1, 0, slot); } fn ourCmd52R() void { stage(.{ .index = 52, .response = .short, .check_crc = true, .slot = slot }); } fn idfCmd52W() void { // CMD52 carries its payload in the argument, not in a data phase, so the word is identical to // the read one. Kept as its own case because that is a claim worth checking rather than // assuming: an implementation that set `rw` for a write would fail here and nowhere else. oracle_sdmmc_stage_command(52, 0, 1, 1, 0, 0, 1, 0, slot); } fn ourCmd52W() void { stage(.{ .index = 52, .response = .short, .check_crc = true, .slot = slot }); } fn idfCmd53R() void { oracle_sdmmc_stage_command(53, 0, 1, 1, 1, 0, 1, 0, slot); } fn ourCmd53R() void { stage(.{ .index = 53, .response = .short, .check_crc = true, .data = .read, .slot = slot }); } fn idfCmd53W() void { oracle_sdmmc_stage_command(53, 0, 1, 1, 2, 0, 1, 0, slot); } fn ourCmd53W() void { stage(.{ .index = 53, .response = .short, .check_crc = true, .data = .write, .slot = slot }); } fn idfCmdClk() void { oracle_sdmmc_stage_command(0, 0, 0, 0, 0, 0, 1, 1, slot); } fn ourCmdClk() void { stage(.{ .index = 0, .update_clock = true, .slot = slot }); } /// A configuration distinctive enough that failing to clear it is visible in three registers. fn configureDistinctively() void { oracle_sdmmc_set_card_width(slot, 4); oracle_sdmmc_set_block_size(4); oracle_sdmmc_set_fifo_threshold(255, 8, 2); } fn idfPeriphReset() void { configureDistinctively(); oracle_sdmmc_reset_register(); } fn ourPeriphReset() void { configureDistinctively(); hal.clkrst.resetPeripheral(.sdmmc); } // ------------------------------------------------------------------- the host clock generator /// The other half of "set the bus to 40 MHz", which is not in the SDMMC block. /// /// `HP_SYS_CLKRST.peri_clk_ctrl01` holds the source mux and the gate, `peri_clk_ctrl02` the /// three-edge divider and the driving/sampling phase clocks (`sdmmc_ll.h:212-315`). At 40 MHz the /// host divider is 4 and the card divider is 0, so *all* of the division happens here: an /// implementation that wrote CLKDIV correctly and this register not at all would clock the C6 at /// 160 MHz, which is four times the part's limit and would fail as a wiring problem. pub const clock_suite: types.Suite = .{ .descriptor = .{ .name = "sdmmc_clk", .base = @intCast(regs.HP_SYS_CLKRST_SOC_CLK_CTRL1_REG - 0x18), // block base // 0x00 through PERI_CLK_CTRL03 at +0x3c: SOC_CLK_CTRL0..3 (the bus-clock gates) and // PERI_CLK_CTRL00..03 (the SDIO clock generator). .words = 16, // No clock check: HP_SYS_CLKRST is the block that holds every other block's gate and has // none of its own, and one of the cases below deliberately turns SDMMC's off. .restore = .{ .configure = restoreClocks }, }, .cases = &.{ // Disable first, so "enable" is not a no-op against a restored state that already has it // on - the shape clkrst_cases.zig arrived at for the same reason. .{ .name = "bus_clock", .arg = 0, .idf = idfBusClkOff, .ours = ourBusClkOff }, .{ .name = "host_clock_div", .arg = 4, .idf = idfHostDiv4, .ours = ourHostDiv4 }, .{ .name = "host_clock_div", .arg = 8, .idf = idfHostDiv8, .ours = ourHostDiv8 }, .{ .name = "host_clock_div", .arg = 10, .idf = idfHostDiv10, .ours = ourHostDiv10 }, .{ .name = "select_clk_source", .idf = idfSelectSrc, .ours = ourSelectSrc }, .{ .name = "init_phase_delay", .idf = idfPhase, .ours = ourPhase }, // Last, so the block is left clocked whichever side ran last: every suite after this one // that touches SDMMC depends on it. .{ .name = "bus_clock", .arg = 1, .idf = idfBusClkOn, .ours = ourBusClkOn }, }, }; const soc_clk_ctrl1 = mmio.Reg.atAddress(@intCast(regs.HP_SYS_CLKRST_SOC_CLK_CTRL1_REG)); const peri01 = mmio.Reg.atAddress(@intCast(regs.HP_SYS_CLKRST_PERI_CLK_CTRL01_REG)); const peri02 = mmio.Reg.atAddress(@intCast(regs.HP_SYS_CLKRST_PERI_CLK_CTRL02_REG)); /// Every SDIO field of the three registers this suite's cases touch, back to its reset value - /// and nothing else, because these words also hold the gates and clock muxes of peripherals that /// have nothing to do with SDMMC (MIPI DSI's D-PHY source select is bits 30-31 of PERI_CLK_CTRL02). /// Built from register macros only; nothing here calls the code under test. fn restoreClocks() void { soc_clk_ctrl1.modify(.{ mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDMMC_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SDMMC_SYS_CLK_EN_V).is(1), }); peri01.modify(.{ mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_HS_MODE_S, regs.HP_SYS_CLKRST_REG_SDIO_HS_MODE_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_SRC_SEL_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EN_V).is(0), }); peri02.modify(.{ mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EDGE_L_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EDGE_L_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EDGE_H_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EDGE_H_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EDGE_N_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_CLK_EDGE_N_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_SLF_CLK_EDGE_SEL_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_SLF_CLK_EDGE_SEL_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_DRV_CLK_EDGE_SEL_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_DRV_CLK_EDGE_SEL_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_SAM_CLK_EDGE_SEL_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_SAM_CLK_EDGE_SEL_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_SLF_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_SLF_CLK_EN_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_DRV_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_DRV_CLK_EN_V).is(0), mmio.Field.of(regs.HP_SYS_CLKRST_REG_SDIO_LS_SAM_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SDIO_LS_SAM_CLK_EN_V).is(0), }); } fn idfBusClkOff() void { oracle_sdmmc_bus_clock(0); } fn ourBusClkOff() void { hal.clkrst.setClockEnabled(.sdmmc, false); } fn idfBusClkOn() void { oracle_sdmmc_bus_clock(1); } fn ourBusClkOn() void { hal.clkrst.setClockEnabled(.sdmmc, true); } fn idfHostDiv4() void { oracle_sdmmc_set_host_clock_div(4); } fn ourHostDiv4() void { hal.sdmmc.setHostClockDiv(4); } fn idfHostDiv8() void { oracle_sdmmc_set_host_clock_div(8); } fn ourHostDiv8() void { hal.sdmmc.setHostClockDiv(8); } fn idfHostDiv10() void { oracle_sdmmc_set_host_clock_div(10); } fn ourHostDiv10() void { hal.sdmmc.setHostClockDiv(10); } fn idfSelectSrc() void { oracle_sdmmc_select_clk_source_pll160m(); } fn ourSelectSrc() void { hal.sdmmc.selectPll160m(); } fn idfPhase() void { oracle_sdmmc_init_phase_delay(); } fn ourPhase() void { hal.sdmmc.initPhaseDelay(); }