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
use crate::pac::LIC;
/// Safe wrapper around the BCM2836/2837 legacy interrupt controller —
/// routes GPU-side IRQ sources (System Timer, UART0, etc.) to the ARM
/// core. Despite the PAC's name for it, this is the legacy
/// VideoCore-shared controller, not the separate per-core "local"
/// controller (mailboxes, ARM generic timer) that `bcm2837-lpa` doesn't
/// model at all.
///
/// Distinct from the CPU-level IRQ mask (see
/// [`crate::irq::enable_irq`]/[`crate::irq::disable_irq`]) — both need
/// to be open for an interrupt to fire.
pub struct Lic {
lic: LIC,
}
impl Lic {
/// Wraps an already-configured `LIC` peripheral. The controller
/// itself needs no setup — routing is per-source, via the
/// `enable_*`/`disable_*` methods below.
pub fn new(lic: LIC) -> Self {
Self { lic }
}
/// Routes System Timer Compare 1's IRQ to the ARM core. Compare 1
/// (not 0 or 2) is used throughout this crate because GPU firmware
/// reserves those two — see `Timer`'s module doc comment.
pub fn enable_timer1_irq(&self) {
unsafe {
self.lic
.enable_1()
.write_with_zero(|w| w.timer_1().set_bit());
}
}
/// Masks System Timer Compare 1's IRQ at the interrupt controller —
/// the inverse of `enable_timer1_irq`.
pub fn disable_timer1_irq(&self) {
unsafe {
self.lic
.disable_1()
.write_with_zero(|w| w.timer_1().clear_bit_by_one());
}
}
/// True if Compare 1's IRQ is currently pending at the interrupt
/// controller.
pub fn is_timer1_pending(&self) -> bool {
self.lic.pending_1().read().timer_1().bit_is_set()
}
/// Routes UART0's IRQ (shared by all four status conditions PL011
/// can raise — see `Uart::enable_rx_irq`) to the ARM core.
pub fn enable_uart_irq(&self) {
unsafe {
self.lic.enable_2().write_with_zero(|w| w.uart().set_bit());
}
}
/// Masks UART0's IRQ at the interrupt controller — the inverse of
/// `enable_uart_irq`.
pub fn disable_uart_irq(&self) {
unsafe {
self.lic
.disable_2()
.write_with_zero(|w| w.uart().clear_bit_by_one());
}
}
/// True if UART0's IRQ is currently pending at the interrupt
/// controller.
pub fn is_uart_pending(&self) -> bool {
self.lic.pending_2().read().uart().bit_is_set()
}
/// Routes the I2C IRQ to the ARM core. Like the AUX line below, this
/// one is shared: BSC0 and BSC1 raise the same interrupt, so a
/// handler cannot tell from the controller which of them fired and
/// has to read each one's `S` register — which is what
/// `i2c::on_irq` does (behind the `async` feature, which is what
/// arms those conditions in the first place).
///
/// The interrupt conditions themselves (`C.INTD`/`INTT`/`INTR`) are
/// opened by the async transfer that needs them, not here.
pub fn enable_i2c_irq(&self) {
unsafe {
self.lic.enable_2().write_with_zero(|w| w.i2c().set_bit());
}
}
/// Masks the I2C IRQ at the interrupt controller — the inverse of
/// [`enable_i2c_irq`](Self::enable_i2c_irq), and it masks it for both
/// BSC0 and BSC1, the two sharing one line.
pub fn disable_i2c_irq(&self) {
unsafe {
self.lic
.disable_2()
.write_with_zero(|w| w.i2c().clear_bit_by_one());
}
}
/// True if the I2C IRQ is currently pending at the interrupt
/// controller — for either controller, the line being shared.
pub fn is_i2c_pending(&self) -> bool {
self.lic.pending_2().read().i2c().bit_is_set()
}
/// Routes the AUX IRQ to the ARM core. This one line is shared by all
/// three AUX sub-peripherals — the mini UART (UART1), SPI1, and SPI2
/// (the controller ORs their interrupts together), so a handler must
/// read the AUX `IRQ` status register to tell which one fired. The
/// mini UART's RX interrupt is enabled at the peripheral with
/// [`MiniUart::enable_rx_irq`](crate::mini_uart::MiniUart::enable_rx_irq).
pub fn enable_aux_irq(&self) {
unsafe {
self.lic.enable_1().write_with_zero(|w| w.aux().set_bit());
}
}
/// Masks the AUX IRQ at the interrupt controller — the inverse of
/// `enable_aux_irq`. Because the line is shared across all three AUX
/// sub-peripherals, this masks every one of them.
pub fn disable_aux_irq(&self) {
unsafe {
self.lic
.disable_1()
.write_with_zero(|w| w.aux().clear_bit_by_one());
}
}
/// True if the AUX IRQ is currently pending at the interrupt
/// controller. This only says *some* AUX sub-peripheral fired; read
/// the AUX `IRQ` status register to find which.
pub fn is_aux_pending(&self) -> bool {
self.lic.pending_1().read().aux().bit_is_set()
}
/// Routes the USB controller's IRQ to the ARM core. Like the AUX
/// line this is one interrupt covering everything the DWC2 core can
/// report — channel halts, start-of-frame, root-port changes — so a
/// handler dispatches on the core's own `GINTSTS`/`HAINT` rather
/// than on which line fired;
/// [`usb::dwc2::on_irq`](crate::usb::dwc2::on_irq) does exactly
/// that.
///
/// Which of those conditions actually reach this line is decided at
/// the core by `GINTMSK`, which
/// [`Dwc2Host::init`](crate::usb::dwc2::Dwc2Host::init) sets up. In
/// particular start-of-frame is masked there and only unmasked
/// while something is waiting on it — at high speed it fires every
/// 125µs, and it is a level source, so an unserviced SOF is not a
/// wasted interrupt but a hang.
pub fn enable_usb_irq(&self) {
unsafe {
self.lic.enable_1().write_with_zero(|w| w.usb().set_bit());
}
}
/// Masks the USB controller's IRQ at the interrupt controller — the
/// inverse of `enable_usb_irq`.
pub fn disable_usb_irq(&self) {
unsafe {
self.lic
.disable_1()
.write_with_zero(|w| w.usb().clear_bit_by_one());
}
}
/// True if the USB controller's IRQ is currently pending at the
/// interrupt controller. This only says the DWC2 core asserted its
/// line; `GINTSTS`/`HAINT` say why.
pub fn is_usb_pending(&self) -> bool {
self.lic.pending_1().read().usb().bit_is_set()
}
/// Routes the EMMC (Arasan SD host controller) IRQ to the ARM core —
/// the line [`sd::on_irq`](crate::sd::on_irq) services, and what an
/// `async` SD transfer parks on instead of spinning on `INTERRUPT`.
///
/// Which of the controller's conditions actually reach this line is
/// decided at the peripheral by its `IRPT_EN` register, and that one
/// is opened by the transfer that needs it rather than by
/// [`Sd::init`](crate::sd::Sd::init) — so a program driving the card
/// through the blocking API raises nothing here even with this line
/// routed. The controller's other register, `IRPT_MASK`, gates only
/// whether a condition becomes visible in `INTERRUPT` at all; `init`
/// opens that one wide for the polling loop's benefit, which is why
/// the two must not be confused.
///
/// One line covers everything the controller can report, so a handler
/// dispatches on `INTERRUPT` rather than on which line fired. It is
/// also shared with the same controller's SDIO use
/// ([`crate::sdio`], which drives this same hardware for the on-board
/// WiFi chip): only one of the two can own the controller at a time,
/// so the sharing costs nothing, but it is the reason `sd::on_irq`
/// checks that an async transfer armed `IRPT_EN` before touching
/// anything.
pub fn enable_emmc_irq(&self) {
unsafe {
self.lic.enable_2().write_with_zero(|w| w.emmc().set_bit());
}
}
/// Masks the EMMC IRQ at the interrupt controller — the inverse of
/// [`enable_emmc_irq`](Self::enable_emmc_irq).
pub fn disable_emmc_irq(&self) {
unsafe {
self.lic
.disable_2()
.write_with_zero(|w| w.emmc().clear_bit_by_one());
}
}
/// True if the EMMC IRQ is currently pending at the interrupt
/// controller. This only says the controller asserted its line; its
/// `INTERRUPT` register says why.
pub fn is_emmc_pending(&self) -> bool {
self.lic.pending_2().read().emmc().bit_is_set()
}
/// Routes the GPIO bank IRQ that covers `pin` to the ARM core.
///
/// The BCM2836/2837 splits GPIO event detection across three
/// interrupt lines by pin range — 0-27, 28-45, 46-53 — so this maps
/// the pin to the right one. Those ranges are the hardware's, matching
/// Linux's `pinctrl-bcm2835` bank split; note they are deliberately
/// *not* the 0-31/32-53 boundary the GPEDS registers use, which is
/// different. (A fourth line ORs all GPIO events together, but the
/// per-range lines are used instead so unrelated pins can be masked
/// independently.)
///
/// All pins in the same range share one line: enabling it for one pin
/// enables it for every pin in that range, so a handler must read
/// GPEDS — [`Pin::is_interrupt_pending`](crate::gpio::Pin::is_interrupt_pending)
/// — to tell which pin actually fired.
pub fn enable_gpio_irq(&self, pin: u8) {
unsafe {
match gpio_line(pin) {
0 => self
.lic
.enable_2()
.write_with_zero(|w| w.gpio_0().set_bit()),
1 => self
.lic
.enable_2()
.write_with_zero(|w| w.gpio_1().set_bit()),
_ => self
.lic
.enable_2()
.write_with_zero(|w| w.gpio_2().set_bit()),
}
}
}
/// Masks the GPIO bank IRQ covering `pin` — the inverse of
/// `enable_gpio_irq`. Because the line is shared across the whole pin
/// range, this also masks every other pin in that range.
pub fn disable_gpio_irq(&self, pin: u8) {
unsafe {
match gpio_line(pin) {
0 => self
.lic
.disable_2()
.write_with_zero(|w| w.gpio_0().clear_bit_by_one()),
1 => self
.lic
.disable_2()
.write_with_zero(|w| w.gpio_1().clear_bit_by_one()),
_ => self
.lic
.disable_2()
.write_with_zero(|w| w.gpio_2().clear_bit_by_one()),
}
}
}
/// True if the GPIO bank IRQ covering `pin` is currently pending. This
/// only says *some* pin in the range fired; read GPEDS via
/// [`Pin::is_interrupt_pending`](crate::gpio::Pin::is_interrupt_pending)
/// to find which.
pub fn is_gpio_pending(&self, pin: u8) -> bool {
match gpio_line(pin) {
0 => self.lic.pending_2().read().gpio_0().bit_is_set(),
1 => self.lic.pending_2().read().gpio_1().bit_is_set(),
_ => self.lic.pending_2().read().gpio_2().bit_is_set(),
}
}
}
/// Maps a GPIO pin to which of the three GPIO interrupt lines carries its
/// events: 0 for pins 0-27, 1 for 28-45, 2 for 46-53. See
/// [`Lic::enable_gpio_irq`] for why these ranges (not the GPEDS register
/// banks) are the right split.
const fn gpio_line(pin: u8) -> u8 {
if pin <= 27 {
0
} else if pin <= 45 {
1
} else {
2
}
}