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
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
|
//! 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());
}
|