esp_hal/gpio/
interconnect.rs

1//! Peripheral signal interconnect using IOMUX or GPIOMUX.
2
3// FIXME: https://github.com/esp-rs/esp-hal/issues/2954 The GPIO implementation does not contain any
4// locking. This is okay there, because the implementation uses either W1TS/W1TC
5// registers to set certain bits, or separate registers for each pin - and there
6// can only be one instance of every GPIO struct in safe code. However, with the
7// interconnect module we allow multiple handles, which means possible RMW
8// operations on the pin registers cause data races.
9
10use crate::{
11    gpio::{
12        self,
13        AlternateFunction,
14        AnyPin,
15        Flex,
16        InputPin,
17        InputSignalType,
18        Level,
19        NoPin,
20        OutputPin,
21        OutputSignalType,
22        Pin,
23        PinGuard,
24        Pull,
25        FUNC_IN_SEL_OFFSET,
26        GPIO_FUNCTION,
27        INPUT_SIGNAL_MAX,
28        OUTPUT_SIGNAL_MAX,
29    },
30    peripheral::Peripheral,
31    peripherals::GPIO,
32    private::{self, Sealed},
33};
34
35/// A signal that can be connected to a peripheral input.
36///
37/// Peripheral drivers are encouraged to accept types that implement this and
38/// [`PeripheralOutput`] as arguments instead of pin types.
39#[allow(
40    private_bounds,
41    reason = "InputConnection is unstable, but the trait needs to be public"
42)]
43pub trait PeripheralInput: Into<InputConnection> + 'static + crate::private::Sealed {}
44
45/// A signal that can be connected to a peripheral input and/or output.
46///
47/// Peripheral drivers are encouraged to accept types that implement this and
48/// [`PeripheralInput`] as arguments instead of pin types.
49#[allow(
50    private_bounds,
51    reason = "OutputConnection is unstable, but the trait needs to be public"
52)]
53pub trait PeripheralOutput: Into<OutputConnection> + 'static + crate::private::Sealed {}
54
55// Pins
56impl<P: InputPin> PeripheralInput for P {}
57impl<P: OutputPin> PeripheralOutput for P {}
58
59// Pin drivers
60impl PeripheralInput for Flex<'static> {}
61impl PeripheralOutput for Flex<'static> {}
62
63// Placeholders
64impl PeripheralInput for NoPin {}
65impl PeripheralOutput for NoPin {}
66
67impl PeripheralInput for Level {}
68impl PeripheralOutput for Level {}
69
70// Split signals
71impl PeripheralInput for InputSignal {}
72impl PeripheralInput for OutputSignal {}
73impl PeripheralOutput for OutputSignal {}
74
75// Type-erased signals
76impl PeripheralInput for InputConnection {}
77impl PeripheralInput for OutputConnection {}
78impl PeripheralOutput for OutputConnection {}
79
80impl gpio::InputSignal {
81    fn can_use_gpio_matrix(self) -> bool {
82        self as InputSignalType <= INPUT_SIGNAL_MAX
83    }
84
85    /// Connects a peripheral input signal to a GPIO or a constant level.
86    ///
87    /// Note that connecting multiple GPIOs to a single peripheral input is not
88    /// possible and the previous connection will be replaced.
89    ///
90    /// Also note that a peripheral input must always be connected to something,
91    /// so if you want to disconnect it from GPIOs, you should connect it to a
92    /// constant level.
93    #[inline]
94    pub fn connect_to(self, pin: impl Peripheral<P = impl PeripheralInput>) {
95        crate::into_mapped_ref!(pin);
96
97        pin.connect_input_to_peripheral(self);
98    }
99}
100
101impl gpio::OutputSignal {
102    fn can_use_gpio_matrix(self) -> bool {
103        self as OutputSignalType <= OUTPUT_SIGNAL_MAX
104    }
105
106    /// Connects a peripheral output signal to a GPIO.
107    ///
108    /// Note that connecting multiple output signals to a single GPIO is not
109    /// possible and the previous connection will be replaced.
110    ///
111    /// Also note that it is possible to connect a peripheral output signal to
112    /// multiple GPIOs, and old connections will not be cleared automatically.
113    #[inline]
114    pub fn connect_to(self, pin: impl Peripheral<P = impl PeripheralOutput>) {
115        crate::into_mapped_ref!(pin);
116
117        pin.connect_peripheral_to_output(self);
118    }
119
120    /// Disconnects a peripheral output signal from a GPIO.
121    #[inline]
122    pub fn disconnect_from(self, pin: impl Peripheral<P = impl PeripheralOutput>) {
123        crate::into_mapped_ref!(pin);
124
125        pin.disconnect_from_peripheral_output(self);
126    }
127}
128
129/// Connects a peripheral input (`signal`, e.g. SPI MISO) to a GPIO or a
130/// constant level (`input`).
131///
132/// - `signal`: The input signal to connect to the pin
133/// - `input`: The GPIO (or constant level) number to connect to the input
134///   signal.
135/// - `invert`: Configures whether or not to invert the input value
136/// - `use_gpio_matrix`: true to route through the GPIO matrix
137pub(crate) fn connect_input_signal(
138    signal: gpio::InputSignal,
139    input: u8,
140    invert: bool,
141    use_gpio_matrix: bool,
142) {
143    assert!(
144        signal.can_use_gpio_matrix() || !use_gpio_matrix,
145        "{:?} cannot be routed through the GPIO matrix",
146        signal
147    );
148    GPIO::regs()
149        .func_in_sel_cfg(signal as usize - FUNC_IN_SEL_OFFSET)
150        .write(|w| unsafe {
151            w.sel().bit(use_gpio_matrix);
152            w.in_inv_sel().bit(invert);
153            w.in_sel().bits(input) // Connect to GPIO or constant level
154        });
155}
156
157fn connect_pin_to_input_signal(
158    pin: &AnyPin,
159    signal: gpio::InputSignal,
160    is_inverted: bool,
161    force_gpio: bool,
162) {
163    let af = if is_inverted || force_gpio {
164        GPIO_FUNCTION
165    } else {
166        pin.input_signals(private::Internal)
167            .iter()
168            .find(|(_af, s)| *s == signal)
169            .map(|(af, _)| *af)
170            .unwrap_or(GPIO_FUNCTION)
171    };
172
173    pin.set_alternate_function(af);
174
175    connect_input_signal(signal, pin.number(), is_inverted, af == GPIO_FUNCTION);
176}
177
178fn connect_peripheral_to_output(
179    pin: &AnyPin,
180    signal: gpio::OutputSignal,
181    is_inverted: bool,
182    force_gpio: bool,
183    peripheral_control_output_enable: bool,
184    invert_output_enable: bool,
185) {
186    let af = if is_inverted || force_gpio {
187        GPIO_FUNCTION
188    } else {
189        pin.output_signals(private::Internal)
190            .iter()
191            .find(|(_af, s)| *s == signal)
192            .map(|(af, _)| *af)
193            .unwrap_or(GPIO_FUNCTION)
194    };
195
196    assert!(
197        signal.can_use_gpio_matrix() || af != GPIO_FUNCTION,
198        "{:?} cannot be routed through the GPIO matrix",
199        signal
200    );
201
202    pin.set_alternate_function(af);
203
204    // Inlined because output signals can only be connected to pins or nothing, so
205    // there is no other user.
206    GPIO::regs()
207        .func_out_sel_cfg(pin.number() as usize)
208        .write(|w| unsafe {
209            if af == GPIO_FUNCTION {
210                // Ignored if the signal is not routed through the GPIO matrix - alternate
211                // function selects peripheral signal directly.
212                w.out_sel().bits(signal as _);
213                w.inv_sel().bit(is_inverted);
214            }
215            w.oen_sel().bit(!peripheral_control_output_enable);
216            w.oen_inv_sel().bit(invert_output_enable)
217        });
218}
219
220fn disconnect_peripheral_output_from_pin(pin: &AnyPin, signal: gpio::OutputSignal) {
221    pin.set_alternate_function(GPIO_FUNCTION);
222
223    GPIO::regs()
224        .func_in_sel_cfg(signal as usize - FUNC_IN_SEL_OFFSET)
225        .write(|w| w.sel().clear_bit());
226}
227
228/// A configurable input signal between a peripheral and a GPIO pin.
229///
230/// Multiple input signals can be connected to one pin.
231#[instability::unstable]
232pub struct InputSignal {
233    pin: AnyPin,
234    is_inverted: bool,
235}
236
237impl<P> From<P> for InputSignal
238where
239    P: InputPin,
240{
241    fn from(input: P) -> Self {
242        Self::new(input.degrade())
243    }
244}
245
246impl From<Flex<'static>> for InputSignal {
247    fn from(input: Flex<'static>) -> Self {
248        Self::new(unsafe { AnyPin::steal(input.pin.number()) })
249    }
250}
251
252impl Clone for InputSignal {
253    fn clone(&self) -> Self {
254        Self {
255            pin: unsafe { self.pin.clone_unchecked() },
256            is_inverted: self.is_inverted,
257        }
258    }
259}
260
261impl Peripheral for InputSignal {
262    type P = Self;
263
264    unsafe fn clone_unchecked(&self) -> Self::P {
265        self.clone()
266    }
267}
268
269impl Sealed for InputSignal {}
270
271impl InputSignal {
272    pub(crate) fn new(pin: AnyPin) -> Self {
273        Self {
274            pin,
275            is_inverted: false,
276        }
277    }
278
279    /// Returns the GPIO number of the underlying pin.
280    pub fn number(&self) -> u8 {
281        self.pin.number()
282    }
283
284    /// Returns the current signal level.
285    pub fn level(&self) -> Level {
286        self.is_input_high().into()
287    }
288
289    /// Inverts the peripheral's input signal.
290    ///
291    /// Calling this function multiple times toggles the setting.
292    pub fn invert(&mut self) {
293        self.is_inverted = !self.is_inverted;
294    }
295
296    /// Consumes the signal and returns a new one that inverts the peripheral's
297    /// input signal.
298    ///
299    /// Calling this function multiple times toggles the setting.
300    pub fn inverted(mut self) -> Self {
301        self.invert();
302        self
303    }
304
305    /// Connect the pin to a peripheral input signal.
306    ///
307    /// Since there can only be one input signal connected to a peripheral at a
308    /// time, this function will disconnect any previously connected input
309    /// signals.
310    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
311        connect_pin_to_input_signal(&self.pin, signal, self.is_inverted, true);
312    }
313
314    delegate::delegate! {
315        #[doc(hidden)]
316        to self.pin {
317            pub fn pull_direction(&self, pull: Pull);
318            pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
319            pub fn init_input(&self, pull: Pull);
320            pub fn is_input_high(&self) -> bool;
321            pub fn enable_input(&self, on: bool);
322        }
323    }
324}
325
326/// A limited, private version of [InputSignal] that allows bypassing the GPIO
327/// matrix. This is only usable when the GPIO pin connected to the signal is not
328/// split.
329struct DirectInputSignal {
330    pin: AnyPin,
331}
332
333impl Clone for DirectInputSignal {
334    fn clone(&self) -> Self {
335        Self::new(unsafe { self.pin.clone_unchecked() })
336    }
337}
338
339impl DirectInputSignal {
340    pub(crate) fn new(pin: AnyPin) -> Self {
341        Self { pin }
342    }
343
344    /// Connect the pin to a peripheral input signal.
345    ///
346    /// Since there can only be one input signal connected to a peripheral at a
347    /// time, this function will disconnect any previously connected input
348    /// signals.
349    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
350        connect_pin_to_input_signal(&self.pin, signal, false, false);
351    }
352
353    delegate::delegate! {
354        to self.pin {
355            fn pull_direction(&self, pull: Pull);
356            fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
357            fn init_input(&self, pull: Pull);
358            fn is_input_high(&self) -> bool;
359            fn enable_input(&self, on: bool);
360        }
361    }
362}
363
364/// A configurable output signal between a peripheral and a GPIO pin.
365///
366/// Multiple pins can be connected to one output signal.
367#[instability::unstable]
368pub struct OutputSignal {
369    pin: AnyPin,
370    is_inverted: bool,
371}
372
373impl<P> From<P> for OutputSignal
374where
375    P: OutputPin,
376{
377    fn from(output: P) -> Self {
378        Self::new(output.degrade())
379    }
380}
381
382impl From<Flex<'static>> for OutputSignal {
383    fn from(output: Flex<'static>) -> Self {
384        Self::new(unsafe { AnyPin::steal(output.pin.number()) })
385    }
386}
387
388impl Peripheral for OutputSignal {
389    type P = Self;
390
391    unsafe fn clone_unchecked(&self) -> Self::P {
392        Self {
393            pin: self.pin.clone_unchecked(),
394            is_inverted: self.is_inverted,
395        }
396    }
397}
398
399impl Sealed for OutputSignal {}
400
401impl OutputSignal {
402    pub(crate) fn new(pin: AnyPin) -> Self {
403        Self {
404            pin,
405            is_inverted: false,
406        }
407    }
408
409    /// Returns the GPIO number of the underlying pin.
410    pub fn number(&self) -> u8 {
411        self.pin.number()
412    }
413
414    /// Inverts the peripheral's output signal.
415    ///
416    /// Calling this function multiple times toggles the setting.
417    pub fn invert(&mut self) {
418        self.is_inverted = !self.is_inverted;
419    }
420
421    /// Consumes the signal and returns a new one that inverts the peripheral's
422    /// output signal.
423    ///
424    /// Calling this function multiple times toggles the setting.
425    pub fn inverted(mut self) -> Self {
426        self.invert();
427        self
428    }
429
430    /// Connect the pin to a peripheral output signal.
431    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) {
432        connect_peripheral_to_output(&self.pin, signal, self.is_inverted, true, true, false);
433    }
434
435    /// Remove this output pin from a connected [signal](`gpio::OutputSignal`).
436    ///
437    /// Clears the entry in the GPIO matrix / Io mux that associates this output
438    /// pin with a previously connected [signal](`gpio::OutputSignal`). Any
439    /// other outputs connected to the peripheral remain intact.
440    fn disconnect_from_peripheral_output(&self, signal: gpio::OutputSignal) {
441        disconnect_peripheral_output_from_pin(&self.pin, signal);
442    }
443
444    delegate::delegate! {
445        #[instability::unstable]
446        to self.pin {
447            pub fn pull_direction(&self, pull: Pull);
448            pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
449            pub fn init_input(&self, pull: Pull);
450            pub fn is_input_high(&self) -> bool;
451            pub fn enable_input(&self, on: bool);
452
453            pub fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
454            pub fn set_to_open_drain_output(&self);
455            pub fn set_to_push_pull_output(&self);
456            pub fn enable_output(&self, on: bool);
457            pub fn set_output_high(&self, on: bool);
458            pub fn set_drive_strength(&self, strength: gpio::DriveStrength);
459            pub fn enable_open_drain(&self, on: bool);
460            pub fn is_set_high(&self) -> bool;
461        }
462    }
463}
464
465/// A limited, private version of [OutputSignal] that allows bypassing the GPIO
466/// matrix. This is only usable when the GPIO pin connected to the signal is not
467/// split.
468struct DirectOutputSignal {
469    pin: AnyPin,
470}
471
472impl DirectOutputSignal {
473    pub(crate) fn new(pin: AnyPin) -> Self {
474        Self { pin }
475    }
476
477    /// Connect the pin to a peripheral output signal.
478    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) {
479        connect_peripheral_to_output(&self.pin, signal, false, false, true, false);
480    }
481
482    /// Remove this output pin from a connected [signal](`gpio::OutputSignal`).
483    ///
484    /// Clears the entry in the GPIO matrix / Io mux that associates this output
485    /// pin with a previously connected [signal](`gpio::OutputSignal`). Any
486    /// other outputs connected to the peripheral remain intact.
487    fn disconnect_from_peripheral_output(&self, signal: gpio::OutputSignal) {
488        disconnect_peripheral_output_from_pin(&self.pin, signal);
489    }
490
491    delegate::delegate! {
492        to self.pin {
493            fn pull_direction(&self, pull: Pull);
494            fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
495            fn init_input(&self, pull: Pull);
496            fn is_input_high(&self) -> bool;
497            fn enable_input(&self, on: bool);
498
499            fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
500            fn set_to_open_drain_output(&self);
501            fn set_to_push_pull_output(&self);
502            fn enable_output(&self, on: bool);
503            fn set_output_high(&self, on: bool);
504            fn set_drive_strength(&self, strength: gpio::DriveStrength);
505            fn enable_open_drain(&self, on: bool);
506            fn is_set_high(&self) -> bool;
507        }
508    }
509}
510
511#[derive(Clone)]
512enum InputConnectionInner {
513    Input(InputSignal),
514    DirectInput(DirectInputSignal),
515    Constant(Level),
516}
517
518/// A peripheral input signal connection.
519///
520/// This is mainly intended for internal use, but it can be used to connect
521/// peripherals within the MCU without external hardware.
522#[derive(Clone)]
523#[instability::unstable]
524pub struct InputConnection(InputConnectionInner);
525
526impl Peripheral for InputConnection {
527    type P = Self;
528
529    unsafe fn clone_unchecked(&self) -> Self::P {
530        self.clone()
531    }
532}
533
534impl From<InputSignal> for InputConnection {
535    fn from(input: InputSignal) -> Self {
536        Self(InputConnectionInner::Input(input))
537    }
538}
539
540impl From<Level> for InputConnection {
541    fn from(level: Level) -> Self {
542        Self(InputConnectionInner::Constant(level))
543    }
544}
545
546impl From<NoPin> for InputConnection {
547    fn from(_pin: NoPin) -> Self {
548        Self(InputConnectionInner::Constant(Level::Low))
549    }
550}
551
552impl<P> From<P> for InputConnection
553where
554    P: InputPin,
555{
556    fn from(input: P) -> Self {
557        Self(InputConnectionInner::Input(InputSignal::from(input)))
558    }
559}
560
561impl From<OutputSignal> for InputConnection {
562    fn from(output_signal: OutputSignal) -> Self {
563        Self(InputConnectionInner::Input(InputSignal {
564            pin: output_signal.pin,
565            is_inverted: output_signal.is_inverted,
566        }))
567    }
568}
569
570impl From<DirectOutputSignal> for InputConnection {
571    fn from(output_signal: DirectOutputSignal) -> Self {
572        Self(InputConnectionInner::DirectInput(DirectInputSignal::new(
573            output_signal.pin,
574        )))
575    }
576}
577
578impl From<OutputConnection> for InputConnection {
579    fn from(conn: OutputConnection) -> Self {
580        match conn.0 {
581            OutputConnectionInner::Output(inner) => inner.into(),
582            OutputConnectionInner::DirectOutput(inner) => inner.into(),
583            OutputConnectionInner::Constant(inner) => inner.into(),
584        }
585    }
586}
587
588impl From<Flex<'static>> for InputConnection {
589    fn from(pin: Flex<'static>) -> Self {
590        pin.peripheral_input().into()
591    }
592}
593
594impl Sealed for InputConnection {}
595
596impl InputConnection {
597    delegate::delegate! {
598        #[instability::unstable]
599        to match &self.0 {
600            InputConnectionInner::Input(pin) => pin,
601            InputConnectionInner::DirectInput(pin) => pin,
602            InputConnectionInner::Constant(level) => level,
603        } {
604            pub fn pull_direction(&self, pull: Pull);
605            pub fn init_input(&self, pull: Pull);
606            pub fn is_input_high(&self) -> bool;
607            pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
608            pub fn enable_input(&self, on: bool);
609
610            // This doesn't need to be public, the intended way is `connect_to` and `disconnect_from`
611            fn connect_input_to_peripheral(&self, signal: gpio::InputSignal);
612        }
613    }
614}
615
616enum OutputConnectionInner {
617    Output(OutputSignal),
618    DirectOutput(DirectOutputSignal),
619    Constant(Level),
620}
621
622/// A peripheral (input and) output signal connection.
623///
624/// This is mainly intended for internal use, but it can be used to connect
625/// peripherals within the MCU without external hardware.
626#[instability::unstable]
627pub struct OutputConnection(OutputConnectionInner);
628
629impl Sealed for OutputConnection {}
630
631impl Peripheral for OutputConnection {
632    type P = Self;
633
634    unsafe fn clone_unchecked(&self) -> Self::P {
635        match self {
636            Self(OutputConnectionInner::Output(signal)) => Self::from(signal.clone_unchecked()),
637            Self(OutputConnectionInner::DirectOutput(signal)) => {
638                Self::from(DirectOutputSignal::new(signal.pin.clone_unchecked()))
639            }
640            Self(OutputConnectionInner::Constant(level)) => Self::from(*level),
641        }
642    }
643}
644
645impl From<NoPin> for OutputConnection {
646    fn from(_pin: NoPin) -> Self {
647        Self(OutputConnectionInner::Constant(Level::Low))
648    }
649}
650
651impl From<Level> for OutputConnection {
652    fn from(level: Level) -> Self {
653        Self(OutputConnectionInner::Constant(level))
654    }
655}
656
657impl<P> From<P> for OutputConnection
658where
659    P: OutputPin,
660{
661    fn from(input: P) -> Self {
662        Self(OutputConnectionInner::Output(OutputSignal::from(input)))
663    }
664}
665
666impl From<OutputSignal> for OutputConnection {
667    fn from(signal: OutputSignal) -> Self {
668        Self(OutputConnectionInner::Output(signal))
669    }
670}
671
672impl From<Flex<'static>> for OutputConnection {
673    fn from(pin: Flex<'static>) -> Self {
674        pin.into_peripheral_output().into()
675    }
676}
677
678impl From<DirectOutputSignal> for OutputConnection {
679    fn from(signal: DirectOutputSignal) -> Self {
680        Self(OutputConnectionInner::DirectOutput(signal))
681    }
682}
683
684impl OutputConnection {
685    delegate::delegate! {
686        #[instability::unstable]
687        to match &self.0 {
688            OutputConnectionInner::Output(pin) => pin,
689            OutputConnectionInner::DirectOutput(pin) => pin,
690            OutputConnectionInner::Constant(level) => level,
691        } {
692            pub fn is_input_high(&self) -> bool;
693            pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
694
695            pub fn is_set_high(&self) -> bool;
696            pub fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
697            pub fn pull_direction(&self, pull: Pull);
698            pub fn init_input(&self, pull: Pull);
699            pub fn enable_input(&self, on: bool);
700
701            pub fn set_to_open_drain_output(&self);
702            pub fn set_to_push_pull_output(&self);
703            pub fn enable_output(&self, on: bool);
704            pub fn set_output_high(&self, on: bool);
705            pub fn set_drive_strength(&self, strength: gpio::DriveStrength);
706            pub fn enable_open_drain(&self, on: bool);
707
708            // These don't need to be public, the intended way is `connect_to` and `disconnect_from`
709            fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal);
710            fn disconnect_from_peripheral_output(&self, signal: gpio::OutputSignal);
711        }
712    }
713
714    pub(crate) fn connect_with_guard(
715        this: impl Peripheral<P = impl PeripheralOutput>,
716        signal: crate::gpio::OutputSignal,
717    ) -> PinGuard {
718        crate::into_mapped_ref!(this);
719        match &this.0 {
720            OutputConnectionInner::Output(pin) => {
721                PinGuard::new(unsafe { pin.pin.clone_unchecked() }, signal)
722            }
723            OutputConnectionInner::DirectOutput(pin) => {
724                PinGuard::new(unsafe { pin.pin.clone_unchecked() }, signal)
725            }
726            OutputConnectionInner::Constant(_) => PinGuard::new_unconnected(signal),
727        }
728    }
729}