diff options
Diffstat (limited to 'src/oracle/sdmmc_cases.zig')
| -rw-r--r-- | src/oracle/sdmmc_cases.zig | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/src/oracle/sdmmc_cases.zig b/src/oracle/sdmmc_cases.zig new file mode 100644 index 0000000..4b53aed --- /dev/null +++ b/src/oracle/sdmmc_cases.zig @@ -0,0 +1,567 @@ +//! 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(); +} |
