• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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