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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
|
//! 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);
}
|