//! I2C's side of the differential test: the same operations expressed as ESP-IDF's LL calls and as //! this project's HAL calls. //! //! Two suites, because this peripheral's state lives in two register blocks that are 0x24000 bytes //! apart and the harness compares one window per suite: //! //! * `suite` - the I2C0 block itself (0x500C4000, 128 words). Timing, FIFOs, the command list, //! the filter, the timeout. //! * `clock_suite` - the two HP_SYS_CLKRST words that hold I2C's controller clock: source select, //! clock enable and the divider, for *both* ports (HP_SYS_CLKRST_PERI_CLK_CTRL10/11). Without //! this second window the divider half of `setBusTiming` would be untested, because the divider //! write does not land in the I2C block at all. Registering only the first suite would leave a //! bus that is a factor of `clkm_div` too fast with nothing to notice. //! //! Restore differs between the two, and both choices are forced: //! //! * The I2C block is restored by its **reset bit**. It has three write-to-trigger fields //! (`trans_start`, `fsm_rst`, `conf_upgate`) and a self-setting `command_done` per slot, so //! writing a snapshot back would trigger a transaction. HP_SYS_CLKRST's reset bit is what the //! datasheet defines the reset values against, and I2C0 carries nothing this board needs - no //! console, no flash - so pulsing it is safe. //! * The clock words cannot be reset that way: they are in HP_SYS_CLKRST, not in the I2C block, and //! PERI_CLK_CTRL11 also holds three I2S0_RX clock fields. Restore there is a configure function //! that writes only I2C's own fields back to their documented reset value of zero. //! //! The restore function deliberately builds its field descriptors from the macros itself rather than //! calling into `hal.i2c`: a restore that shared the HAL's idea of where a field lives would agree //! with a HAL that had it wrong, and the case would pass while configuring the wrong bits. Same //! reason `i2c_ref.c` maps command *kinds* to IDF's `I2C_LL_CMD_*` macros instead of taking an //! opcode number from Zig. const std = @import("std"); const hal = @import("hal"); const regs = @import("regs"); const mmio = @import("mmio"); const types = @import("differ_types.zig"); const Reg = mmio.Reg; const Field = mmio.Field; extern fn oracle_i2c_enable_bus_clock(port: c_int, enable: c_int) void; extern fn oracle_i2c_reset_register(port: c_int) void; extern fn oracle_i2c_enable_controller_clock(port: c_int, enable: c_int) void; extern fn oracle_i2c_set_source_clk(port: c_int, src: c_int) void; extern fn oracle_i2c_master_init(port: c_int) void; extern fn oracle_i2c_set_mode_master(port: c_int) void; extern fn oracle_i2c_enable_pins_open_drain(port: c_int, enable_od: c_int) void; extern fn oracle_i2c_update(port: c_int) void; extern fn oracle_i2c_fsm_rst(port: c_int) void; extern fn oracle_i2c_set_bus_timing(port: c_int, source_hz: c_uint, bus_hz: c_uint) void; extern fn oracle_i2c_set_start_timing(port: c_int, setup: c_int, hold: c_int) void; extern fn oracle_i2c_set_stop_timing(port: c_int, setup: c_int, hold: c_int) void; extern fn oracle_i2c_set_sda_timing(port: c_int, sample: c_int, hold: c_int) void; extern fn oracle_i2c_set_tout(port: c_int, tout: c_int) void; extern fn oracle_i2c_set_scl_timeout_us(port: c_int, source_hz: c_uint, timeout_us: c_uint) void; extern fn oracle_i2c_set_filter(port: c_int, filter_num: c_uint) void; extern fn oracle_i2c_txfifo_rst(port: c_int) void; extern fn oracle_i2c_rxfifo_rst(port: c_int) void; extern fn oracle_i2c_enable_fifo_mode(port: c_int, fifo_mode_en: c_int) void; extern fn oracle_i2c_set_fifo_thresholds(port: c_int, tx_empty: c_uint, rx_full: c_uint) void; extern fn oracle_i2c_write_txfifo_pattern(port: c_int, len: c_uint) void; extern fn oracle_i2c_write_cmd( port: c_int, slot: c_int, kind: c_uint, byte_num: c_uint, ack_en: c_int, ack_exp: c_int, ack_val: c_int, ) void; extern fn oracle_i2c_clear_intr_mask(port: c_int, mask: c_uint) void; extern fn oracle_i2c_disable_intr_mask(port: c_int, mask: c_uint) void; extern fn oracle_i2c_get_hw_version(port: c_int) c_uint; extern fn oracle_i2c_cmd_reg_num() c_uint; extern fn oracle_i2c_fifo_len() c_uint; /// ESP-IDF's own view of two chip constants this HAL hard-codes. The harness prints them; a /// disagreement means `hal.i2c.cmd_slots` or `fifo_len` was read out of the wrong chip's header, /// which is a mistake no register comparison would ever show. pub fn idfCmdSlots() u32 { return oracle_i2c_cmd_reg_num(); } pub fn idfFifoLen() u32 { return oracle_i2c_fifo_len(); } pub fn hardwareVersion() u32 { return oracle_i2c_get_hw_version(0); } comptime { // These are constants in both implementations, so they can be checked here rather than on the // die - but only against the *header*, which is why the runtime accessors above exist too. if (hal.i2c.cmd_slots != 8) @compileError("this chip has eight command slots"); if (hal.i2c.fifo_len != 32) @compileError("this chip's I2C FIFO is 32 bytes"); } // There is no module-level "port under test" variable here, unlike the GPIO suite's `pin`, and the // reason is in the descriptors: a `Peripheral` carries one `base` and one `clock`, both constants, // so the I2C-block suite is pinned to I2C0 by construction and running it "for port 1" would need a // second descriptor rather than a variable. Nothing is lost by that, because the only per-port // arithmetic in this peripheral is which HP_SYS_CLKRST field a port's clock lives in - and both // ports' fields are inside `clock_suite`'s two-word window, where the cases name the port directly. /// 40 MHz crystal, which is what `Timing.calculate` is fed on both sides. Not a measurement: the /// board's crystal, and the P4's only XTAL frequency. const source_hz: u32 = hal.i2c.xtal_hz; // --------------------------------------------------------------------------- the I2C0 block fn resetI2c0() void { // Same pulse the harness's `.reset_bit` restore performs, for `setup` to use before the first // case. Interrupt-masked because HP_RST_EN1 holds every peripheral's reset bit. const guard = hal.clkrst.maskInterrupts(); defer guard.release(); const r = Reg.at(regs.HP_SYS_CLKRST_HP_RST_EN1_REG); const bit = @as(u32, 1) << @intCast(regs.HP_SYS_CLKRST_REG_RST_EN_I2C0_S); r.writeRaw(r.raw() | bit); r.writeRaw(r.raw() & ~bit); } /// Bring I2C0 far enough up that its registers are live and its state machine is clocked. /// /// The APB gate defaults to 1 on this chip so the registers are readable from boot, but the /// *controller* clock defaults to 0 - and that one is in HP_SYS_CLKRST, outside the block, so the /// reset-bit restore between cases does not disturb it. fn setupI2c0() void { hal.clkrst.setClockEnabled(.i2c0, true); hal.i2c.setControllerClockEnabled(0, true); resetI2c0(); } pub const suite: types.Suite = .{ .descriptor = .{ .name = "i2c", .base = @intCast(regs.I2C_SCL_LOW_PERIOD_REG(0)), // I2C0 + 0x000 // 128 words = 0x200 bytes, which is the whole instance: configuration and the command list // end at +0x84, the version word is at +0xf8, and the two 32-byte FIFO RAMs are at +0x100 // (TX) and +0x180 (RX). The RAMs are in the window on purpose - a TX FIFO write is otherwise // observable only as a count in I2C_SR, and a count is a much weaker witness than the bytes // themselves. If those words ever turn out to read unstably in FIFO mode - ESP-IDF only ever // touches them in non-FIFO mode - they belong in `volatile_words`, not out of the window. .words = 128, // Reading I2C_DATA_REG pops the RX FIFO. The register header gives no hint of it: the only // field is annotated `HRO` and described as "Rx FIFO read data" (i2c_reg.h:464-474). What // settles it is that `i2c_ll_read_rxfifo` reads this one address `len` times and expects // `len` different bytes (i2c_ll.h:691-697), which is only possible if the read advances the // FIFO - and `i2c_ll_write_txfifo` writes the same address to fill the *other* FIFO // (i2c_ll.h:674-680). Same shape as UART_FIFO_REG. A snapshot loop that reads it would eat // received bytes and desynchronise the read pointer under the case being measured. .no_read = &.{hal.i2c.data_word_offset}, .clock = .{ .reg = @intCast(regs.HP_SYS_CLKRST_SOC_CLK_CTRL2_REG), .bit = @intCast(regs.HP_SYS_CLKRST_REG_I2C0_APB_CLK_EN_S), }, .restore = .{ .reset_bit = .{ .reg = @intCast(regs.HP_SYS_CLKRST_HP_RST_EN1_REG), .bit = @intCast(regs.HP_SYS_CLKRST_REG_RST_EN_I2C0_S), } }, }, .cases = &.{ // ---- bus timing. Five frequencies, chosen for the branches rather than for roundness. // 100 kHz and 400 kHz are the two speeds every device supports; 1 MHz is fast-mode-plus, // where half_cycle is down to 20 source cycles and the minus-one asymmetries dominate; // 50 kHz and 10 kHz are on the other side of the 80 kHz boundary where the scl_wait_high // split changes formula (i2c_ll.h:112-115); and 10 kHz is the one that needs a controller // clock divider greater than 1 - the half that this window cannot see, which is what // `clock_suite` is for. .{ .name = "bus_timing_100k", .arg = 100_000, .idf = idfTiming100k, .ours = ourTiming100k }, .{ .name = "bus_timing_400k", .arg = 400_000, .idf = idfTiming400k, .ours = ourTiming400k }, .{ .name = "bus_timing_1M", .arg = 1_000_000, .idf = idfTiming1M, .ours = ourTiming1M }, .{ .name = "bus_timing_50k", .arg = 50_000, .idf = idfTiming50k, .ours = ourTiming50k }, .{ .name = "bus_timing_10k", .arg = 10_000, .idf = idfTiming10k, .ours = ourTiming10k }, // ---- master bring-up, and the open-drain polarity on its own. .{ .name = "master_init", .idf = idfMasterInit, .ours = ourMasterInit }, .{ .name = "pins_open_drain", .arg = 1, .idf = idfOpenDrainOn, .ours = ourOpenDrainOn }, .{ .name = "pins_push_pull", .arg = 0, .idf = idfOpenDrainOff, .ours = ourOpenDrainOff }, .{ .name = "fifo_mode", .arg = 1, .idf = idfFifoMode, .ours = ourFifoMode }, .{ .name = "nonfifo_mode", .arg = 0, .idf = idfNonFifoMode, .ours = ourNonFifoMode }, // ---- FIFOs. The resets are two stores each (the bit is not self-clearing), so a // half-done reset shows up as a FIFO held in reset rather than as a wrong value. .{ .name = "txfifo_rst", .idf = idfTxFifoRst, .ours = ourTxFifoRst }, .{ .name = "rxfifo_rst", .idf = idfRxFifoRst, .ours = ourRxFifoRst }, .{ .name = "txfifo_write", .arg = 4, .idf = idfWrite4, .ours = ourWrite4 }, .{ .name = "txfifo_write", .arg = 31, .idf = idfWrite31, .ours = ourWrite31 }, .{ .name = "fifo_thresholds", .arg = 8, .idf = idfThresholds, .ours = ourThresholds }, // ---- filter. Three cases because "off" is not "on with a threshold of zero": both enables // default to 1 with zero thresholds, so disabling has to clear the enables and leave the // thresholds alone (i2c_ll.h:753-764). .{ .name = "filter_7", .arg = 7, .idf = idfFilter7, .ours = ourFilter7 }, .{ .name = "filter_15", .arg = 15, .idf = idfFilter15, .ours = ourFilter15 }, .{ .name = "filter_off", .arg = 0, .idf = idfFilter0, .ours = ourFilter0 }, // ---- timeout. The field is five bits and holds an *exponent*: the bus times out after // 2^value source-clock cycles, so 12 is 102 us at 40 MHz and 31 is the largest the register // can hold. The third case goes through the microsecond conversion IDF's driver uses // (i2c_ll.h:1060-1065) for its documented 2000 us default, which comes out as 17. .{ .name = "tout_12", .arg = 12, .idf = idfTout12, .ours = ourTout12 }, .{ .name = "tout_31", .arg = 31, .idf = idfTout31, .ours = ourTout31 }, .{ .name = "scl_timeout_us", .arg = 2000, .idf = idfSclTimeoutUs, .ours = ourSclTimeoutUs }, // ---- the explicit timing setters, where IDF's minus-one convention is least uniform: // start setup as given but start hold minus one, stop and sda both as given. .{ .name = "start_timing", .arg = 7, .idf = idfStartTiming, .ours = ourStartTiming }, .{ .name = "stop_timing", .arg = 5, .idf = idfStopTiming, .ours = ourStopTiming }, .{ .name = "sda_timing", .arg = 11, .idf = idfSdaTiming, .ours = ourSdaTiming }, // ---- the command list, one opcode per slot. The IDF side names the opcode // (`I2C_LL_CMD_*`) and the ours side names it too (`Op.restart`), so the *numbers* are never // passed across: this chip's register header documents the pre-C3 numbering, and a test that // handed the number over would agree with a wrong constant instead of catching it. .{ .name = "cmd_restart", .arg = 0, .idf = idfCmdRestart, .ours = ourCmdRestart }, .{ .name = "cmd_write_ack", .arg = 5, .idf = idfCmdWrite, .ours = ourCmdWrite }, .{ .name = "cmd_read_ack", .arg = 3, .idf = idfCmdReadAck, .ours = ourCmdReadAck }, .{ .name = "cmd_read_nack", .arg = 1, .idf = idfCmdReadNack, .ours = ourCmdReadNack }, .{ .name = "cmd_stop", .arg = 0, .idf = idfCmdStop, .ours = ourCmdStop }, .{ .name = "cmd_end", .arg = 0, .idf = idfCmdEnd, .ours = ourCmdEnd }, .{ .name = "cmd_list_write", .arg = 4, .idf = idfCmdListWrite, .ours = ourCmdListWrite }, // ---- interrupt state. Not an interrupt-driven driver - this HAL polls - but the clear // register is write-1-to-clear, so getting it wrong (a read-modify-write instead of a raw // store) is a class of bug worth one case. .{ .name = "clear_intr", .idf = idfClearIntr, .ours = ourClearIntr }, .{ .name = "disable_intr", .idf = idfDisableIntr, .ours = ourDisableIntr }, }, .setup = setupI2c0, }; // ---- bus timing -------------------------------------------------------------------------------- // Each pair is IDF's calculate-and-write (i2c_hal.c:27-32) against ours (hal.i2c.setBusTiming). The // comparison covers ten in-block registers at once, so a single wrong subtraction anywhere in the // derivation shows up here. fn idfTiming100k() void { oracle_i2c_set_bus_timing(0, source_hz, 100_000); } fn ourTiming100k() void { hal.i2c.setBusTiming(0, source_hz, 100_000); } fn idfTiming400k() void { oracle_i2c_set_bus_timing(0, source_hz, 400_000); } fn ourTiming400k() void { hal.i2c.setBusTiming(0, source_hz, 400_000); } fn idfTiming1M() void { oracle_i2c_set_bus_timing(0, source_hz, 1_000_000); } fn ourTiming1M() void { hal.i2c.setBusTiming(0, source_hz, 1_000_000); } fn idfTiming50k() void { oracle_i2c_set_bus_timing(0, source_hz, 50_000); } fn ourTiming50k() void { hal.i2c.setBusTiming(0, source_hz, 50_000); } fn idfTiming10k() void { oracle_i2c_set_bus_timing(0, source_hz, 10_000); } fn ourTiming10k() void { hal.i2c.setBusTiming(0, source_hz, 10_000); } // ---- bring-up ---------------------------------------------------------------------------------- fn idfMasterInit() void { oracle_i2c_master_init(0); } fn ourMasterInit() void { hal.i2c.initMaster(0); } fn idfOpenDrainOn() void { oracle_i2c_enable_pins_open_drain(0, 1); } fn ourOpenDrainOn() void { hal.i2c.setPinsOpenDrain(0, true); } fn idfOpenDrainOff() void { oracle_i2c_enable_pins_open_drain(0, 0); } fn ourOpenDrainOff() void { hal.i2c.setPinsOpenDrain(0, false); } fn idfFifoMode() void { oracle_i2c_enable_fifo_mode(0, 1); } fn ourFifoMode() void { hal.i2c.setFifoMode(0, true); } fn idfNonFifoMode() void { oracle_i2c_enable_fifo_mode(0, 0); } fn ourNonFifoMode() void { hal.i2c.setFifoMode(0, false); } // ---- FIFOs ------------------------------------------------------------------------------------- fn idfTxFifoRst() void { oracle_i2c_txfifo_rst(0); } fn ourTxFifoRst() void { hal.i2c.resetTxFifo(0); } fn idfRxFifoRst() void { oracle_i2c_rxfifo_rst(0); } fn ourRxFifoRst() void { hal.i2c.resetRxFifo(0); } /// The same pattern `oracle_i2c_write_txfifo_pattern` generates: 0xA0 + i, so every byte differs /// from its neighbours and from the 0x00/0xFF a broken FIFO produces. const pattern: [hal.i2c.fifo_len]u8 = blk: { var p: [hal.i2c.fifo_len]u8 = undefined; for (&p, 0..) |*b, i| b.* = 0xA0 + @as(u8, @intCast(i)); break :blk p; }; fn idfWrite4() void { oracle_i2c_write_txfifo_pattern(0, 4); } fn ourWrite4() void { hal.i2c.writeTxFifo(0, pattern[0..4]); } // 31 bytes rather than 32: one short of full, so the case cannot be passed by a FIFO that silently // wrapped and cannot trip the overflow protection either. fn idfWrite31() void { oracle_i2c_write_txfifo_pattern(0, 31); } fn ourWrite31() void { hal.i2c.writeTxFifo(0, pattern[0..31]); } fn idfThresholds() void { oracle_i2c_set_fifo_thresholds(0, 8, 20); } fn ourThresholds() void { hal.i2c.setFifoThresholds(0, 8, 20); } // ---- filter and timeout ------------------------------------------------------------------------ fn idfFilter7() void { oracle_i2c_set_filter(0, 7); } fn ourFilter7() void { hal.i2c.setFilter(0, 7); } fn idfFilter15() void { oracle_i2c_set_filter(0, 15); } fn ourFilter15() void { hal.i2c.setFilter(0, 15); } fn idfFilter0() void { oracle_i2c_set_filter(0, 0); } fn ourFilter0() void { hal.i2c.setFilter(0, 0); } fn idfTout12() void { oracle_i2c_set_tout(0, 12); } fn ourTout12() void { hal.i2c.setTimeout(0, 12); } fn idfTout31() void { oracle_i2c_set_tout(0, 31); } fn ourTout31() void { hal.i2c.setTimeout(0, 31); } fn idfSclTimeoutUs() void { oracle_i2c_set_scl_timeout_us(0, source_hz, 2000); } fn ourSclTimeoutUs() void { hal.i2c.setTimeout(0, hal.i2c.timeoutExponent(source_hz, 2000)); } // ---- explicit timing setters ------------------------------------------------------------------- // The same registers `applyTiming` writes, but reached by IDF's three narrow setters, whose // minus-one convention is *different* from the one in the calculate-and-write path: start setup as // given and start hold minus one, both stop values as given, both sda values as given // (i2c_ll.h:452-486 against i2c_ll.h:210-217). Numbers with no relation to any real bus frequency, // so a HAL that quietly recomputed them from a frequency instead of writing what it was given would // show up here rather than passing. fn idfStartTiming() void { oracle_i2c_set_start_timing(0, 7, 9); } fn ourStartTiming() void { hal.i2c.setStartTiming(0, 7, 9); } fn idfStopTiming() void { oracle_i2c_set_stop_timing(0, 5, 6); } fn ourStopTiming() void { hal.i2c.setStopTiming(0, 5, 6); } fn idfSdaTiming() void { oracle_i2c_set_sda_timing(0, 11, 3); } fn ourSdaTiming() void { hal.i2c.setSdaTiming(0, 11, 3); } // ---- the command list -------------------------------------------------------------------------- // Kind numbers as `i2c_ref.c` reads them: 0 restart, 1 write, 2 read, 3 stop, 4 end. Only the *kind* // crosses the language boundary; the C side turns it into an opcode with IDF's own macro. const kind_restart: c_uint = 0; const kind_write: c_uint = 1; const kind_read: c_uint = 2; const kind_stop: c_uint = 3; const kind_end: c_uint = 4; fn idfCmdRestart() void { oracle_i2c_write_cmd(0, 0, kind_restart, 0, 0, 0, 0); } fn ourCmdRestart() void { hal.i2c.writeCommand(0, 0, .{ .op = .restart }); } fn idfCmdWrite() void { oracle_i2c_write_cmd(0, 1, kind_write, 5, 1, 0, 0); } fn ourCmdWrite() void { hal.i2c.writeCommand(0, 1, .{ .op = .write, .bytes = 5, .ack_check = true }); } fn idfCmdReadAck() void { oracle_i2c_write_cmd(0, 2, kind_read, 3, 0, 0, 0); } fn ourCmdReadAck() void { hal.i2c.writeCommand(0, 2, .{ .op = .read, .bytes = 3, .ack_value = 0 }); } fn idfCmdReadNack() void { oracle_i2c_write_cmd(0, 3, kind_read, 1, 0, 0, 1); } fn ourCmdReadNack() void { hal.i2c.writeCommand(0, 3, .{ .op = .read, .bytes = 1, .ack_value = 1 }); } fn idfCmdStop() void { oracle_i2c_write_cmd(0, 4, kind_stop, 0, 0, 0, 0); } fn ourCmdStop() void { hal.i2c.writeCommand(0, 4, .{ .op = .stop }); } fn idfCmdEnd() void { oracle_i2c_write_cmd(0, 5, kind_end, 0, 0, 0, 0); } fn ourCmdEnd() void { hal.i2c.writeCommand(0, 5, .{ .op = .end }); } /// A whole list, in the shape `hal.i2c.write` builds for a four-byte transfer: RSTART, WRITE of /// 1 + 4 bytes with ACK checking, STOP. Slots 3 to 7 keep the reset value on both sides. fn idfCmdListWrite() void { oracle_i2c_write_cmd(0, 0, kind_restart, 0, 0, 0, 0); oracle_i2c_write_cmd(0, 1, kind_write, 5, 1, 0, 0); oracle_i2c_write_cmd(0, 2, kind_stop, 0, 0, 0, 0); } fn ourCmdListWrite() void { hal.i2c.writeCommands(0, &.{ .{ .op = .restart }, .{ .op = .write, .bytes = 5, .ack_check = true }, .{ .op = .stop }, }); } // ---- interrupt state --------------------------------------------------------------------------- fn idfClearIntr() void { oracle_i2c_clear_intr_mask(0, hal.i2c.all_interrupts); } fn ourClearIntr() void { hal.i2c.clearInterrupts(0, hal.i2c.all_interrupts); } fn idfDisableIntr() void { oracle_i2c_disable_intr_mask(0, hal.i2c.all_interrupts); } fn ourDisableIntr() void { hal.i2c.disableInterrupts(0); } // ------------------------------------------------------- the clock domain: HP_SYS_CLKRST words // // Field descriptors built here rather than borrowed from hal.i2c, on purpose: the restore function // below must not share the HAL's idea of where these fields live, or a HAL with a field in the wrong // place would be restored consistently with its own mistake and every case would pass. const peri_clk_ctrl10 = Reg.at(regs.HP_SYS_CLKRST_PERI_CLK_CTRL10_REG); const peri_clk_ctrl11 = Reg.at(regs.HP_SYS_CLKRST_PERI_CLK_CTRL11_REG); const i2c0_clock_fields = [_]Field{ Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_SRC_SEL_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_EN_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_EN_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUM_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUM_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUMERATOR_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUMERATOR_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_DENOMINATOR_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_DENOMINATOR_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_SRC_SEL_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_EN_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_EN_V), }; const i2c1_divider_fields = [_]Field{ Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUM_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUM_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUMERATOR_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUMERATOR_V), Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_DENOMINATOR_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_DENOMINATOR_V), }; /// Zero every I2C clock field in the two words, which is their documented reset value /// (hp_sys_clkrst_reg.h: all ten default to 0), leaving everything else in those words alone. /// /// "Everything else" is not empty: PERI_CLK_CTRL11 also holds `REG_I2S0_RX_CLK_EN` and /// `REG_I2S0_RX_CLK_SRC_SEL` in bits 24-26. Restoring by writing a whole word would take I2S0's /// receive clock with it, which is exactly the class of collateral damage the harness's /// no-write-back rule exists to prevent - so this is a masked read-modify-write, interrupt-masked /// because these registers are shared. fn restoreI2cClocks() void { const guard = hal.clkrst.maskInterrupts(); defer guard.release(); var mask10: u32 = 0; for (i2c0_clock_fields) |f| mask10 |= f.mask(); peri_clk_ctrl10.writeRaw(peri_clk_ctrl10.raw() & ~mask10); var mask11: u32 = 0; for (i2c1_divider_fields) |f| mask11 |= f.mask(); peri_clk_ctrl11.writeRaw(peri_clk_ctrl11.raw() & ~mask11); } /// I2C's controller clock: source, gate and divider, for both ports, in two words. /// /// This is the other half of `setBusTiming`. The divider is what keeps `half_cycle` inside the /// nine-bit period fields at low bus frequencies - 10 kHz needs `clkm_div` 4 - so an implementation /// that wrote the timing registers correctly and the divider not at all would produce a bus four /// times too fast and pass every case in the suite above. pub const clock_suite: types.Suite = .{ .descriptor = .{ .name = "i2c_clk", .base = @intCast(regs.HP_SYS_CLKRST_PERI_CLK_CTRL10_REG), // Two words: CTRL10 (all of I2C0's clock fields plus I2C1's source select and gate) and // CTRL11 (I2C1's divider, and three I2S0_RX bits neither side touches). .words = 2, // No reset bit for HP_SYS_CLKRST, and no gate in front of it either: it is the block that // holds every other block's gate. .restore = .{ .configure = restoreI2cClocks }, }, .cases = &.{ .{ .name = "source_xtal", .arg = 0, .idf = idfSourceXtal0, .ours = ourSourceXtal0 }, .{ .name = "source_rc_fast", .arg = 0, .idf = idfSourceRcFast0, .ours = ourSourceRcFast0 }, .{ .name = "source_xtal_p1", .arg = 1, .idf = idfSourceXtal1, .ours = ourSourceXtal1 }, .{ .name = "source_rc_fast_p1", .arg = 1, .idf = idfSourceRcFast1, .ours = ourSourceRcFast1 }, .{ .name = "controller_clock_on", .arg = 0, .idf = idfCtrlClkOn0, .ours = ourCtrlClkOn0 }, .{ .name = "controller_clock_off", .arg = 0, .idf = idfCtrlClkOff0, .ours = ourCtrlClkOff0 }, .{ .name = "controller_clock_on_p1", .arg = 1, .idf = idfCtrlClkOn1, .ours = ourCtrlClkOn1 }, // The divider written by the same calculate-and-write pair as the timing cases, at the two // frequencies either side of where clkm_div stops being 1. .{ .name = "divider_100k", .arg = 100_000, .idf = idfDiv100k, .ours = ourDiv100k }, .{ .name = "divider_10k", .arg = 10_000, .idf = idfDiv10k, .ours = ourDiv10k }, // ... and on port 1, where the divider is in the *other* word from its own source select. .{ .name = "divider_10k_p1", .arg = 10_000, .idf = idfDiv10kP1, .ours = ourDiv10kP1 }, }, .setup = null, }; fn idfSourceXtal0() void { oracle_i2c_set_source_clk(0, 0); } fn ourSourceXtal0() void { hal.i2c.setSource(0, .xtal); } fn idfSourceRcFast0() void { oracle_i2c_set_source_clk(0, 1); } fn ourSourceRcFast0() void { hal.i2c.setSource(0, .rc_fast); } fn idfSourceXtal1() void { oracle_i2c_set_source_clk(1, 0); } fn ourSourceXtal1() void { hal.i2c.setSource(1, .xtal); } fn idfSourceRcFast1() void { oracle_i2c_set_source_clk(1, 1); } fn ourSourceRcFast1() void { hal.i2c.setSource(1, .rc_fast); } fn idfCtrlClkOn0() void { oracle_i2c_enable_controller_clock(0, 1); } fn ourCtrlClkOn0() void { hal.i2c.setControllerClockEnabled(0, true); } fn idfCtrlClkOff0() void { oracle_i2c_enable_controller_clock(0, 0); } fn ourCtrlClkOff0() void { hal.i2c.setControllerClockEnabled(0, false); } fn idfCtrlClkOn1() void { oracle_i2c_enable_controller_clock(1, 1); } fn ourCtrlClkOn1() void { hal.i2c.setControllerClockEnabled(1, true); } fn idfDiv100k() void { oracle_i2c_set_bus_timing(0, source_hz, 100_000); } fn ourDiv100k() void { hal.i2c.setBusTiming(0, source_hz, 100_000); } fn idfDiv10k() void { oracle_i2c_set_bus_timing(0, source_hz, 10_000); } fn ourDiv10k() void { hal.i2c.setBusTiming(0, source_hz, 10_000); } fn idfDiv10kP1() void { oracle_i2c_set_bus_timing(1, source_hz, 10_000); } fn ourDiv10kP1() void { hal.i2c.setBusTiming(1, source_hz, 10_000); }