1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
/* 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;
}
|