//! I2C0 and I2C1 in master mode, FIFO access, no interrupts and no DMA. //! //! Slave mode, LP_I2C and the RAM (non-FIFO) access path are deliberately absent. //! //! Three things about this peripheral are not visible in the register headers, and each one is a //! way for a port to produce a bus that half-works: //! //! **1. The timing is a dozen registers computed from one number.** SCL low, SCL high, SCL //! wait-high, SDA hold, SDA sample, start hold, restart setup, stop hold, stop setup and the //! timeout exponent all come from a single `half_cycle` derived from the source clock and the wanted //! SCL frequency, and several of them are written *minus one* while two deliberately are not. The //! arithmetic is reproduced from ESP-IDF exactly, with the line numbers, in `Timing.calculate` and //! `applyTiming` below - including the parts that look like bugs and are not. //! //! **2. Nothing takes effect until `CONF_UPGATE` is written.** The timing and control registers feed //! a synchroniser rather than the state machine directly, so a driver that configures the block and //! starts a transaction without `commitConfig()` runs on the *previous* configuration. It is a //! write-to-trigger bit that reads back 0, so nothing about the register state afterwards shows //! whether it was ever written - which is exactly the kind of bug a state-comparing differential //! test cannot see, so it is called out here instead. ESP-IDF puts the call in the driver //! (`esp_driver_i2c/i2c_master.c:96`, `i2c_ll_update` at `i2c_ll.h:137-141`), not in the LL //! functions that write the timing. //! //! **3. The command opcode numbers changed after the original ESP32, and this chip's own register //! header still documents the old ones.** `i2c_struct.h:1009-1021` and `i2c_reg.h:1174-1186` say //! "0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END". ESP-IDF's P4 LL says RESTART=6, WRITE=1, //! READ=3, STOP=2 (`i2c_ll.h:55-59`), which is what every post-ESP32 target uses (esp32c3, esp32c6 //! and esp32p4 agree; only `esp32/include/hal/i2c_ll.h:47-51` has the numbers the P4 header's prose //! describes). The LL is the version the shipping driver runs on silicon, so it is the one here, and //! `i2c_ref.c` builds its command words from IDF's own `I2C_LL_CMD_*` macros so that the //! differential test would catch a wrong constant here rather than agreeing with it. //! //! And one hazard for anything that snapshots this block: **reading `I2C_DATA_REG` pops the RX //! FIFO.** See `Data register` below. const std = @import("std"); const regs = @import("regs"); const mmio = @import("mmio"); const gpio = @import("gpio.zig"); const clkrst = @import("clkrst.zig"); const Reg = mmio.Reg; const Field = mmio.Field; /// HP I2C instances. LP_I2C is a third `i2c_dev_t` in ESP-IDF (`SOC_I2C_NUM` is 3, `soc_caps.h:310`) /// but it lives in the LP domain with its own clock and pad rules, and is out of scope here. pub const port_count: u8 = 2; /// Bytes in each direction. `i2c_ll.h:29` (`I2C_LL_FIFO_LEN`); the RAM behind it is 32 bytes at /// +0x100 (TX) and +0x180 (RX), reachable directly only in non-FIFO mode. pub const fifo_len: u8 = 32; /// Command slots. **Eight on this chip**, not sixteen: `i2c_ll.h:31` says `I2C_LL_CMD_REG_NUM 8`, /// `i2c_struct.h:1073` declares `command[8]`, and the register header stops at `I2C_COMD7_REG` /// (+0x74). ESP-IDF's own `i2c_ll_master_write_cmd_reg` doc comment claims "should be less than 16" /// (`i2c_ll.h:433`) - that comment is stale, and `i2c_ll_master_is_cmd_done` two hundred lines later /// says 8 (`i2c_ll.h:1043`). Eight slots is why the driver's long transfers end a chunk with an END /// opcode and continue: there is no room for a command per byte. pub const cmd_slots: u8 = 8; // ------------------------------------------------------------------------------------ registers // // One array per register, indexed by port. The stride is checked against I2C1's own macro rather // than assumed: `REG_I2C_BASE(i)` is `DR_REG_I2C0_BASE + i * 0x1000` (`soc/esp32p4/include/soc/ // soc.h:24`), which the linker script agrees with (`esp32p4.peripherals.ld:17-18`, I2C0 = // 0x500C4000, I2C1 = 0x500C5000). fn portArray(comptime macro0: anytype, comptime macro1: anytype) type { return mmio.RegArray(macro0, macro1, port_count); } const scl_low_period = portArray(regs.I2C_SCL_LOW_PERIOD_REG(0), regs.I2C_SCL_LOW_PERIOD_REG(1)); const ctr = portArray(regs.I2C_CTR_REG(0), regs.I2C_CTR_REG(1)); const sr = portArray(regs.I2C_SR_REG(0), regs.I2C_SR_REG(1)); const to = portArray(regs.I2C_TO_REG(0), regs.I2C_TO_REG(1)); const fifo_st = portArray(regs.I2C_FIFO_ST_REG(0), regs.I2C_FIFO_ST_REG(1)); const fifo_conf = portArray(regs.I2C_FIFO_CONF_REG(0), regs.I2C_FIFO_CONF_REG(1)); const data = portArray(regs.I2C_DATA_REG(0), regs.I2C_DATA_REG(1)); const int_raw = portArray(regs.I2C_INT_RAW_REG(0), regs.I2C_INT_RAW_REG(1)); const int_clr = portArray(regs.I2C_INT_CLR_REG(0), regs.I2C_INT_CLR_REG(1)); const int_ena = portArray(regs.I2C_INT_ENA_REG(0), regs.I2C_INT_ENA_REG(1)); const sda_hold = portArray(regs.I2C_SDA_HOLD_REG(0), regs.I2C_SDA_HOLD_REG(1)); const sda_sample = portArray(regs.I2C_SDA_SAMPLE_REG(0), regs.I2C_SDA_SAMPLE_REG(1)); const scl_high_period = portArray(regs.I2C_SCL_HIGH_PERIOD_REG(0), regs.I2C_SCL_HIGH_PERIOD_REG(1)); const scl_start_hold = portArray(regs.I2C_SCL_START_HOLD_REG(0), regs.I2C_SCL_START_HOLD_REG(1)); const scl_rstart_setup = portArray(regs.I2C_SCL_RSTART_SETUP_REG(0), regs.I2C_SCL_RSTART_SETUP_REG(1)); const scl_stop_hold = portArray(regs.I2C_SCL_STOP_HOLD_REG(0), regs.I2C_SCL_STOP_HOLD_REG(1)); const scl_stop_setup = portArray(regs.I2C_SCL_STOP_SETUP_REG(0), regs.I2C_SCL_STOP_SETUP_REG(1)); const filter_cfg = portArray(regs.I2C_FILTER_CFG_REG(0), regs.I2C_FILTER_CFG_REG(1)); const comd0 = portArray(regs.I2C_COMD0_REG(0), regs.I2C_COMD0_REG(1)); const scl_sp_conf = portArray(regs.I2C_SCL_SP_CONF_REG(0), regs.I2C_SCL_SP_CONF_REG(1)); /// First address of a port's register block, for the differential harness's window. pub inline fn base(port: u8) u32 { std.debug.assert(port < port_count); return @intCast(scl_low_period.base + scl_low_period.stride * port); } // I2C_CTR_REG. `trans_start`, `fsm_rst` and `conf_upgate` are write-to-trigger: they read back 0, // so a read-modify-write of this register does not re-trigger them. const sda_force_out = Field.of(regs.I2C_SDA_FORCE_OUT_S, regs.I2C_SDA_FORCE_OUT_V); const scl_force_out = Field.of(regs.I2C_SCL_FORCE_OUT_S, regs.I2C_SCL_FORCE_OUT_V); const rx_full_ack_level = Field.of(regs.I2C_RX_FULL_ACK_LEVEL_S, regs.I2C_RX_FULL_ACK_LEVEL_V); const ms_mode = Field.of(regs.I2C_MS_MODE_S, regs.I2C_MS_MODE_V); const trans_start = Field.of(regs.I2C_TRANS_START_S, regs.I2C_TRANS_START_V); const tx_lsb_first = Field.of(regs.I2C_TX_LSB_FIRST_S, regs.I2C_TX_LSB_FIRST_V); const rx_lsb_first = Field.of(regs.I2C_RX_LSB_FIRST_S, regs.I2C_RX_LSB_FIRST_V); const arbitration_en = Field.of(regs.I2C_ARBITRATION_EN_S, regs.I2C_ARBITRATION_EN_V); const fsm_rst = Field.of(regs.I2C_FSM_RST_S, regs.I2C_FSM_RST_V); const conf_upgate = Field.of(regs.I2C_CONF_UPGATE_S, regs.I2C_CONF_UPGATE_V); // I2C_SR_REG, all read-only. const resp_rec = Field.of(regs.I2C_RESP_REC_S, regs.I2C_RESP_REC_V); const arb_lost = Field.of(regs.I2C_ARB_LOST_S, regs.I2C_ARB_LOST_V); const bus_busy = Field.of(regs.I2C_BUS_BUSY_S, regs.I2C_BUS_BUSY_V); const rxfifo_cnt = Field.of(regs.I2C_RXFIFO_CNT_S, regs.I2C_RXFIFO_CNT_V); const txfifo_cnt = Field.of(regs.I2C_TXFIFO_CNT_S, regs.I2C_TXFIFO_CNT_V); // I2C_TO_REG. `time_out_value` is only five bits wide - the timeout is 2^value source-clock cycles, // so 31 is the largest legal exponent and the arithmetic below never approaches it. const time_out_value = Field.of(regs.I2C_TIME_OUT_VALUE_S, regs.I2C_TIME_OUT_VALUE_V); const time_out_en = Field.of(regs.I2C_TIME_OUT_EN_S, regs.I2C_TIME_OUT_EN_V); // I2C_FIFO_CONF_REG. `rx_fifo_rst`/`tx_fifo_rst` are annotated R/W, not self-clearing: they hold // the FIFO in reset until written back to 0, which is why resetting one is two stores. const rxfifo_wm_thrhd = Field.of(regs.I2C_RXFIFO_WM_THRHD_S, regs.I2C_RXFIFO_WM_THRHD_V); const txfifo_wm_thrhd = Field.of(regs.I2C_TXFIFO_WM_THRHD_S, regs.I2C_TXFIFO_WM_THRHD_V); const nonfifo_en = Field.of(regs.I2C_NONFIFO_EN_S, regs.I2C_NONFIFO_EN_V); const rx_fifo_rst = Field.of(regs.I2C_RX_FIFO_RST_S, regs.I2C_RX_FIFO_RST_V); const tx_fifo_rst = Field.of(regs.I2C_TX_FIFO_RST_S, regs.I2C_TX_FIFO_RST_V); const fifo_prt_en = Field.of(regs.I2C_FIFO_PRT_EN_S, regs.I2C_FIFO_PRT_EN_V); // Timing fields. Every period is nine bits ([8:0], max 511) except `scl_wait_high_period`, which is // seven ([15:9], max 127) and shares its register with `scl_high_period`. const scl_low_period_f = Field.of(regs.I2C_SCL_LOW_PERIOD_S, regs.I2C_SCL_LOW_PERIOD_V); const scl_high_period_f = Field.of(regs.I2C_SCL_HIGH_PERIOD_S, regs.I2C_SCL_HIGH_PERIOD_V); const scl_wait_high_period_f = Field.of(regs.I2C_SCL_WAIT_HIGH_PERIOD_S, regs.I2C_SCL_WAIT_HIGH_PERIOD_V); const sda_hold_time = Field.of(regs.I2C_SDA_HOLD_TIME_S, regs.I2C_SDA_HOLD_TIME_V); const sda_sample_time = Field.of(regs.I2C_SDA_SAMPLE_TIME_S, regs.I2C_SDA_SAMPLE_TIME_V); const scl_start_hold_time = Field.of(regs.I2C_SCL_START_HOLD_TIME_S, regs.I2C_SCL_START_HOLD_TIME_V); const scl_rstart_setup_time = Field.of(regs.I2C_SCL_RSTART_SETUP_TIME_S, regs.I2C_SCL_RSTART_SETUP_TIME_V); const scl_stop_hold_time = Field.of(regs.I2C_SCL_STOP_HOLD_TIME_S, regs.I2C_SCL_STOP_HOLD_TIME_V); const scl_stop_setup_time = Field.of(regs.I2C_SCL_STOP_SETUP_TIME_S, regs.I2C_SCL_STOP_SETUP_TIME_V); // I2C_FILTER_CFG_REG. Both thresholds are four bits, both filters default *enabled* with a // threshold of 0 - which filters nothing - so "disable" and "enable with 0" are different words. const scl_filter_thres = Field.of(regs.I2C_SCL_FILTER_THRES_S, regs.I2C_SCL_FILTER_THRES_V); const sda_filter_thres = Field.of(regs.I2C_SDA_FILTER_THRES_S, regs.I2C_SDA_FILTER_THRES_V); const scl_filter_en = Field.of(regs.I2C_SCL_FILTER_EN_S, regs.I2C_SCL_FILTER_EN_V); const sda_filter_en = Field.of(regs.I2C_SDA_FILTER_EN_S, regs.I2C_SDA_FILTER_EN_V); // I2C_SCL_SP_CONF_REG: the hardware bus-clear generator. const scl_rst_slv_en = Field.of(regs.I2C_SCL_RST_SLV_EN_S, regs.I2C_SCL_RST_SLV_EN_V); const scl_rst_slv_num = Field.of(regs.I2C_SCL_RST_SLV_NUM_S, regs.I2C_SCL_RST_SLV_NUM_V); /// Data register offset in words, for the harness's `no_read` list. See `Data register` below. pub const data_word_offset: u32 = (0x1c - 0x00) / 4; // ----------------------------------------------------------------------------- clocks and reset // // I2C has clock control in two places, and the split is not symmetrical between the two ports: // // * the APB bus clock gate and the block reset are in HP_SYS_CLKRST's shared registers, and live // in `clkrst.zig` with every other peripheral's (`i2c_ll.h:149-176`); // * the *controller* clock - the one the bus state machine runs on - its source select and its // divider are I2C-specific fields of HP_SYS_CLKRST_PERI_CLK_CTRL10/11, and are here. // // The asymmetry is the trap: I2C1's source select and controller-clock enable are in PERI_CLK_CTRL10 // beside I2C0's (bits 26 and 27, `i2c_ll.h:851-852` and `i2c_ll.h:944-945`), while I2C1's *divider* // is in PERI_CLK_CTRL11 (`i2c_ll.h:196-199`). Reading the field names alone would put all of I2C1 // in ctrl11. 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_clk_src_sel = Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_SRC_SEL_V); const i2c1_clk_src_sel = Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_SRC_SEL_V); const i2c0_clk_en = Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_EN_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_EN_V); const i2c1_clk_en = Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_EN_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_EN_V); const i2c0_div_num = Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUM_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUM_V); const i2c0_div_numerator = Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUMERATOR_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_NUMERATOR_V); const i2c0_div_denominator = Field.of(regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_DENOMINATOR_S, regs.HP_SYS_CLKRST_REG_I2C0_CLK_DIV_DENOMINATOR_V); const i2c1_div_num = Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUM_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUM_V); const i2c1_div_numerator = Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUMERATOR_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_NUMERATOR_V); const i2c1_div_denominator = Field.of(regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_DENOMINATOR_S, regs.HP_SYS_CLKRST_REG_I2C1_CLK_DIV_DENOMINATOR_V); /// Controller clock source. Two choices on this chip (`clk_tree_defs.h:486-494`), and the register /// field is one bit: 0 = XTAL, 1 = RC_FAST (`i2c_ll.h:848-852`). pub const Source = enum(u1) { /// 40 MHz on this board, and the default. Accurate, which for a bus with a specified maximum /// clock is the whole point. xtal = 0, /// The internal RC oscillator, ~20 MHz and temperature-dependent. Usable only because I2C is a /// clocked bus with no baud-rate agreement to keep. rc_fast = 1, }; /// XTAL frequency on this board, as the source frequency to hand `Timing.calculate` for /// `Source.xtal`. Fixed by the crystal, not by the clock tree: 40 MHz. pub const xtal_hz: u32 = 40_000_000; /// Select the controller clock source. A read-modify-write of a register shared with the other /// port's clock fields, so it takes the interrupt guard. pub fn setSource(port: u8, src: Source) void { std.debug.assert(port < port_count); const v: u32 = @intFromEnum(src); const guard = clkrst.maskInterrupts(); defer guard.release(); peri_clk_ctrl10.modify(.{if (port == 0) i2c0_clk_src_sel.is(v) else i2c1_clk_src_sel.is(v)}); } /// The controller clock gate, which is *not* the APB gate in `clkrst.zig`: registers stay readable /// and writable with this off, and only the bus state machine stops. It defaults to 0 /// (`hp_sys_clkrst_reg.h`, REG_I2C0_CLK_EN default 0), so unlike most peripherals on this chip I2C /// genuinely needs this call before it will do anything. `_i2c_hal_init` (`i2c_hal.c:52-58`) is /// where ESP-IDF makes it. pub fn setControllerClockEnabled(port: u8, on: bool) void { std.debug.assert(port < port_count); const v: u32 = @intFromBool(on); const guard = clkrst.maskInterrupts(); defer guard.release(); peri_clk_ctrl10.modify(.{if (port == 0) i2c0_clk_en.is(v) else i2c1_clk_en.is(v)}); } // -------------------------------------------------------------------------------------- timing /// Everything the bus timing registers need, in source-clock cycles, as ESP-IDF computes it. /// /// The field widths are ESP-IDF's: `i2c_hal_clk_config_t` is nine `uint16_t` /// (`hal/i2c_types.h:46-56`). That matters at the extremes - a value that would exceed 65535 wraps /// there too - and it is why this is `u16` rather than `u32`. pub const Timing = struct { /// Controller clock divider, as a *count*: the register takes this minus one. clkm_div: u16, scl_low: u16, scl_high: u16, scl_wait_high: u16, sda_hold: u16, sda_sample: u16, /// Both the start-condition and the stop-condition setup time. setup: u16, /// Both the start-condition and the stop-condition hold time. hold: u16, /// Timeout *exponent*: the bus times out after 2^tout source-clock cycles. tout: u16, /// Reproduce `i2c_ll_master_cal_bus_clk` (`i2c_ll.h:104-128`) exactly. /// /// The whole derivation, because every line of it is load-bearing: /// /// clkm_div = source / (bus * 1024) + 1 /// sclk = source / clkm_div /// half = sclk / bus / 2 /// /// The `+ 1` is not rounding, it is a floor: the period registers are nine bits, so `half` must /// stay under 512, and dividing the source clock until `sclk <= 1024 * bus` is what guarantees /// it. At 40 MHz that makes `clkm_div` 1 for every bus frequency above 39 kHz and grows it /// below - 10 kHz gives `clkm_div` 4, `sclk` 10 MHz, `half` 500 - so the divider is not an /// optional refinement, it is what makes slow buses representable at all. /// /// From `half`, in source-clock cycles: /// /// scl_low = half /// scl_wait_high = half/2 - 2 if bus >= 80 kHz, else half/4 /// scl_high = half - scl_wait_high /// sda_hold = half/4 /// sda_sample = half/2 /// setup = hold = half /// tout = 32 - clz(5 * half) + 2 /// /// `scl_wait_high` is the part of the high period during which the master waits for the slave to /// release SCL (clock stretching); `scl_high` is the part it drives. They sum to `half`, so the /// nominal frequency is the same either way, and IDF's own comment (`i2c_ll.h:112-114`) records /// why the split changes at 80 kHz: below that, too much wait-high measurably *raises* the /// frequency on real hardware. /// /// The `tout` expression is `log2(5 * half) + 2` written with a count-leading-zeros: a timeout /// of about 20 half-cycles, i.e. ten bit times, rounded up to the next power of two because the /// register holds an exponent. IDF writes it as /// `sizeof(half_cycle) * 8 - __builtin_clz(5 * half_cycle) + 2` with `half_cycle` a `uint32_t`, /// hence the 32 here. /// /// Not reproduced: the `HAL_ASSERT` at `i2c_ll.h:126-127` that /// `scl_wait_high < sda_sample < scl_high`. It holds for every frequency this can be asked for /// at 40 MHz (checked from 10 kHz to 1 MHz), and an assert that cannot fire is noise; the /// ordering it protects is a hardware requirement, not something this code can choose. pub fn calculate(source_hz: u32, bus_hz: u32) Timing { std.debug.assert(bus_hz > 0); std.debug.assert(source_hz / 2 > bus_hz); const clkm_div: u32 = source_hz / (bus_hz * 1024) + 1; const sclk_hz: u32 = source_hz / clkm_div; const half: u32 = sclk_hz / bus_hz / 2; const wait_high: u32 = if (bus_hz >= 80_000) half / 2 - 2 else half / 4; return .{ .clkm_div = @truncate(clkm_div), .scl_low = @truncate(half), .scl_wait_high = @truncate(wait_high), .scl_high = @truncate(half - wait_high), .sda_hold = @truncate(half / 4), .sda_sample = @truncate(half / 2), .setup = @truncate(half), .hold = @truncate(half), // @clz(0) is 32 in Zig where __builtin_clz(0) is undefined in C, so this differs from // IDF only for half == 0, which the assert above rules out. .tout = @truncate(32 - @clz(5 * half) + 2), }; } }; /// Write a computed `Timing` to the peripheral's ten timing registers and the controller-clock /// divider - `i2c_ll_master_set_bus_timing` (`i2c_ll.h:190-220`). /// /// **Which values are written minus one and which are not is the substance of this function.** /// Eight of the ten are `value - 1`, because the hardware counts from zero. `scl_high_period` and /// `scl_wait_high_period` are written as-is, and that asymmetry is deliberate: IDF's comment /// (`i2c_ll.h:201-205`) says the Technical Reference Manual asks for minus one on those two as well, /// and that following it measurably produces an SCL a little *faster* than asked for, so they do not /// subtract. A port that "fixes" this by making all ten consistent gets a bus that is out of spec at /// the top end and passes every test that does not include an oscilloscope. /// /// Subtractions are done in `u32` with wrapping and truncated by the field write, which is what the /// C does for a `uint16_t` of 0 as well - it is unreachable here anyway, since `calculate` asserts /// `half >= 1`. pub fn applyTiming(port: u8, t: Timing) void { std.debug.assert(port < port_count); setClockDivider(port, t.clkm_div); scl_low_period.at(port).modify(.{scl_low_period_f.is(@as(u32, t.scl_low) -% 1)}); // One store where IDF does two read-modify-writes of the same register (`i2c_ll.h:207-208`). // Same final word; a write-trace comparison sees the difference, a state comparison does not. scl_high_period.at(port).modify(.{ scl_high_period_f.is(t.scl_high), scl_wait_high_period_f.is(t.scl_wait_high), }); sda_hold.at(port).modify(.{sda_hold_time.is(@as(u32, t.sda_hold) -% 1)}); sda_sample.at(port).modify(.{sda_sample_time.is(@as(u32, t.sda_sample) -% 1)}); scl_rstart_setup.at(port).modify(.{scl_rstart_setup_time.is(@as(u32, t.setup) -% 1)}); scl_stop_setup.at(port).modify(.{scl_stop_setup_time.is(@as(u32, t.setup) -% 1)}); scl_start_hold.at(port).modify(.{scl_start_hold_time.is(@as(u32, t.hold) -% 1)}); scl_stop_hold.at(port).modify(.{scl_stop_hold_time.is(@as(u32, t.hold) -% 1)}); to.at(port).modify(.{ time_out_value.is(t.tout), time_out_en.is(1) }); } /// Compute and apply the timing for a target SCL frequency. The whole point of the file. /// /// Does **not** commit: call `commitConfig` when the rest of the configuration is in place. That is /// ESP-IDF's division too - `_i2c_hal_set_bus_timing` (`i2c_hal.c:27-32`) is calculate-then-write, /// and the driver commits separately. pub fn setBusTiming(port: u8, source_hz: u32, bus_hz: u32) void { applyTiming(port, Timing.calculate(source_hz, bus_hz)); } /// The controller clock divider: register field is the divider *minus one*, with the fractional /// numerator and denominator zeroed because ESP-IDF does not use them /// (`i2c_ll.h:193-199`, `i2c_ll.h:229-239`). pub fn setClockDivider(port: u8, clkm_div: u16) void { std.debug.assert(port < port_count); const num: u32 = @as(u32, clkm_div) -% 1; const guard = clkrst.maskInterrupts(); defer guard.release(); if (port == 0) { peri_clk_ctrl10.modify(.{ i2c0_div_num.is(num), i2c0_div_numerator.is(0), i2c0_div_denominator.is(0), }); } else { peri_clk_ctrl11.modify(.{ i2c1_div_num.is(num), i2c1_div_numerator.is(0), i2c1_div_denominator.is(0), }); } } // The three narrow timing setters, for tuning one condition without recomputing the whole set - a // slow slave that needs a longer SDA hold, say. // // **These do not use the same convention as `applyTiming`, and that is ESP-IDF's inconsistency, not // a transcription error.** `i2c_ll_master_set_start_timing` writes `scl_rstart_setup = setup` but // `scl_start_hold = hold - 1` (`i2c_ll.h:452-456`); `i2c_ll_master_set_stop_timing` writes both as // given (`i2c_ll.h:467-471`); `i2c_ll_set_sda_timing` writes both as given (`i2c_ll.h:482-486`). // `i2c_ll_master_set_bus_timing`, meanwhile, subtracts one from all six of those // (`i2c_ll.h:210-217`). The reconciliation is that `cal_bus_clk` produces *cycle counts* and these // setters take *register values*, with the single exception of `start_hold` - and IDF's own getters // agree: `i2c_ll_get_start_timing` adds one back to the hold and not to the setup // (`i2c_ll.h:644-648`), while `i2c_ll_get_stop_timing` adds nothing (`i2c_ll.h:659-663`). Anything // tidier here would be a different peripheral configuration from the one IDF produces. pub fn setStartTiming(port: u8, setup: u32, hold: u32) void { std.debug.assert(port < port_count); scl_rstart_setup.at(port).modify(.{scl_rstart_setup_time.is(setup)}); scl_start_hold.at(port).modify(.{scl_start_hold_time.is(hold -% 1)}); } pub fn setStopTiming(port: u8, setup: u32, hold: u32) void { std.debug.assert(port < port_count); scl_stop_setup.at(port).modify(.{scl_stop_setup_time.is(setup)}); scl_stop_hold.at(port).modify(.{scl_stop_hold_time.is(hold)}); } pub fn setSdaTiming(port: u8, sample: u32, hold: u32) void { std.debug.assert(port < port_count); sda_hold.at(port).modify(.{sda_hold_time.is(hold)}); sda_sample.at(port).modify(.{sda_sample_time.is(sample)}); } /// Timeout exponent for a wanted timeout in microseconds - /// `i2c_ll_calculate_timeout_us_to_reg_val` (`i2c_ll.h:1060-1065`). /// /// `32 - clz(cycles_per_us * timeout_us)` is `log2` rounded *up*, which is the only sensible /// direction for a bus timeout. IDF's own default for the SCL timeout is 2000 us /// (`i2c_ll.h:88`). pub fn timeoutExponent(source_hz: u32, timeout_us: u32) u32 { const cycles_per_us = source_hz / 1_000_000; return 32 - @clz(cycles_per_us * timeout_us); } /// Set just the timeout exponent, leaving the enable bit alone - `i2c_ll_set_tout` /// (`i2c_ll.h:358-361`). The field is five bits: 2^31 source cycles is the longest expressible /// timeout, which at 40 MHz is 54 seconds. pub fn setTimeout(port: u8, exponent: u32) void { std.debug.assert(port < port_count); to.at(port).modify(.{time_out_value.is(exponent)}); } pub fn setTimeoutEnabled(port: u8, on: bool) void { std.debug.assert(port < port_count); to.at(port).modify(.{time_out_en.is(@intFromBool(on))}); } /// Glitch filter: pulses shorter than `cycles` source-clock cycles are ignored on both SDA and SCL. /// `cycles == 0` disables both filters - `i2c_ll_master_set_filter` (`i2c_ll.h:753-764`). /// /// Note what "disable" means here: the two enable bits default to 1 with thresholds of 0, so the /// reset state is "filtering enabled, filtering nothing", and disabling is not the same word as /// enabling with a threshold of 0. Passing 0 therefore leaves the thresholds untouched, exactly as /// IDF does, rather than zeroing them - a difference the register comparison would catch. pub fn setFilter(port: u8, cycles: u4) void { std.debug.assert(port < port_count); const r = filter_cfg.at(port); if (cycles > 0) { r.modify(.{ scl_filter_thres.is(cycles), sda_filter_thres.is(cycles), scl_filter_en.is(1), sda_filter_en.is(1), }); } else { r.modify(.{ scl_filter_en.is(0), sda_filter_en.is(0) }); } } // ----------------------------------------------------------------------------------- bring-up /// Put a port into master mode with the defaults ESP-IDF's `i2c_hal_master_init` establishes /// (`i2c_hal.c:39-50`), in the same order. /// /// The four control bits are one store where IDF does five separate read-modify-writes of the same /// register; the resulting word is identical. Each one matters: /// /// * `ms_mode = 1` - master. /// * `sda_force_out = scl_force_out = 0` - open drain. The names are inverted: /// `i2c_ll_enable_pins_open_drain` writes `!enable_od` (`i2c_ll.h:971-975`), so *zero* is /// open-drain and one is push-pull. Push-pull on a shared bus is a short circuit the moment two /// devices disagree, so this is the bit that must not be got backwards. /// * `arbitration_en = 0` - IDF's master init disables arbitration, which defaults to 1. With a /// single master there is nothing to arbitrate, and a false arbitration-lost abort on a noisy /// line is worse than none. /// * `rx_full_ack_level = 0` - ACK, not NACK, when the RX FIFO hits its threshold. /// * `tx_lsb_first = rx_lsb_first = 0` - MSB first, which is what I2C is. /// /// Then both FIFOs are reset, as IDF does, so the block starts with empty FIFOs whatever the /// previous user left behind. pub fn initMaster(port: u8) void { std.debug.assert(port < port_count); ctr.at(port).modify(.{ ms_mode.is(1), sda_force_out.is(0), scl_force_out.is(0), arbitration_en.is(0), rx_full_ack_level.is(0), tx_lsb_first.is(0), rx_lsb_first.is(0), }); resetTxFifo(port); resetRxFifo(port); } /// Latch the configuration into the state machine. Write-to-trigger, self-clearing, and required: /// see note 2 in this file's header. `i2c_ll_update` (`i2c_ll.h:137-141`). pub inline fn commitConfig(port: u8) void { ctr.at(port).modify(.{conf_upgate.is(1)}); } /// Reset the master state machine without touching its configuration. Self-clearing in hardware - /// IDF writes 1 and never writes 0 (`i2c_ll.h:785-789`, "fsm_rst is a self cleared bit"). For a /// master that has hung mid-transaction; the bus itself may still need `clearBus`. pub inline fn resetFsm(port: u8) void { ctr.at(port).modify(.{fsm_rst.is(1)}); } /// Drive up to `pulses` SCL clocks to free a slave that is holding SDA low, then a STOP - /// `i2c_ll_master_clr_bus` (`i2c_ll.h:803-810`). Nine pulses is IDF's default /// (`I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT`, `i2c_ll.h:87`): enough for any slave to finish the /// byte it is stuck in and see a NACK. /// /// The enable bit is cleared *by hardware* when the pulses have been sent, so completion is polled /// through `isBusClearDone`, and `commitConfig` is needed both to start it and, per IDF's comment, /// to resynchronise afterwards. Only meaningful with SCL and SDA actually routed to pads. pub fn clearBus(port: u8, pulses: u5) void { std.debug.assert(port < port_count); scl_sp_conf.at(port).modify(.{ scl_rst_slv_num.is(pulses), scl_rst_slv_en.is(1) }); commitConfig(port); } pub inline fn isBusClearDone(port: u8) bool { return scl_sp_conf.at(port).get(scl_rst_slv_en) == 0; } /// Open-drain or push-pull SCL and SDA, at the peripheral end. /// /// **The register fields are the inverse of this argument.** `i2c_ll_enable_pins_open_drain` writes /// `sda_force_out = scl_force_out = !enable_od` (`i2c_ll.h:971-975`), so a zero in either field is /// what makes that line release instead of driving high. `initMaster` already establishes /// open-drain; this exists to be able to change it, and to have the polarity checked against IDF's /// on its own rather than only as part of a seven-field store. /// /// This is the *peripheral's* driver behaviour. The pad also has an open-drain bit of its own in the /// GPIO block (`gpio.setOpenDrain`), and a real bus needs both: the pad hardware must not drive /// high, and the peripheral must not ask it to. pub fn setPinsOpenDrain(port: u8, open_drain: bool) void { std.debug.assert(port < port_count); const v: u32 = @intFromBool(!open_drain); ctr.at(port).modify(.{ sda_force_out.is(v), scl_force_out.is(v) }); } // --------------------------------------------------------------------------------------- FIFOs /// FIFO or RAM access. FIFO mode is `nonfifo_en = 0`, i.e. the field is the inverse of the name of /// this function - `i2c_ll_enable_fifo_mode` (`i2c_ll.h:345-348`). pub fn setFifoMode(port: u8, fifo: bool) void { std.debug.assert(port < port_count); fifo_conf.at(port).modify(.{nonfifo_en.is(@intFromBool(!fifo))}); } /// Hold the TX FIFO in reset, then release it. Two stores, because the bit is plain R/W and not /// self-clearing: writing only the 1 leaves the FIFO permanently reset and every subsequent /// transmission silently empty (`i2c_ll.h:248-253`). pub fn resetTxFifo(port: u8) void { std.debug.assert(port < port_count); const r = fifo_conf.at(port); r.modify(.{tx_fifo_rst.is(1)}); r.modify(.{tx_fifo_rst.is(0)}); } pub fn resetRxFifo(port: u8) void { std.debug.assert(port < port_count); const r = fifo_conf.at(port); r.modify(.{rx_fifo_rst.is(1)}); r.modify(.{rx_fifo_rst.is(0)}); } /// FIFO watermark thresholds, and the two side effects ESP-IDF attaches to setting them. /// /// `fifo_prt_en` gates the watermark interrupts *and* the overflow/underflow protection /// (`i2c_reg.h:449-459`), and IDF sets it in both threshold setters /// (`i2c_ll.h:496-500` and `i2c_ll.h:510-515`), so it is set here rather than left to the caller. /// /// The other side effect is less obvious and is copied deliberately: IDF's /// `i2c_ll_set_rxfifo_full_thr` also writes `ctr.rx_full_ack_level = 0`, in a different register. /// That is coherent rather than sloppy - an RX threshold means "ACK up to here", and a master that /// NACKed at the threshold would end the transfer instead of pausing it - but it means this /// operation touches two registers, and after a peripheral reset (where `rx_full_ack_level` defaults /// to 1) leaving it out is an observable difference rather than a stylistic one. pub fn setFifoThresholds(port: u8, tx_empty: u5, rx_full: u5) void { std.debug.assert(port < port_count); fifo_conf.at(port).modify(.{ fifo_prt_en.is(1), txfifo_wm_thrhd.is(tx_empty), rxfifo_wm_thrhd.is(rx_full), }); ctr.at(port).modify(.{rx_full_ack_level.is(0)}); } // ------------------------------------------------------------------------------- Data register // // **Reading `I2C_DATA_REG` pops the RX FIFO.** The register header does not say so - it annotates // the single field `I2C_FIFO_RDATA` as `HRO` and describes the register as "Rx FIFO read data" // (`i2c_reg.h:464-474`) - but ESP-IDF's LL settles it: `i2c_ll_read_rxfifo` reads *the same address* // `len` times into successive bytes of a buffer (`i2c_ll.h:691-697`), which can only produce // distinct bytes if each read advances the FIFO. The write direction is the same address for the // other FIFO: `i2c_ll_write_txfifo` stores `len` bytes to `hw->data.val` (`i2c_ll.h:674-680`). One // address, two FIFOs, both with side effects - the same shape as `UART_FIFO_REG`, and the reason // this offset is in the differential harness's `no_read` list. /// Push bytes into the TX FIFO. In FIFO mode each store is one byte into the FIFO regardless of the /// width of the access; the FIFO is `fifo_len` deep and there is no flow control here, so the caller /// must not exceed `txSpace`. pub fn writeTxFifo(port: u8, bytes: []const u8) void { std.debug.assert(port < port_count); std.debug.assert(bytes.len <= fifo_len); const r = data.at(port); for (bytes) |b| r.writeRaw(b); } /// Pop bytes out of the RX FIFO. Destructive by construction - see above. pub fn readRxFifo(port: u8, out: []u8) void { std.debug.assert(port < port_count); const r = data.at(port); for (out) |*b| b.* = @truncate(r.raw()); } /// Bytes waiting in the RX FIFO. pub inline fn rxCount(port: u8) u32 { return sr.at(port).get(rxfifo_cnt); } /// Bytes queued in the TX FIFO. pub inline fn txCount(port: u8) u32 { return sr.at(port).get(txfifo_cnt); } /// Room left in the TX FIFO, saturating at 0 the way `i2c_ll_get_txfifo_len` does /// (`i2c_ll.h:604-608`) - the counter can read `fifo_len` and the subtraction must not wrap. pub inline fn txSpace(port: u8) u32 { const used = txCount(port); return if (used >= fifo_len) 0 else fifo_len - used; } pub inline fn isBusBusy(port: u8) bool { return sr.at(port).get(bus_busy) == 1; } // -------------------------------------------------------------------------------- command list // // A transaction is up to eight commands written into I2C_COMD0..7 and then triggered as a unit. The // register header exposes each slot as a single 14-bit field `I2C_COMMANDn` plus a `_DONE` bit at 31 // and stops there: the sub-fields exist only in `i2c_ll_hw_cmd_t` (`i2c_ll.h:41-52`). So this is one // of the few places where the field geometry cannot come from a macro pair, and the comptime check // below is what keeps that honest - the five sub-fields must tile exactly the bits the header calls // I2C_COMMANDn. const cmd_byte_num = Field.of(0, 0xff); const cmd_ack_en = Field.bit(8); const cmd_ack_exp = Field.bit(9); const cmd_ack_val = Field.bit(10); const cmd_op_code = Field.of(11, 0x7); const cmd_done = Field.of(regs.I2C_COMMAND0_DONE_S, regs.I2C_COMMAND0_DONE_V); comptime { const command_field = Field.of(regs.I2C_COMMAND0_S, regs.I2C_COMMAND0_V); const tiled = cmd_byte_num.mask() | cmd_ack_en.mask() | cmd_ack_exp.mask() | cmd_ack_val.mask() | cmd_op_code.mask(); if (tiled != command_field.mask()) @compileError( "the command sub-fields from i2c_ll.h do not tile I2C_COMMAND0 - one of the two headers moved", ); if (cmd_done.mask() & command_field.mask() != 0) @compileError("command done bit overlaps the command"); } /// Opcodes, from `i2c_ll.h:55-59`. **Not** the numbers this chip's own register header describes - /// see note 3 in the file header. pub const Op = enum(u3) { write = 1, stop = 2, read = 3, /// Hand the command list back to software with the bus still held, so the next chunk can be /// loaded. This is how a transfer longer than eight commands or 32 bytes is done without DMA. end = 4, /// START, and equally a repeated START. restart = 6, }; /// One command slot as a value rather than a raw word. /// /// The three ACK fields only mean something for one direction each, which is why they are separate /// rather than one "ack" number: /// /// * `ack_check` (WRITE) - compare the ACK bit the slave returns against `ack_expected` and abort /// the list if it differs. This is what turns a missing device into a NACK error instead of a /// transfer into the void. /// * `ack_value` (READ) - the ACK bit this master sends after each byte it reads. Zero (ACK) for /// every byte but the last, one (NACK) for the last, which is how a slave is told to stop /// driving the bus. pub const Command = struct { op: Op, /// Bytes to move. Only WRITE and READ use it; a READ of n bytes is one command, not n. bytes: u8 = 0, ack_check: bool = false, ack_expected: u1 = 0, ack_value: u1 = 0, pub inline fn encode(self: Command) u32 { return (@as(u32, self.bytes) << cmd_byte_num.shift) | (@as(u32, @intFromBool(self.ack_check)) << cmd_ack_en.shift) | (@as(u32, self.ack_expected) << cmd_ack_exp.shift) | (@as(u32, self.ack_value) << cmd_ack_val.shift) | (@as(u32, @intFromEnum(self.op)) << cmd_op_code.shift); } }; /// One command slot. The slot stride is checked against the header's own COMD1 macro rather than /// assumed to be 4. inline fn cmdReg(port: u8, slot: u8) Reg { std.debug.assert(slot < cmd_slots); const stride = comptime mmio.addr(regs.I2C_COMD1_REG(0)) - mmio.addr(regs.I2C_COMD0_REG(0)); comptime { // ... and the array is contiguous all the way to the last slot. if (mmio.addr(regs.I2C_COMD7_REG(0)) != mmio.addr(regs.I2C_COMD0_REG(0)) + stride * 7) @compileError("the command registers are not a contiguous array of 8"); } return Reg.atAddress(comd0.at(port).address + stride * slot); } /// Write a command into a slot. A whole-word store, as IDF's `i2c_ll_master_write_cmd_reg` does /// (`i2c_ll.h:437-441`): it is the one register here where establishing the entire word is right, /// because the `done` bit must go back to 0 for the slot to be waited on again. pub fn writeCommand(port: u8, slot: u8, cmd: Command) void { std.debug.assert(port < port_count); cmdReg(port, slot).writeRaw(cmd.encode()); } /// Load a whole command list, in order. Any slot the list does not reach keeps whatever it held - /// which is harmless, because the sequencer stops at the STOP or END that the list must contain. pub fn writeCommands(port: u8, cmds: []const Command) void { std.debug.assert(cmds.len <= cmd_slots); for (cmds, 0..) |c, i| writeCommand(port, @intCast(i), c); } /// Whether the sequencer has finished a slot. Set by hardware (`R/W/SS`), cleared by writing the /// slot again. `i2c_ll_master_is_cmd_done` (`i2c_ll.h:1047-1051`). pub inline fn isCommandDone(port: u8, slot: u8) bool { return cmdReg(port, slot).get(cmd_done) == 1; } // --------------------------------------------------------------------------------- transactions // The master event bits, in I2C_INT_RAW/I2C_INT_ST/I2C_INT_CLR - the same bit numbers in all three // (`i2c_ll.h:61-70`). Reading INT_RAW is safe: the bits are `R/SS/WTC`, set by hardware and cleared // only by writing a 1 to the same position in INT_CLR, so polling does not consume them. Writing // INT_CLR is the one place in this file that must be `writeRaw` rather than `modify`. const int_trans_complete = Field.of(regs.I2C_TRANS_COMPLETE_INT_RAW_S, regs.I2C_TRANS_COMPLETE_INT_RAW_V); const int_end_detect = Field.of(regs.I2C_END_DETECT_INT_RAW_S, regs.I2C_END_DETECT_INT_RAW_V); const int_nack = Field.of(regs.I2C_NACK_INT_RAW_S, regs.I2C_NACK_INT_RAW_V); const int_arbitration_lost = Field.of(regs.I2C_ARBITRATION_LOST_INT_RAW_S, regs.I2C_ARBITRATION_LOST_INT_RAW_V); const int_time_out = Field.of(regs.I2C_TIME_OUT_INT_RAW_S, regs.I2C_TIME_OUT_INT_RAW_V); const int_scl_st_to = Field.of(regs.I2C_SCL_ST_TO_INT_RAW_S, regs.I2C_SCL_ST_TO_INT_RAW_V); const int_scl_main_st_to = Field.of(regs.I2C_SCL_MAIN_ST_TO_INT_RAW_S, regs.I2C_SCL_MAIN_ST_TO_INT_RAW_V); /// The mask ESP-IDF uses for "all interrupts" - `I2C_LL_INTR_MASK`, `i2c_ll.h:1097`. /// /// It is 14 bits, and this block has 19 (`I2C_SLAVE_ADDR_UNMATCH_INT` is bit 18). The five it leaves /// out are slave-mode and general-call events, which is presumably why IDF's mask stops where it /// does; the value is IDF's rather than a recount so that clearing "everything" means the same thing /// on both sides of the differential. pub const all_interrupts: u32 = 0x3fff; /// Clear interrupt flags. Write-1-to-clear, so this is a raw store of a mask and never a /// read-modify-write: reading INT_RAW and writing it back would clear whatever had arrived in /// between and nothing else. pub inline fn clearInterrupts(port: u8, mask: u32) void { int_clr.at(port).writeRaw(mask); } /// Mask every interrupt at the peripheral. This HAL polls; nothing here reaches the CLIC. /// /// A whole-word zero rather than IDF's `int_ena &= ~mask` (`i2c_ll.h:305-309`), so it also covers /// the five slave-mode bits outside `all_interrupts`. Reaching the same word from a block whose /// `int_ena` reset value is 0 either way, which is why the differential case for it agrees. pub inline fn disableInterrupts(port: u8) void { int_ena.at(port).writeRaw(0); } /// How a triggered command list ended. pub const Outcome = enum { /// The list ran to its STOP. complete, /// The list hit an END opcode: the bus is still held and the next chunk can be loaded. end_detect, /// A slave did not acknowledge. The usual meaning is "nothing at that address". nack, /// Another master won the bus. Only possible with `arbitration_en` set, which `initMaster` /// clears. arbitration_lost, /// SCL was held low past the configured timeout - `I2C_TO_REG`. Almost always a slave holding /// the clock, or no pull-up on the line at all. timeout, /// The SCL state machine stalled: `scl_st_to` or `scl_main_st_to`. IDF's driver treats this as /// the signal that a bus deadlock may have happened and `clearBus` is worth trying /// (`i2c_ll.h:795`). stalled, /// Nothing had happened yet. pending, }; /// Trigger the loaded command list. Write-to-trigger; the bit reads back 0, so this leaves no trace /// in a register snapshot. `i2c_ll_start_trans` (`i2c_ll.h:629-633`). pub inline fn startTransaction(port: u8) void { ctr.at(port).modify(.{trans_start.is(1)}); } /// Read the outcome so far from one load of INT_RAW. /// /// Errors are reported ahead of completion, and in the order they matter: an arbitration loss or a /// NACK can be raised in the same word as `trans_complete`, and calling that transaction complete /// is how a driver comes to believe a device answered when it did not. pub fn outcome(port: u8) Outcome { const raw = int_raw.at(port).raw(); if (raw & int_arbitration_lost.mask() != 0) return .arbitration_lost; if (raw & int_nack.mask() != 0) return .nack; if (raw & int_time_out.mask() != 0) return .timeout; if (raw & (int_scl_st_to.mask() | int_scl_main_st_to.mask()) != 0) return .stalled; if (raw & int_trans_complete.mask() != 0) return .complete; if (raw & int_end_detect.mask() != 0) return .end_detect; return .pending; } /// Spin until the transaction resolves. Returns `.pending` if it never does, rather than hanging: /// a bus with no pull-up produces exactly that, and it is a fault to report rather than a board to /// power-cycle. /// /// `spins` is a loop count, not a time. At the ~90 MHz this board boots at, a 100 kHz transfer of a /// few bytes needs on the order of 10^4 iterations of this loop; the default of 200,000 leaves an /// order of magnitude of headroom and still returns in well under a second. pub fn waitTransaction(port: u8, spins: u32) Outcome { var n: u32 = 0; while (n < spins) : (n += 1) { const o = outcome(port); if (o != .pending) return o; } return .pending; } /// The status register's own error bits, which are not the interrupt flags: `resp_rec` is the last /// ACK level *received* and `arb_lost` is the state machine's own latch. Both are read-only and /// survive an interrupt clear, so they are what to look at when diagnosing a transfer after the fact. pub const Status = struct { /// The ACK bit the slave last returned: 0 = ACK, 1 = NACK. last_ack: u1, arbitration_lost: bool, bus_busy: bool, rx_bytes: u32, tx_bytes: u32, }; pub fn status(port: u8) Status { const raw = sr.at(port).raw(); return .{ .last_ack = @intCast((raw >> resp_rec.shift) & 1), .arbitration_lost = raw & arb_lost.mask() != 0, .bus_busy = raw & bus_busy.mask() != 0, .rx_bytes = (raw >> rxfifo_cnt.shift) & rxfifo_cnt.unshiftedMask(), .tx_bytes = (raw >> txfifo_cnt.shift) & txfifo_cnt.unshiftedMask(), }; } // ------------------------------------------------------------------------------------ the pads // // I2C is a two-wire open-drain bus and the P4 reaches it only through the GPIO matrix: there is no // IO MUX function for I2C on any pad, so both signals go out through `matrixOut` and come back in // through `matrixIn`. Both directions are needed even for a write-only master - the master samples // SDA to read the slave's ACK, and samples SCL to detect stretching - which is why every pad here // gets its input buffer enabled as well as its driver. /// The GPIO matrix signal indices for a port, from ESP-IDF's own signal map /// (`gpio_sig_map.h:141-148`) via `i2c_periph.c`. On this chip a signal's input and output index /// happen to be the same number, which is not true on every part and is not something to rely on. pub fn sclSignal(port: u8) u32 { return switch (port) { 0 => regs.I2C0_SCL_PAD_OUT_IDX, else => regs.I2C1_SCL_PAD_OUT_IDX, }; } pub fn sdaSignal(port: u8) u32 { return switch (port) { 0 => regs.I2C0_SDA_PAD_OUT_IDX, else => regs.I2C1_SDA_PAD_OUT_IDX, }; } /// Route SCL and SDA to two pads, open-drain, following `i2c_common_set_pins` /// (`esp_driver_i2c/i2c_common.c:318-345`) step for step. /// /// **The internal pull-ups are not enough for a real bus.** They are on the order of 45 kOhm, which /// with a few tens of picofarads of trace and device capacitance gives a rise time far past the /// 1 us that 100 kHz I2C allows. ESP-IDF says the same thing in its own driver documentation and /// enables them anyway as a convenience for a single device on a short wire. A bus that is expected /// to work needs external resistors - 4.7 kOhm to 3.3 V is the usual choice at 100 kHz, 2.2 kOhm at /// 400 kHz - and then `internal_pullups` should be false, because two resistors in parallel is not /// what either calculation assumed. /// /// The order matters in one place: the pad is driven high *before* its output is enabled, so /// enabling the driver cannot pull the bus low for the few cycles before the peripheral takes over. /// A low SCL glitch is a clock edge to every device on the bus. pub fn configurePins(port: u8, scl_pin: u8, sda_pin: u8, opts: struct { internal_pullups: bool = false, }) void { std.debug.assert(port < port_count); for ([_]struct { pin: u8, signal: u32 }{ .{ .pin = scl_pin, .signal = sclSignal(port) }, .{ .pin = sda_pin, .signal = sdaSignal(port) }, }) |wire| { gpio.setHigh(wire.pin); gpio.setInputEnable(wire.pin, true); gpio.setOpenDrain(wire.pin, true); gpio.setPull(wire.pin, if (opts.internal_pullups) .up else .none); gpio.matrixOut(wire.pin, wire.signal); gpio.matrixIn(wire.pin, wire.signal); } } // ------------------------------------------------------------------------------- transfers /// Bring a port up as a master on a given bus frequency, in the order the hardware requires: /// clocks, then reset, then configuration, then commit. /// /// Reset before configure, because a reset drops everything configured before it. `clkrst.init` /// does the gate-then-reset pair; the controller clock is separate and enabled after, since it only /// feeds the state machine. pub fn init(port: u8, opts: struct { source: Source = .xtal, source_hz: u32 = xtal_hz, bus_hz: u32 = 100_000, /// Glitch filter width in source-clock cycles. ESP-IDF's driver default is 7. filter_cycles: u4 = 7, }) void { std.debug.assert(port < port_count); switch (port) { 0 => clkrst.init(.i2c0), else => clkrst.init(.i2c1), } setControllerClockEnabled(port, true); setSource(port, opts.source); initMaster(port); setFifoMode(port, true); disableInterrupts(port); clearInterrupts(port, all_interrupts); setBusTiming(port, opts.source_hz, opts.bus_hz); setFilter(port, opts.filter_cycles); commitConfig(port); } /// Default spin budget for `write`/`read`. See `waitTransaction`. pub const default_spins: u32 = 200_000; /// Write `bytes` to a 7-bit address as one command list. /// /// RSTART | WRITE (1 + len bytes, ack checked) | STOP /// /// The address byte goes in the TX FIFO ahead of the data and is counted in the WRITE command's byte /// count: to the sequencer the address is just the first byte written after a START. `ack_check` is /// on, so a missing device comes back as `.nack` rather than as a successful write into nothing. /// /// One command list, one FIFO load: at most `fifo_len - 1` = 31 data bytes. Longer transfers need /// the END-and-continue loop that ESP-IDF's driver runs from its interrupt handler, which is out of /// scope here - hence the assert rather than a partial write. pub fn write(port: u8, address: u7, bytes: []const u8, spins: u32) Outcome { std.debug.assert(bytes.len < fifo_len); resetTxFifo(port); resetRxFifo(port); clearInterrupts(port, all_interrupts); writeTxFifo(port, &[_]u8{@as(u8, address) << 1}); writeTxFifo(port, bytes); writeCommands(port, &.{ .{ .op = .restart }, .{ .op = .write, .bytes = @intCast(bytes.len + 1), .ack_check = true }, .{ .op = .stop }, }); commitConfig(port); startTransaction(port); return waitTransaction(port, spins); } /// Read into `out` from a 7-bit address as one command list. /// /// RSTART | WRITE 1 (address|read, ack checked) | READ n-1 sending ACK | READ 1 sending NACK | STOP /// /// The last byte is a separate command because its ACK bit differs: a master that ACKs the final /// byte tells the slave to keep going, and the slave then holds SDA for a byte that will never be /// clocked out. That is the classic I2C read bug, and it is a *command list* bug - which is why the /// split is here rather than being something the caller can get wrong. /// /// Reads of one byte collapse to a single NACKed READ, so the list is four commands instead of five. pub fn read(port: u8, address: u7, out: []u8, spins: u32) Outcome { std.debug.assert(out.len > 0); std.debug.assert(out.len <= fifo_len); resetTxFifo(port); resetRxFifo(port); clearInterrupts(port, all_interrupts); writeTxFifo(port, &[_]u8{(@as(u8, address) << 1) | 1}); writeCommand(port, 0, .{ .op = .restart }); writeCommand(port, 1, .{ .op = .write, .bytes = 1, .ack_check = true }); var slot: u8 = 2; if (out.len > 1) { writeCommand(port, slot, .{ .op = .read, .bytes = @intCast(out.len - 1), .ack_value = 0 }); slot += 1; } writeCommand(port, slot, .{ .op = .read, .bytes = 1, .ack_value = 1 }); writeCommand(port, slot + 1, .{ .op = .stop }); commitConfig(port); startTransaction(port); const result = waitTransaction(port, spins); if (result == .complete) readRxFifo(port, out); return result; } test "the timing arithmetic reproduces ESP-IDF's, including where it looks wrong" { // 100 kHz on a 40 MHz XTAL: the case every I2C device supports, worked through by hand from // i2c_ll.h:104-128. clkm_div = 40e6/(100e3*1024) + 1 = 0 + 1 = 1, so sclk stays 40 MHz and // half = 40e6/100e3/2 = 200. const t100 = Timing.calculate(40_000_000, 100_000); try std.testing.expectEqual(@as(u16, 1), t100.clkm_div); try std.testing.expectEqual(@as(u16, 200), t100.scl_low); try std.testing.expectEqual(@as(u16, 98), t100.scl_wait_high); // half/2 - 2 try std.testing.expectEqual(@as(u16, 102), t100.scl_high); // half - wait_high try std.testing.expectEqual(@as(u16, 50), t100.sda_hold); try std.testing.expectEqual(@as(u16, 100), t100.sda_sample); try std.testing.expectEqual(@as(u16, 200), t100.setup); try std.testing.expectEqual(@as(u16, 200), t100.hold); // 5*200 = 1000, which needs 10 bits, so 32 - 22 + 2 = 12: a timeout of 2^12 = 4096 cycles, // 102 us at 40 MHz, about ten bit times. try std.testing.expectEqual(@as(u16, 12), t100.tout); // 400 kHz: same divider, quarter the half-cycle. const t400 = Timing.calculate(40_000_000, 400_000); try std.testing.expectEqual(@as(u16, 1), t400.clkm_div); try std.testing.expectEqual(@as(u16, 50), t400.scl_low); try std.testing.expectEqual(@as(u16, 23), t400.scl_wait_high); try std.testing.expectEqual(@as(u16, 27), t400.scl_high); try std.testing.expectEqual(@as(u16, 10), t400.tout); // 10 kHz: the branch that actually uses the controller-clock divider. 40e6/(10e3*1024) = 3, so // clkm_div = 4, sclk = 10 MHz and half = 500 - just inside the nine-bit period fields, which is // what the divider exists to guarantee. const t10 = Timing.calculate(40_000_000, 10_000); try std.testing.expectEqual(@as(u16, 4), t10.clkm_div); try std.testing.expectEqual(@as(u16, 500), t10.scl_low); // Below 80 kHz the wait-high split changes: half/4 rather than half/2 - 2. try std.testing.expectEqual(@as(u16, 125), t10.scl_wait_high); try std.testing.expectEqual(@as(u16, 375), t10.scl_high); // The hardware ordering constraint IDF asserts (i2c_ll.h:126-127) across the whole range. for ([_]u32{ 10_000, 50_000, 100_000, 400_000, 1_000_000 }) |hz| { const t = Timing.calculate(40_000_000, hz); try std.testing.expect(t.scl_wait_high < t.sda_sample); try std.testing.expect(t.sda_sample < t.scl_high); // Every period register is nine bits wide, and scl_low is written minus one. try std.testing.expect(t.scl_low - 1 <= 511); try std.testing.expect(t.scl_wait_high <= 127); // this one is seven try std.testing.expect(t.tout <= 31); // and the timeout exponent is five } } test "the timeout exponent rounds up, and where the five-bit field runs out" { // 2000 us at 40 MHz is 80,000 cycles; 2^17 = 131,072 is the first power of two above it, so // IDF's documented default SCL timeout comes out as 17 - which fits the five-bit field with // room to spare. This test exists because the first version of this file asserted the opposite. try std.testing.expectEqual(@as(u32, 17), timeoutExponent(40_000_000, 2000)); try std.testing.expect(timeoutExponent(40_000_000, 2000) <= time_out_value.max()); // The field runs out at 2^31 source cycles, 53.7 seconds at 40 MHz - a timeout no I2C bus has a // use for, which is why neither IDF nor this file range-checks it. Past that the exponent is // truncated by the field write rather than rejected, exactly as IDF's bitfield store does. try std.testing.expectEqual(@as(u32, 32), timeoutExponent(40_000_000, 100_000_000)); try std.testing.expect(timeoutExponent(40_000_000, 100_000_000) > time_out_value.max()); } test "commands encode to the layout i2c_ll_hw_cmd_t describes" { // A WRITE of three bytes with ACK checking: byte_num=3, ack_en=1, op_code=1. try std.testing.expectEqual( @as(u32, 3) | (1 << 8) | (1 << 11), (Command{ .op = .write, .bytes = 3, .ack_check = true }).encode(), ); // RESTART is opcode 6 on this chip, not 0 - the number the register header's prose still gives. try std.testing.expectEqual(@as(u32, 6 << 11), (Command{ .op = .restart }).encode()); // A final READ NACKs: ack_val=1 at bit 10, opcode 3. try std.testing.expectEqual( @as(u32, 1) | (1 << 10) | (3 << 11), (Command{ .op = .read, .bytes = 1, .ack_value = 1 }).encode(), ); // STOP is 2 and READ is 3, which is the pair the ESP32-era numbering had the other way around. try std.testing.expectEqual(@as(u32, 2 << 11), (Command{ .op = .stop }).encode()); }