/* I2C's reference implementation, which is ESP-IDF's own. * * These wrappers exist only to give IDF's `static inline` LL functions external linkage so Zig can * call them. There is no logic here - anything clever in this file would be a third implementation * to doubt - with one deliberate exception, `opcode_of`, explained where it appears. */ /* IDF's clock and reset LL functions are shadowed by a wrapper macro that references * `__DECLARE_RCC_ATOMIC_ENV`, an identifier IDF never defines anywhere; its purpose is to make an * unguarded call fail to compile, because the only legal caller holds a spinlock. There is no * FreeRTOS here and core 1 is held in reset at power-on, so declaring the name is exactly as safe as * the spinlock would be - and it is what IDF's own bootloader does * (bootloader_support/src/bootloader_console.c:53 declares a dummy local for the same reason). * * For I2C this covers four functions: i2c_ll_enable_bus_clock, i2c_ll_reset_register, * i2c_ll_set_source_clk and the LP_I2C ones this file does not use. */ static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); #include "hal/i2c_ll.h" /* I2C0 and I2C1 are addresses PROVIDEd by soc/esp32p4/ld/esp32p4.peripherals.ld (lines 17-18: * 0x500C4000 and 0x500C5000), which the build links when -Doracle is passed. Several LL functions * dispatch on the *pointer* - i2c_ll_master_set_bus_timing compares `hw == &I2C0` to decide which * HP_SYS_CLKRST divider to write - so passing the right one of these two is load-bearing, and a * third port cannot be faked. */ static i2c_dev_t *dev(int port) { return (port == 0) ? &I2C0 : &I2C1; } /* ------------------------------------------------------------------ clocks, reset, bring-up */ void oracle_i2c_enable_bus_clock(int port, int enable) { i2c_ll_enable_bus_clock(port, enable != 0); } void oracle_i2c_reset_register(int port) { i2c_ll_reset_register(port); } void oracle_i2c_enable_controller_clock(int port, int enable) { i2c_ll_enable_controller_clock(dev(port), enable != 0); } /* src: 0 = XTAL, 1 = RC_FAST. Passed as IDF's own enum values rather than as the register bit, so a * wrong bit polarity in the Zig would show up as a difference. */ void oracle_i2c_set_source_clk(int port, int src) { i2c_ll_set_source_clk(dev(port), (src == 1) ? I2C_CLK_SRC_RC_FAST : I2C_CLK_SRC_XTAL); } /* i2c_hal_master_init, i2c_hal.c:39-50, inlined here because i2c_hal.c is not linked into this * image - only the LL headers are. The sequence is IDF's, unchanged. */ void oracle_i2c_master_init(int port) { i2c_dev_t *hw = dev(port); i2c_ll_set_mode(hw, I2C_BUS_MODE_MASTER); i2c_ll_enable_pins_open_drain(hw, true); i2c_ll_enable_arbitration(hw, false); i2c_ll_master_rx_full_ack_level(hw, false); i2c_ll_set_data_mode(hw, I2C_DATA_MODE_MSB_FIRST, I2C_DATA_MODE_MSB_FIRST); i2c_ll_txfifo_rst(hw); i2c_ll_rxfifo_rst(hw); } void oracle_i2c_set_mode_master(int port) { i2c_ll_set_mode(dev(port), I2C_BUS_MODE_MASTER); } void oracle_i2c_enable_pins_open_drain(int port, int enable_od) { i2c_ll_enable_pins_open_drain(dev(port), enable_od != 0); } void oracle_i2c_update(int port) { i2c_ll_update(dev(port)); } void oracle_i2c_fsm_rst(int port) { i2c_ll_master_fsm_rst(dev(port)); } /* ------------------------------------------------------------------------------- bus timing */ /* _i2c_hal_set_bus_timing, i2c_hal.c:27-32: calculate then write. The calculation * (i2c_ll_master_cal_bus_clk, i2c_ll.h:104-128) is the part this project reimplements in Zig, and * this is the only honest way to compare it - the computed struct never leaves the register file, so * the comparison has to be of the registers it produced. */ void oracle_i2c_set_bus_timing(int port, unsigned source_hz, unsigned bus_hz) { i2c_hal_clk_config_t clk_cal = {0}; i2c_ll_master_cal_bus_clk(source_hz, bus_hz, &clk_cal); i2c_ll_master_set_bus_timing(dev(port), &clk_cal); } /* The three timing setters that take explicit periods, which is where IDF's minus-one convention is * least uniform: start_setup is written as given while start_hold is written minus one * (i2c_ll.h:452-456), both stop values are written as given (i2c_ll.h:467-471), and so are both sda * values (i2c_ll.h:482-486). None of that is derivable from the register headers. */ void oracle_i2c_set_start_timing(int port, int setup, int hold) { i2c_ll_master_set_start_timing(dev(port), setup, hold); } void oracle_i2c_set_stop_timing(int port, int setup, int hold) { i2c_ll_master_set_stop_timing(dev(port), setup, hold); } void oracle_i2c_set_sda_timing(int port, int sample, int hold) { i2c_ll_set_sda_timing(dev(port), sample, hold); } void oracle_i2c_set_tout(int port, int tout) { i2c_ll_set_tout(dev(port), tout); } /* i2c_hal_master_set_scl_timeout_val, i2c_hal.c:66-70. */ void oracle_i2c_set_scl_timeout_us(int port, unsigned source_hz, unsigned timeout_us) { uint32_t reg_val = i2c_ll_calculate_timeout_us_to_reg_val(source_hz, timeout_us); i2c_ll_set_tout(dev(port), reg_val); } void oracle_i2c_set_filter(int port, unsigned filter_num) { i2c_ll_master_set_filter(dev(port), (uint8_t)filter_num); } /* --------------------------------------------------------------------------------- the FIFOs */ void oracle_i2c_txfifo_rst(int port) { i2c_ll_txfifo_rst(dev(port)); } void oracle_i2c_rxfifo_rst(int port) { i2c_ll_rxfifo_rst(dev(port)); } void oracle_i2c_enable_fifo_mode(int port, int fifo_mode_en) { i2c_ll_enable_fifo_mode(dev(port), fifo_mode_en != 0); } void oracle_i2c_set_fifo_thresholds(int port, unsigned tx_empty, unsigned rx_full) { i2c_ll_set_txfifo_empty_thr(dev(port), (uint8_t)tx_empty); i2c_ll_set_rxfifo_full_thr(dev(port), (uint8_t)rx_full); } /* A pattern rather than a caller-supplied buffer: the point is that both implementations push the * same bytes through the same FIFO port, and a fixed generator makes the two sides impossible to * accidentally disagree about. 0xA0 + i is chosen so every byte differs from its neighbours and from * 0x00/0xFF, which are the values a broken FIFO produces. */ void oracle_i2c_write_txfifo_pattern(int port, unsigned len) { uint8_t buf[32]; if (len > sizeof(buf)) { len = sizeof(buf); } for (unsigned i = 0; i < len; i++) { buf[i] = (uint8_t)(0xA0 + i); } i2c_ll_write_txfifo(dev(port), buf, (uint8_t)len); } /* ------------------------------------------------------------------------- the command list */ /* The one piece of logic in this file, and it is here on purpose: it maps a command *kind* to * ESP-IDF's own `I2C_LL_CMD_*` macro, so the opcode number crosses the boundary as a name rather * than as an integer. If the Zig side had the numbers wrong - and this chip's register header * documents the pre-ESP32-C3 numbering, so getting them wrong is easy - passing the raw number * through would make both sides agree on the same mistake and the differential would prove nothing. * * Kinds: 0 restart, 1 write, 2 read, 3 stop, 4 end. */ static uint32_t opcode_of(unsigned kind) { switch (kind) { case 0: return I2C_LL_CMD_RESTART; case 1: return I2C_LL_CMD_WRITE; case 2: return I2C_LL_CMD_READ; case 3: return I2C_LL_CMD_STOP; default: return I2C_LL_CMD_END; } } void oracle_i2c_write_cmd(int port, int slot, unsigned kind, unsigned byte_num, int ack_en, int ack_exp, int ack_val) { i2c_ll_hw_cmd_t cmd = { .byte_num = byte_num, .ack_en = (ack_en != 0), .ack_exp = (ack_exp != 0), .ack_val = (ack_val != 0), .op_code = opcode_of(kind), }; i2c_ll_master_write_cmd_reg(dev(port), cmd, slot); } /* ---------------------------------------------------------------------------- interrupt state */ void oracle_i2c_clear_intr_mask(int port, unsigned mask) { i2c_ll_clear_intr_mask(dev(port), mask); } void oracle_i2c_disable_intr_mask(int port, unsigned mask) { i2c_ll_disable_intr_mask(dev(port), mask); } /* ------------------------------------------------------------------------------ observations */ /* Not part of any comparison - these exist so the harness can print what the reference thinks the * hardware says, next to what ours says, when a case fails. */ unsigned oracle_i2c_get_hw_version(int port) { return i2c_ll_get_hw_version(dev(port)); } unsigned oracle_i2c_get_txfifo_len(int port) { uint32_t len = 0; i2c_ll_get_txfifo_len(dev(port), &len); return len; } unsigned oracle_i2c_get_rxfifo_cnt(int port) { uint32_t len = 0; i2c_ll_get_rxfifo_cnt(dev(port), &len); return len; } int oracle_i2c_get_tout(int port) { int tout = 0; i2c_ll_get_tout(dev(port), &tout); return tout; } /* The chip's command-slot count as ESP-IDF's own header states it, so the Zig constant is checked * against IDF rather than against a reading of IDF. */ unsigned oracle_i2c_cmd_reg_num(void) { return I2C_LL_CMD_REG_NUM; } unsigned oracle_i2c_fifo_len(void) { return I2C_LL_FIFO_LEN; }