1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_digital_io/digital_io.h"
16
17 #include <array>
18 #include <mutex>
19
20 #include "fsl_clock.h"
21 #include "fsl_gpio.h"
22 #include "fsl_reset.h"
23 #include "pw_assert/check.h"
24 #include "pw_digital_io_mcuxpresso/digital_io.h"
25 #include "pw_status/status.h"
26 #include "pw_sync/interrupt_spin_lock.h"
27
28 namespace pw::digital_io {
29 namespace {
30
31 constexpr std::array kGpioClocks = GPIO_CLOCKS;
32 constexpr std::array kGpioResets = GPIO_RSTS_N;
33 constexpr uint32_t kNumGpioPorts = GPIO_INTSTATA_COUNT;
34 constexpr gpio_interrupt_index_t kGpioInterruptBankIndex = kGPIO_InterruptA;
35
36 // This lock is used to prevent simultaneous access to the list of registered
37 // interrupt handlers and the underlying interrupt hardware.
38 sync::InterruptSpinLock port_interrupts_lock;
39
40 // The HS GPIO block culminates all pin interrupts into single interrupt
41 // vectors. Each GPIO port has a corresponding interrupt register and status
42 // register.
43 //
44 // It would be expensive from a memory perspective to statically define a
45 // handler pointer for each pin so a linked list used instead. It's added to
46 // whenever a handler is registered with DoSetInterruptHandler() and removed
47 // whenever a handler is de-registered with DoSetInterruptHandler().
48 //
49 // To improve handler lookup performance, an array of linked lists is created
50 // below, 1 per port. This allows for traversal of smaller linked lists, and
51 // only the linked lists that have active interrupts.
52 std::array<pw::IntrusiveForwardList<McuxpressoDigitalInOutInterrupt>,
53 kNumGpioPorts>
54 port_interrupts PW_GUARDED_BY(port_interrupts_lock);
55
GPIO_PortGetInterruptEnable(GPIO_Type * base,uint32_t port,gpio_interrupt_index_t interrupt)56 uint32_t GPIO_PortGetInterruptEnable(GPIO_Type* base,
57 uint32_t port,
58 gpio_interrupt_index_t interrupt) {
59 switch (interrupt) {
60 case kGPIO_InterruptA:
61 return base->INTENA[port];
62 case kGPIO_InterruptB:
63 return base->INTENB[port];
64 default:
65 PW_CRASH("Invalid interrupt");
66 }
67 }
68
GPIO_PinGetInterruptEnable(GPIO_Type * base,uint32_t port,uint32_t pin,gpio_interrupt_index_t interrupt)69 bool GPIO_PinGetInterruptEnable(GPIO_Type* base,
70 uint32_t port,
71 uint32_t pin,
72 gpio_interrupt_index_t interrupt) {
73 return ((GPIO_PortGetInterruptEnable(base, port, interrupt) >> pin) & 1);
74 }
75
GPIO_PinGetInterruptPolarity(GPIO_Type * base,uint32_t port,uint32_t pin)76 gpio_pin_enable_polarity_t GPIO_PinGetInterruptPolarity(GPIO_Type* base,
77 uint32_t port,
78 uint32_t pin) {
79 return static_cast<gpio_pin_enable_polarity_t>((base->INTPOL[port] >> pin) &
80 1);
81 }
82
GPIO_PinSetInterruptPolarity(GPIO_Type * base,uint32_t port,uint32_t pin,gpio_pin_enable_polarity_t polarity)83 void GPIO_PinSetInterruptPolarity(GPIO_Type* base,
84 uint32_t port,
85 uint32_t pin,
86 gpio_pin_enable_polarity_t polarity) {
87 base->INTPOL[port] = (base->INTPOL[port] & ~(1UL << pin)) |
88 (static_cast<uint32_t>(polarity) << pin);
89 }
90
91 } // namespace
92
McuxpressoDigitalOut(GPIO_Type * base,uint32_t port,uint32_t pin,pw::digital_io::State initial_state)93 McuxpressoDigitalOut::McuxpressoDigitalOut(GPIO_Type* base,
94 uint32_t port,
95 uint32_t pin,
96 pw::digital_io::State initial_state)
97 : base_(base), port_(port), pin_(pin), initial_state_(initial_state) {
98 PW_CHECK(base != nullptr);
99 PW_CHECK(port < kGpioClocks.size());
100 PW_CHECK(port < kGpioResets.size());
101 }
102
DoEnable(bool enable)103 pw::Status McuxpressoDigitalOut::DoEnable(bool enable) {
104 if (enable) {
105 if (is_enabled()) {
106 return pw::OkStatus();
107 }
108
109 CLOCK_EnableClock(kGpioClocks[port_]);
110 RESET_ClearPeripheralReset(kGpioResets[port_]);
111
112 gpio_pin_config_t config = {
113 .pinDirection = kGPIO_DigitalOutput,
114 .outputLogic = static_cast<uint8_t>(
115 initial_state_ == pw::digital_io::State::kActive ? 1U : 0U),
116 };
117 GPIO_PinInit(base_, port_, pin_, &config);
118
119 } else {
120 // Set to input on disable.
121 gpio_pin_config_t config = {
122 .pinDirection = kGPIO_DigitalInput,
123 .outputLogic = 0,
124 };
125
126 // Must enable the clock, since GPIO can get disabled without ever
127 // being enabled.
128 CLOCK_EnableClock(kGpioClocks[port_]);
129 GPIO_PinInit(base_, port_, pin_, &config);
130
131 // Can't disable clock as other users on same port may be active.
132 }
133 enabled_ = enable;
134 return pw::OkStatus();
135 }
136
DoSetState(pw::digital_io::State state)137 pw::Status McuxpressoDigitalOut::DoSetState(pw::digital_io::State state) {
138 if (!is_enabled()) {
139 return pw::Status::FailedPrecondition();
140 }
141 GPIO_PinWrite(
142 base_, port_, pin_, state == pw::digital_io::State::kActive ? 1 : 0);
143 return pw::OkStatus();
144 }
145
McuxpressoDigitalIn(GPIO_Type * base,uint32_t port,uint32_t pin)146 McuxpressoDigitalIn::McuxpressoDigitalIn(GPIO_Type* base,
147 uint32_t port,
148 uint32_t pin)
149 : base_(base), port_(port), pin_(pin) {
150 PW_CHECK(base != nullptr);
151 PW_CHECK(port < kGpioClocks.size());
152 PW_CHECK(port < kGpioResets.size());
153 }
154
DoEnable(bool enable)155 pw::Status McuxpressoDigitalIn::DoEnable(bool enable) {
156 if (enable) {
157 if (is_enabled()) {
158 return pw::OkStatus();
159 }
160 } else {
161 enabled_ = false;
162 // Can't disable clock as other users on same port may be active.
163 return pw::OkStatus();
164 }
165
166 CLOCK_EnableClock(kGpioClocks[port_]);
167 RESET_ClearPeripheralReset(kGpioResets[port_]);
168
169 gpio_pin_config_t config = {
170 .pinDirection = kGPIO_DigitalInput,
171 .outputLogic = 0,
172 };
173 GPIO_PinInit(base_, port_, pin_, &config);
174
175 enabled_ = enable;
176 return pw::OkStatus();
177 }
178
DoGetState()179 pw::Result<pw::digital_io::State> McuxpressoDigitalIn::DoGetState() {
180 if (!is_enabled()) {
181 return pw::Status::FailedPrecondition();
182 }
183 uint32_t value = GPIO_PinRead(base_, port_, pin_);
184 return value == 1 ? pw::digital_io::State::kActive
185 : pw::digital_io::State::kInactive;
186 }
187
McuxpressoDigitalInOutInterrupt(GPIO_Type * base,uint32_t port,uint32_t pin,bool output)188 McuxpressoDigitalInOutInterrupt::McuxpressoDigitalInOutInterrupt(
189 GPIO_Type* base, uint32_t port, uint32_t pin, bool output)
190 : base_(base), port_(port), pin_(pin), output_(output) {
191 PW_CHECK(base != nullptr);
192 PW_CHECK(port < kGpioClocks.size());
193 PW_CHECK(port < kGpioResets.size());
194 PW_CHECK(port < port_interrupts.size());
195 }
196
DoEnable(bool enable)197 pw::Status McuxpressoDigitalInOutInterrupt::DoEnable(bool enable) {
198 if (enable) {
199 if (is_enabled()) {
200 return pw::OkStatus();
201 }
202 } else {
203 enabled_ = false;
204 // Can't disable clock as other users on same port may be active.
205 return pw::OkStatus();
206 }
207
208 CLOCK_EnableClock(kGpioClocks[port_]);
209 RESET_ClearPeripheralReset(kGpioResets[port_]);
210
211 gpio_pin_config_t config = {
212 .pinDirection = (output_ ? kGPIO_DigitalOutput : kGPIO_DigitalInput),
213 .outputLogic = 0,
214 };
215 GPIO_PinInit(base_, port_, pin_, &config);
216
217 enabled_ = enable;
218 return pw::OkStatus();
219 }
220
221 pw::Result<pw::digital_io::State>
DoGetState()222 McuxpressoDigitalInOutInterrupt::DoGetState() {
223 if (!is_enabled()) {
224 return pw::Status::FailedPrecondition();
225 }
226
227 uint32_t value = GPIO_PinRead(base_, port_, pin_);
228 return value == 1 ? pw::digital_io::State::kActive
229 : pw::digital_io::State::kInactive;
230 }
231
DoSetState(pw::digital_io::State state)232 pw::Status McuxpressoDigitalInOutInterrupt::DoSetState(
233 pw::digital_io::State state) {
234 if (!is_enabled()) {
235 return pw::Status::FailedPrecondition();
236 }
237 GPIO_PinWrite(
238 base_, port_, pin_, state == pw::digital_io::State::kActive ? 1 : 0);
239
240 return pw::OkStatus();
241 }
242
DoSetInterruptHandler(pw::digital_io::InterruptTrigger trigger,pw::digital_io::InterruptHandler && handler)243 pw::Status McuxpressoDigitalInOutInterrupt::DoSetInterruptHandler(
244 pw::digital_io::InterruptTrigger trigger,
245 pw::digital_io::InterruptHandler&& handler) {
246 if (handler == nullptr) {
247 std::lock_guard lock(port_interrupts_lock);
248 if (GPIO_PinGetInterruptEnable(
249 base_, port_, pin_, kGpioInterruptBankIndex)) {
250 // Can only clear handler when the interrupt is disabled
251 return pw::Status::FailedPrecondition();
252 }
253
254 unlist();
255
256 interrupt_handler_ = nullptr;
257 return pw::OkStatus();
258 }
259
260 if (interrupt_handler_ != nullptr) {
261 // Can only set a handler when none is set
262 return pw::Status::FailedPrecondition();
263 }
264
265 trigger_ = trigger;
266
267 std::lock_guard lock(port_interrupts_lock);
268 interrupt_handler_ = std::move(handler);
269
270 if (unlisted()) {
271 auto& list = port_interrupts[port_];
272 list.push_front(*this);
273 }
274
275 return pw::OkStatus();
276 }
277
DoEnableInterruptHandler(bool enable)278 pw::Status McuxpressoDigitalInOutInterrupt::DoEnableInterruptHandler(
279 bool enable) {
280 uint32_t mask = 1 << pin_;
281
282 std::lock_guard lock(port_interrupts_lock);
283
284 if (enable) {
285 if (interrupt_handler_ == nullptr) {
286 return pw::Status::FailedPrecondition();
287 }
288
289 ConfigureInterrupt();
290 GPIO_PortEnableInterrupts(base_, port_, kGpioInterruptBankIndex, mask);
291 NVIC_EnableIRQ(GPIO_INTA_IRQn);
292 } else {
293 GPIO_PortDisableInterrupts(base_, port_, kGpioInterruptBankIndex, mask);
294 }
295
296 return pw::OkStatus();
297 }
298
ConfigureInterrupt() const299 void McuxpressoDigitalInOutInterrupt::ConfigureInterrupt() const {
300 // Emulate edge interrupts with level-sensitive interrupts.
301 //
302 // This is *required* for kBothEdges support, as the underlying hardware
303 // only supports single edge interrupts. However, we choose to do this
304 // for all interrupts to work around a hardware issue: edge-sensitive GPIO
305 // interrupts do not work properly in deep sleep on the RT5xx. In particularly
306 // bad cases, edge-sensitive interrupts could result in the system constantly
307 // waking up from deep-sleep, doing no work, sleeping, and waking again.
308 //
309 // Set the initial polarity of the interrupt to be the opposite of what
310 // the port currently reads (level high if the pin is low, and vice
311 // versa). Either this will capture the first edge,
312 // or if the line changes between when the pin is read and the interrupt
313 // is enabled, it'll fire immediately.
314 const gpio_pin_enable_polarity_t polarity =
315 GPIO_PinRead(base_, port_, pin_) ? kGPIO_PinIntEnableLowOrFall
316 : kGPIO_PinIntEnableHighOrRise;
317 gpio_interrupt_config_t config{
318 .mode = kGPIO_PinIntEnableLevel,
319 .polarity = static_cast<uint8_t>(polarity),
320 };
321 GPIO_SetPinInterruptConfig(base_, port_, pin_, &config);
322 }
323
GPIO_INTA_DriverIRQHandler()324 PW_EXTERN_C void GPIO_INTA_DriverIRQHandler() PW_NO_LOCK_SAFETY_ANALYSIS {
325 auto* base = GPIO;
326
327 // For each port
328 for (uint32_t port = 0; port < kNumGpioPorts; port++) {
329 const auto& list = port_interrupts[port];
330 const uint32_t port_int_flags =
331 GPIO_PortGetInterruptEnable(base, port, kGpioInterruptBankIndex) &
332 GPIO_PortGetInterruptStatus(base, port, kGpioInterruptBankIndex);
333
334 if (port_int_flags == 0) {
335 // If no flags are set, there is nothing to do. Skip traversing the linked
336 // list
337 continue;
338 }
339
340 // For each line registered on that port's interrupt list
341 for (const auto& line : list) {
342 const uint32_t pin_mask = 1UL << line.pin_;
343 if ((port_int_flags & pin_mask) != 0) {
344 const auto trigger = line.trigger_;
345 const auto polarity =
346 GPIO_PinGetInterruptPolarity(base, port, line.pin_);
347 if ((trigger == InterruptTrigger::kDeactivatingEdge &&
348 polarity == kGPIO_PinIntEnableLowOrFall) ||
349 (trigger == InterruptTrigger::kActivatingEdge &&
350 polarity == kGPIO_PinIntEnableHighOrRise) ||
351 (trigger == InterruptTrigger::kBothEdges)) {
352 line.interrupt_handler_(polarity == kGPIO_PinIntEnableHighOrRise
353 ? State::kActive
354 : State::kInactive);
355 }
356
357 // Invert the polarity of the level interrupt before clearing the
358 // flag to catch the next edge.
359 //
360 // We invert here, rather than sampling the line and setting the
361 // polarity based on that. Inverting allows us to capture both edges
362 // of a short pulse. For example, if the polarity is high, and the
363 // line briefly goes high and then low again before the ISR runs.
364 // The first high level causes an interrupt to latch in INTSTAT,
365 // and then when the ISR runs, setting the polarity low would
366 // immediately latch it in INTSTAT again once we clear it.
367 // If we had instead sampled the GPIO as low, and then set the
368 // polarity high, we would have missed the falling edge of the pulse.
369 //
370 // It is critical to invert the polarity before clearing. If INTSTAT
371 // is cleared first, the bit would immediately latch again (assuming
372 // the line is still high). Then we'd invert the polarity to low,
373 // the ISR would fire again, inverting the polarity back to high,
374 // and the ISR would fire again, over and over.
375 gpio_pin_enable_polarity_t new_polarity =
376 (polarity == kGPIO_PinIntEnableHighOrRise)
377 ? kGPIO_PinIntEnableLowOrFall
378 : kGPIO_PinIntEnableHighOrRise;
379 GPIO_PinSetInterruptPolarity(base, port, line.pin_, new_polarity);
380 GPIO_PinClearInterruptFlag(
381 base, port, line.pin_, kGpioInterruptBankIndex);
382 }
383 }
384 }
385
386 SDK_ISR_EXIT_BARRIER;
387 }
388
389 } // namespace pw::digital_io
390