//! # Peripheral signal interconnect using the GPIO matrix.
//!
//! The GPIO matrix offers flexible connection options between GPIO pins and
//! peripherals. This module offers capabilities not covered by GPIO pin types
//! and drivers, like routing fixed logic levels to peripheral inputs, or
//! inverting input and output signals.
//!
//! > Note that routing a signal through the GPIO matrix adds some latency to
//! > the signal. This is not a problem for most peripherals, but it can be an
//! > issue for high-speed peripherals like SPI or I2S. `esp-hal` tries to
//! > bypass the GPIO matrix when possible (e.g. when the pin can be configured
//! > as a suitable Alternate Function for the peripheral signal, and other
//! > settings are compatible), but silently falls back to the GPIO matrix for
//! > flexibility.
#![doc = concat!("## Relation to the ", crate::trm_markdown_link!("iomuxgpio"))]
//! The GPIO drivers implement IO MUX and pin functionality (input/output
//! buffers, pull resistors, etc.). The GPIO matrix is represented by signals
//! and the [`PeripheralInput`] and [`PeripheralOutput`] traits. There is some
//! overlap between them: signal routing depends on what type is passed to a
//! peripheral driver's pin setter functions.
//!
//! ## Signals
//!
//! GPIO signals are represented by the [`InputSignal`] and [`OutputSignal`]
//! structs. Peripheral drivers accept [`PeripheralInput`] and
//! [`PeripheralOutput`] implementations which are implemented for anything that
//! can be converted into the signal types:
//! - GPIO pins and drivers
//! - A fixed logic [`Level`]
//! - [`NoPin`]
//!
//! Note that some of these exist for convenience only. `Level` is meaningful as
//! a peripheral input, but not as a peripheral output. `NoPin` is a placeholder
//! for when a peripheral driver does not require a pin, but the API requires
//! one. It is equivalent to [`Level::Low`].
//!
//! ### Splitting drivers into signals
//!
//! Each GPIO pin driver such as [`Input`], can be converted
//! into input or output signals. [`Flex`], which can be either input or output,
//! can be [`split`](Flex::split) into both signals at once. These signals can
//! then be individually connected to a peripheral input or output signal. This
//! allows for flexible routing of signals between peripherals and GPIO pins.
//!
//! Note that only configured GPIO drivers can be safely turned into signals.
//! This conversion freezes the pin configuration, otherwise it would be
//! possible for multiple peripheral drivers to configure the same GPIO pin at
//! the same time, which is undefined behavior.
//!
//! ### Splitting pins into signals
//!
//! GPIO pin types such as [`GPIO0`] or [`AnyPin`] can be **unsafely**
//! [split](AnyPin::split) into signals. In this case you need to carefully
//! ensure that only a single driver configures the split pin, by selectively
//! [freezing](`InputSignal::freeze`) the signals.
#![cfg_attr(
    uart_driver_supported,
    doc = "\n\nFor example, if you want to route GPIO3 to both a Pulse Counter
 input and a [UART](crate::uart::Uart) RX line, you will need to make sure
 one of the signals is frozen, otherwise the driver that is configured later
 will overwrite the other driver's configuration. Configuring the signals on
 multiple cores is undefined behaviour unless you ensure the configuration
 does not happen at the same time."
)]
//! ### Using pins and signals
//!
//! A GPIO pin can be configured either with a GPIO driver such as [`Input`], or
//! by a peripheral driver using a pin assignment method such as
#![cfg_attr(spi_master_driver_supported, doc = "[`Spi::with_mosi`].")]
#![cfg_attr(not(spi_master_driver_supported), doc = "`Spi::with_mosi`.")]
//! The peripheral drivers' preferences can be overridden by
//! passing a pin driver to the peripheral driver. When converting a driver to
//! signals, the underlying signals will be initially
//! [frozen](InputSignal::freeze) to support this use case.
//!
//! ## Inverting inputs and outputs
//!
//! The GPIO matrix allows for inverting the input and output signals. This can
//! be configured via [`InputSignal::with_input_inverter`] and
//! [`OutputSignal::with_input_inverter`]. The hardware is configured
//! accordingly when the signal is connected to a peripheral input or output.
//!
//! ## Connection rules
//!
//! Peripheral signals and GPIOs can be connected with the following
//! constraints:
//!
//! - A peripheral input signal must be driven by exactly one signal, which can be a GPIO input or a
//!   constant level.
//! - A peripheral output signal can be connected to any number of GPIOs. These GPIOs can be
//!   configured differently. The peripheral drivers will only support a single connection (that is,
//!   they disconnect previously configured signals on repeat calls to the same function), but you
//!   can use `esp_hal::gpio::OutputSignal::connect_to` (note that the type is currently hidden from
//!   the documentation) to connect multiple GPIOs to the same output signal.
//! - A GPIO input signal can be connected to any number of peripheral inputs.
//! - A GPIO output can be driven by only one peripheral output.
//!
//! [`GPIO0`]: crate::peripherals::GPIO0
#![cfg_attr(
    spi_master_driver_supported,
    doc = "[`Spi::with_mosi`]: crate::spi::master::Spi::with_mosi"
)]

#[cfg(feature = "unstable")]
use crate::gpio::{Input, Output};
use crate::{
    gpio::{
        self,
        AlternateFunction,
        AnyPin,
        Flex,
        InputPin,
        Level,
        NoPin,
        OutputPin,
        Pin,
        PinGuard,
    },
    peripherals::GPIO,
    private::{self, Sealed},
};

/// The base of all peripheral signals.
///
/// This trait represents a signal in the GPIO matrix. Signals are converted or
/// split from GPIO pins and can be connected to peripheral inputs and outputs.
///
/// All signals can be peripheral inputs, but not all output-like types should
/// be allowed to be passed as inputs. This trait bridges this gap by defining
/// the logic, but not declaring the signal to be an actual Input signal.
pub trait PeripheralSignal<'d>: Sealed {
    /// Connects the peripheral input to an input signal source.
    #[doc(hidden)] // Considered unstable
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal);
}

/// A signal that can be connected to a peripheral input.
///
/// Peripheral drivers are encouraged to accept types that implement this and
/// [`PeripheralOutput`] as arguments instead of pin types.
#[allow(
    private_bounds,
    reason = "InputSignal is unstable, but the trait needs to be public"
)]
pub trait PeripheralInput<'d>: Into<InputSignal<'d>> + PeripheralSignal<'d> {}

/// A signal that can be connected to a peripheral input and/or output.
///
/// Peripheral drivers are encouraged to accept types that implement this and
/// [`PeripheralInput`] as arguments instead of pin types.
#[allow(
    private_bounds,
    reason = "OutputSignal is unstable, but the trait needs to be public"
)]
pub trait PeripheralOutput<'d>: Into<OutputSignal<'d>> + PeripheralSignal<'d> {
    /// Connects the peripheral output to an output signal target.
    #[doc(hidden)] // Considered unstable
    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal);

    /// Disconnects the peripheral output from an output signal target.
    ///
    /// This function clears the entry in the IO MUX that
    /// associates this output pin with a previously connected
    /// [signal](`gpio::OutputSignal`). Any other outputs connected to the
    /// peripheral remain intact.
    #[doc(hidden)] // Considered unstable
    fn disconnect_from_peripheral_output(&self);
}

// Pins
impl<'d, P> PeripheralSignal<'d> for P
where
    P: Pin + 'd,
{
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        let pin = unsafe { AnyPin::steal(self.number()) };
        InputSignal::new(pin).connect_input_to_peripheral(signal);
    }
}
impl<'d, P> PeripheralInput<'d> for P where P: InputPin + 'd {}

impl<'d, P> PeripheralOutput<'d> for P
where
    P: OutputPin + 'd,
{
    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) {
        let pin = unsafe { AnyPin::steal(self.number()) };
        OutputSignal::new(pin).connect_peripheral_to_output(signal);
    }
    fn disconnect_from_peripheral_output(&self) {
        let pin = unsafe { AnyPin::steal(self.number()) };
        OutputSignal::new(pin).disconnect_from_peripheral_output();
    }
}

// Pin drivers
#[instability::unstable]
impl<'d> PeripheralSignal<'d> for Flex<'d> {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        self.pin.connect_input_to_peripheral(signal);
    }
}
#[instability::unstable]
impl<'d> PeripheralInput<'d> for Flex<'d> {}
#[instability::unstable]
impl<'d> PeripheralOutput<'d> for Flex<'d> {
    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) {
        self.pin.connect_peripheral_to_output(signal);
    }
    fn disconnect_from_peripheral_output(&self) {
        self.pin.disconnect_from_peripheral_output();
    }
}

#[instability::unstable]
impl<'d> PeripheralSignal<'d> for Input<'d> {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        self.pin.connect_input_to_peripheral(signal);
    }
}
#[instability::unstable]
impl<'d> PeripheralInput<'d> for Input<'d> {}

#[instability::unstable]
impl<'d> PeripheralSignal<'d> for Output<'d> {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        self.pin.connect_input_to_peripheral(signal);
    }
}
#[instability::unstable]
impl<'d> PeripheralOutput<'d> for Output<'d> {
    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) {
        self.pin.connect_peripheral_to_output(signal);
    }
    fn disconnect_from_peripheral_output(&self) {
        self.pin.disconnect_from_peripheral_output();
    }
}

// Placeholders
impl PeripheralSignal<'_> for NoPin {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        // Arbitrary choice but we need to overwrite a previous signal input
        // association.
        Level::Low.connect_input_to_peripheral(signal);
    }
}
impl PeripheralInput<'_> for NoPin {}
impl PeripheralOutput<'_> for NoPin {
    fn connect_peripheral_to_output(&self, _: gpio::OutputSignal) {
        // A peripheral's outputs may be connected to any number of GPIOs.
        // Connecting to, and disconnecting from a NoPin is therefore a
        // no-op, as we are adding and removing nothing from that list of
        // connections.
    }
    fn disconnect_from_peripheral_output(&self) {
        // A peripheral's outputs may be connected to any number of GPIOs.
        // Connecting to, and disconnecting from a NoPin is therefore a
        // no-op, as we are adding and removing nothing from that list of
        // connections.
    }
}

impl PeripheralSignal<'_> for Level {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        Signal::Level(*self).connect_to_peripheral_input(signal, false, true);
    }
}
impl PeripheralInput<'_> for Level {}
impl PeripheralOutput<'_> for Level {
    fn connect_peripheral_to_output(&self, _: gpio::OutputSignal) {
        // There is no such thing as a constant-high level peripheral output,
        // the implementation just exists for convenience.
    }
    fn disconnect_from_peripheral_output(&self) {
        // There is no such thing as a constant-high level peripheral output,
        // the implementation just exists for convenience.
    }
}

// Split signals
impl<'d> PeripheralSignal<'d> for InputSignal<'d> {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        // Since there can only be one input signal connected to a peripheral
        // at a time, this function will disconnect any previously
        // connected input signals.
        self.pin.connect_to_peripheral_input(
            signal,
            self.is_input_inverted(),
            self.is_gpio_matrix_forced(),
        );
    }
}
impl<'d> PeripheralInput<'d> for InputSignal<'d> {}

impl<'d> PeripheralSignal<'d> for OutputSignal<'d> {
    fn connect_input_to_peripheral(&self, signal: gpio::InputSignal) {
        self.pin.connect_to_peripheral_input(
            signal,
            self.is_input_inverted(),
            self.is_gpio_matrix_forced(),
        );
    }
}
impl<'d> PeripheralOutput<'d> for OutputSignal<'d> {
    fn connect_peripheral_to_output(&self, signal: gpio::OutputSignal) {
        self.pin.connect_peripheral_to_output(
            signal,
            self.is_output_inverted(),
            self.is_gpio_matrix_forced(),
            true,
            false,
        );
    }
    fn disconnect_from_peripheral_output(&self) {
        self.pin.disconnect_from_peripheral_output();
    }
}

impl gpio::InputSignal {
    fn can_use_gpio_matrix(self) -> bool {
        self as usize <= property!("gpio.input_signal_max")
    }

    /// Connects a peripheral input signal to a GPIO or a constant level.
    ///
    /// Note that connecting multiple GPIOs to a single peripheral input is not
    /// possible and the previous connection will be replaced.
    ///
    /// Also note that a peripheral input must always be connected to something,
    /// so if you want to disconnect it from GPIOs, you should connect it to a
    /// constant level.
    ///
    /// This function allows connecting a peripheral input to either a
    /// [`PeripheralInput`] or [`PeripheralOutput`] implementation.
    #[inline]
    #[instability::unstable]
    pub fn connect_to<'a>(self, pin: &impl PeripheralSignal<'a>) {
        pin.connect_input_to_peripheral(self);
    }
}

impl gpio::OutputSignal {
    fn can_use_gpio_matrix(self) -> bool {
        self as usize <= property!("gpio.output_signal_max")
    }

    /// Connects a peripheral output signal to a GPIO.
    ///
    /// Note that connecting multiple output signals to a single GPIO is not
    /// possible and the previous connection will be replaced.
    ///
    /// Also note that it is possible to connect a peripheral output signal to
    /// multiple GPIOs, and old connections will not be cleared automatically.
    #[inline]
    #[instability::unstable]
    pub fn connect_to<'d>(self, pin: &impl PeripheralOutput<'d>) {
        pin.connect_peripheral_to_output(self);
    }

    /// Disconnects a peripheral output signal from a GPIO.
    #[inline]
    #[instability::unstable]
    pub fn disconnect_from<'d>(self, pin: &impl PeripheralOutput<'d>) {
        pin.disconnect_from_peripheral_output();
    }
}

enum Signal<'d> {
    Pin(AnyPin<'d>),
    Level(Level),
}
impl Signal<'_> {
    fn gpio_number(&self) -> Option<u8> {
        match &self {
            Signal::Pin(pin) => Some(pin.number()),
            Signal::Level(_) => None,
        }
    }

    unsafe fn clone_unchecked(&self) -> Self {
        match self {
            Signal::Pin(pin) => Signal::Pin(unsafe { pin.clone_unchecked() }),
            Signal::Level(level) => Signal::Level(*level),
        }
    }

    fn is_set_high(&self) -> bool {
        match &self {
            Signal::Pin(signal) => signal.is_set_high(),
            Signal::Level(level) => *level == Level::High,
        }
    }

    fn is_input_high(&self) -> bool {
        match &self {
            Signal::Pin(signal) => signal.is_input_high(),
            Signal::Level(level) => *level == Level::High,
        }
    }

    fn connect_to_peripheral_input(
        &self,
        signal: gpio::InputSignal,
        is_inverted: bool,
        force_gpio: bool,
    ) {
        let use_gpio_matrix = match self {
            Signal::Pin(pin) => {
                let af = if is_inverted || force_gpio {
                    AlternateFunction::GPIO
                } else {
                    pin.input_signals(private::Internal)
                        .iter()
                        .find(|(_af, s)| *s == signal)
                        .map(|(af, _)| *af)
                        .unwrap_or(AlternateFunction::GPIO)
                };
                pin.disable_usb_pads();
                pin.set_alternate_function(af);
                af == AlternateFunction::GPIO
            }
            Signal::Level(_) => true,
        };

        if !signal.can_use_gpio_matrix() {
            assert!(
                !use_gpio_matrix,
                "{:?} cannot be routed through the GPIO matrix",
                signal
            );
            // At this point we have set up the AF. The signal does not have a `func_in_sel_cfg`
            // register, and we must not try to write to it.
            return;
        }

        let input = match self {
            Signal::Pin(pin) => pin.number(),
            Signal::Level(Level::Low) => property!("gpio.constant_0_input"),
            Signal::Level(Level::High) => property!("gpio.constant_1_input"),
        };

        // No need for a critical section, this is a write and not a modify operation.
        let offset = property!("gpio.func_in_sel_offset");
        GPIO::regs()
            .func_in_sel_cfg(signal as usize - offset)
            .write(|w| unsafe {
                w.sel().bit(use_gpio_matrix);
                w.in_inv_sel().bit(is_inverted);
                // Connect to GPIO or constant level
                w.in_sel().bits(input)
            });
    }

    fn connect_peripheral_to_output(
        &self,
        signal: gpio::OutputSignal,
        is_inverted: bool,
        force_gpio: bool,
        peripheral_control_output_enable: bool,
        invert_output_enable: bool,
    ) {
        let Signal::Pin(pin) = self else {
            return;
        };
        let af = if is_inverted || force_gpio {
            AlternateFunction::GPIO
        } else {
            pin.output_signals(private::Internal)
                .iter()
                .find(|(_af, s)| *s == signal)
                .map(|(af, _)| *af)
                .unwrap_or(AlternateFunction::GPIO)
        };
        pin.disable_usb_pads();
        pin.set_alternate_function(af);

        let use_gpio_matrix = af == AlternateFunction::GPIO;

        assert!(
            signal.can_use_gpio_matrix() || !use_gpio_matrix,
            "{:?} cannot be routed through the GPIO matrix",
            signal
        );

        GPIO::regs()
            .func_out_sel_cfg(pin.number() as usize)
            .write(|w| unsafe {
                if use_gpio_matrix {
                    // Ignored if the signal is not routed through the GPIO matrix - alternate
                    // function selects peripheral signal directly.
                    w.out_sel().bits(signal as _);
                    w.inv_sel().bit(is_inverted);
                }
                w.oen_sel().bit(!peripheral_control_output_enable);
                w.oen_inv_sel().bit(invert_output_enable)
            });
    }

    fn disconnect_from_peripheral_output(&self) {
        let Some(number) = self.gpio_number() else {
            return;
        };
        GPIO::regs()
            .func_out_sel_cfg(number as usize)
            .modify(|_, w| unsafe { w.out_sel().bits(gpio::OutputSignal::GPIO as _) });
    }
}

bitflags::bitflags! {
    #[derive(Clone, Copy)]
    struct InputFlags: u8 {
        const ForceGpioMatrix = 1 << 0;
        const Frozen          = 1 << 1;
        const InvertInput     = 1 << 2;
    }
}

/// An input signal between a peripheral and a GPIO pin.
///
/// If the `InputSignal` was obtained from a pin driver such as
/// [`Input`](crate::gpio::Input::split), the GPIO driver will be responsible
/// for configuring the pin with the correct settings, peripheral drivers will
/// not be able to modify the pin settings.
///
/// Multiple input signals can be connected to one pin.
#[instability::unstable]
pub struct InputSignal<'d> {
    pin: Signal<'d>,
    flags: InputFlags,
}

impl From<Level> for InputSignal<'_> {
    fn from(level: Level) -> Self {
        InputSignal::new_level(level)
    }
}

impl From<NoPin> for InputSignal<'_> {
    fn from(_pin: NoPin) -> Self {
        InputSignal::new_level(Level::Low)
    }
}

impl<'d, P> From<P> for InputSignal<'d>
where
    P: Pin + 'd,
{
    fn from(input: P) -> Self {
        InputSignal::new(input.degrade())
    }
}

impl<'d> From<Flex<'d>> for InputSignal<'d> {
    fn from(pin: Flex<'d>) -> Self {
        pin.peripheral_input()
    }
}

#[instability::unstable]
impl<'d> From<Input<'d>> for InputSignal<'d> {
    fn from(pin: Input<'d>) -> Self {
        pin.pin.into()
    }
}

impl Sealed for InputSignal<'_> {}

impl Clone for InputSignal<'_> {
    fn clone(&self) -> Self {
        Self {
            pin: unsafe { self.pin.clone_unchecked() },
            flags: self.flags,
        }
    }
}

impl<'d> InputSignal<'d> {
    fn new_inner(inner: Signal<'d>) -> Self {
        Self {
            pin: inner,
            flags: InputFlags::empty(),
        }
    }

    pub(crate) fn new(pin: AnyPin<'d>) -> Self {
        Self::new_inner(Signal::Pin(pin))
    }

    pub(crate) fn new_level(level: Level) -> Self {
        Self::new_inner(Signal::Level(level))
    }

    /// Freezes the pin configuration.
    ///
    /// This will prevent peripheral drivers using this signal from modifying
    /// the pin settings.
    pub fn freeze(mut self) -> Self {
        self.flags.insert(InputFlags::Frozen);
        self
    }

    /// Unfreezes the pin configuration.
    ///
    /// This will enable peripheral drivers to modify the pin settings
    /// again.
    ///
    /// # Safety
    ///
    /// This function is unsafe because it allows peripherals to modify the pin
    /// configuration again. This can lead to undefined behavior if the pin
    /// is being configured by multiple peripherals at the same time. It can
    /// also lead to surprising behavior if the pin is passed to multiple
    /// peripherals that expect conflicting settings.
    pub unsafe fn unfreeze(&mut self) {
        self.flags.remove(InputFlags::Frozen);
    }

    /// Returns the GPIO number of the underlying pin.
    ///
    /// Returns `None` if the signal is a constant level.
    pub fn gpio_number(&self) -> Option<u8> {
        self.pin.gpio_number()
    }

    /// Returns `true` if the input signal is high.
    ///
    /// Note that this does not take [`Self::with_input_inverter`] into account.
    pub fn is_input_high(&self) -> bool {
        self.pin.is_input_high()
    }

    /// Returns the current signal level.
    ///
    /// Note that this does not take [`Self::with_input_inverter`] into account.
    pub fn level(&self) -> Level {
        self.is_input_high().into()
    }

    /// Returns `true` if the input signal is configured to be inverted.
    ///
    /// Note that the hardware is not configured until the signal is actually
    /// connected to a peripheral.
    pub fn is_input_inverted(&self) -> bool {
        self.flags.contains(InputFlags::InvertInput)
    }

    /// Consumes the signal and returns a new one that inverts the peripheral's
    /// input signal.
    pub fn with_input_inverter(mut self, invert: bool) -> Self {
        self.flags.set(InputFlags::InvertInput, invert);
        self
    }

    /// Consumes the signal and returns a new one that forces the GPIO matrix
    /// to be used.
    pub fn with_gpio_matrix_forced(mut self, force: bool) -> Self {
        self.flags.set(InputFlags::ForceGpioMatrix, force);
        self
    }

    /// Returns `true` if the input signal must be routed through the GPIO
    /// matrix.
    pub fn is_gpio_matrix_forced(&self) -> bool {
        self.flags.contains(InputFlags::ForceGpioMatrix)
    }

    delegate::delegate! {
        #[instability::unstable]
        #[doc(hidden)]
        to match &self.pin {
            Signal::Pin(signal) => signal,
            Signal::Level(_) => NoOp,
        } {
            pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
        }
    }

    delegate::delegate! {
        #[instability::unstable]
        #[doc(hidden)]
        to match &self.pin {
            Signal::Pin(_) if self.flags.contains(InputFlags::Frozen) => NoOp,
            Signal::Pin(signal) => signal,
            Signal::Level(_) => NoOp,
        } {
            pub fn apply_input_config(&self, _config: &gpio::InputConfig);
            pub fn set_input_enable(&self, on: bool);
        }
    }
}

bitflags::bitflags! {
    #[derive(Clone, Copy)]
    struct OutputFlags: u8 {
        const ForceGpioMatrix = 1 << 0;
        const Frozen          = 1 << 1;
        const InvertInput     = 1 << 2;
        const InvertOutput    = 1 << 3;
    }
}

/// An (input and) output signal between a peripheral and a GPIO pin.
///
/// If the `OutputSignal` was obtained from a pin driver such as
/// [`Output`](crate::gpio::Output::split), the GPIO driver will be responsible
/// for configuring the pin with the correct settings, peripheral drivers will
/// not be able to modify the pin settings.
///
/// Note that connecting this to a peripheral input will enable the input stage
/// of the GPIO pin.
///
/// Multiple pins can be connected to one output signal.
#[instability::unstable]
pub struct OutputSignal<'d> {
    pin: Signal<'d>,
    flags: OutputFlags,
}

impl Sealed for OutputSignal<'_> {}

impl From<Level> for OutputSignal<'_> {
    fn from(level: Level) -> Self {
        OutputSignal::new_level(level)
    }
}

impl From<NoPin> for OutputSignal<'_> {
    fn from(_pin: NoPin) -> Self {
        OutputSignal::new_level(Level::Low)
    }
}

impl<'d, P> From<P> for OutputSignal<'d>
where
    P: OutputPin + 'd,
{
    fn from(output: P) -> Self {
        OutputSignal::new(output.degrade())
    }
}

impl<'d> From<Flex<'d>> for OutputSignal<'d> {
    fn from(pin: Flex<'d>) -> Self {
        pin.into_peripheral_output()
    }
}

#[instability::unstable]
impl<'d> From<Output<'d>> for OutputSignal<'d> {
    fn from(pin: Output<'d>) -> Self {
        pin.pin.into()
    }
}

impl<'d> OutputSignal<'d> {
    fn new_inner(inner: Signal<'d>) -> Self {
        Self {
            pin: inner,
            flags: OutputFlags::empty(),
        }
    }

    pub(crate) fn new(pin: AnyPin<'d>) -> Self {
        Self::new_inner(Signal::Pin(pin))
    }

    pub(crate) fn new_level(level: Level) -> Self {
        Self::new_inner(Signal::Level(level))
    }

    /// Freezes the pin configuration.
    ///
    /// This will prevent peripheral drivers using this signal from
    /// modifying the pin settings.
    pub fn freeze(mut self) -> Self {
        self.flags.insert(OutputFlags::Frozen);
        self
    }

    /// Unfreezes the pin configuration.
    ///
    /// This will enable peripheral drivers to modify the pin settings
    /// again.
    ///
    /// # Safety
    ///
    /// This function is unsafe because it allows peripherals to modify the pin
    /// configuration again. This can lead to undefined behavior if the pin
    /// is being configured by multiple peripherals at the same time.
    /// It can also lead to surprising behavior if the pin is passed to multiple
    /// peripherals that expect conflicting settings.
    pub unsafe fn unfreeze(&mut self) {
        self.flags.remove(OutputFlags::Frozen);
    }

    /// Returns the GPIO number of the underlying pin.
    ///
    /// Returns `None` if the signal is a constant level.
    pub fn gpio_number(&self) -> Option<u8> {
        self.pin.gpio_number()
    }

    /// Returns `true` if the input signal is configured to be inverted.
    ///
    /// Note that the hardware is not configured until the signal is actually
    /// connected to a peripheral.
    pub fn is_input_inverted(&self) -> bool {
        self.flags.contains(OutputFlags::InvertInput)
    }

    /// Returns `true` if the output signal is configured to be inverted.
    ///
    /// Note that the hardware is not configured until the signal is actually
    /// connected to a peripheral.
    pub fn is_output_inverted(&self) -> bool {
        self.flags.contains(OutputFlags::InvertOutput)
    }

    /// Consumes the signal and returns a new one that inverts the peripheral's
    /// output signal.
    pub fn with_output_inverter(mut self, invert: bool) -> Self {
        self.flags.set(OutputFlags::InvertOutput, invert);
        self
    }

    /// Consumes the signal and returns a new one that inverts the peripheral's
    /// input signal.
    pub fn with_input_inverter(mut self, invert: bool) -> Self {
        self.flags.set(OutputFlags::InvertInput, invert);
        self
    }

    /// Consumes the signal and returns a new one that forces the GPIO matrix
    /// to be used.
    pub fn with_gpio_matrix_forced(mut self, force: bool) -> Self {
        self.flags.set(OutputFlags::ForceGpioMatrix, force);
        self
    }

    /// Returns `true` if the input signal must be routed through the GPIO
    /// matrix.
    pub fn is_gpio_matrix_forced(&self) -> bool {
        self.flags.contains(OutputFlags::ForceGpioMatrix)
    }

    /// Returns `true` if the input signal is high.
    ///
    /// Note that this does not take [`Self::with_input_inverter`] into account.
    pub fn is_input_high(&self) -> bool {
        self.pin.is_input_high()
    }

    /// Returns `true` if the output signal is set high.
    ///
    /// Note that this does not take [`Self::with_output_inverter`] into
    /// account.
    pub fn is_set_high(&self) -> bool {
        self.pin.is_set_high()
    }

    #[doc(hidden)]
    #[instability::unstable]
    #[cfg_attr(
        not(any(
            i2c_master_driver_supported,
            spi_master_driver_supported,
            uart_driver_supported
        )),
        expect(unused)
    )]
    pub(crate) fn connect_with_guard(self, signal: crate::gpio::OutputSignal) -> PinGuard {
        signal.connect_to(&self);
        match self.pin {
            Signal::Pin(pin) => PinGuard::new(pin),
            Signal::Level(_) => PinGuard::new_unconnected(),
        }
    }

    delegate::delegate! {
        #[instability::unstable]
        #[doc(hidden)]
        to match &self.pin {
            Signal::Pin(signal) => signal,
            Signal::Level(_) => NoOp,
        } {
            pub fn input_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::InputSignal)];
            pub fn output_signals(&self, _internal: private::Internal) -> &'static [(AlternateFunction, gpio::OutputSignal)];
        }
    }

    delegate::delegate! {
        #[instability::unstable]
        #[doc(hidden)]
        to match &self.pin {
            Signal::Pin(_) if self.flags.contains(OutputFlags::Frozen) => NoOp,
            Signal::Pin(pin) => pin,
            Signal::Level(_) => NoOp,
        } {
            pub fn apply_input_config(&self, _config: &gpio::InputConfig);
            pub fn apply_output_config(&self, _config: &gpio::OutputConfig);
            pub fn set_input_enable(&self, on: bool);
            pub fn set_output_enable(&self, on: bool);
            pub fn set_output_high(&self, on: bool);
        }
    }
}

struct NoOp;

impl NoOp {
    fn set_input_enable(&self, _on: bool) {}
    fn set_output_enable(&self, _on: bool) {}
    fn set_output_high(&self, _on: bool) {}
    fn apply_input_config(&self, _config: &gpio::InputConfig) {}
    fn apply_output_config(&self, _config: &gpio::OutputConfig) {}

    fn input_signals(
        &self,
        _: private::Internal,
    ) -> &'static [(AlternateFunction, gpio::InputSignal)] {
        &[]
    }

    fn output_signals(
        &self,
        _: private::Internal,
    ) -> &'static [(AlternateFunction, gpio::OutputSignal)] {
        &[]
    }
}

#[procmacros::doc_replace]
/// ```rust,compile_fail
/// // Regression test for <https://github.com/esp-rs/esp-hal/issues/3313>
/// // This test case is expected to generate the following error:
/// // error[E0277]: the trait bound `Output<'_>: PeripheralInput<'_>` is not satisfied
/// //   --> src\gpio\interconnect.rs:977:5
/// //    |
/// // 31 |   function_expects_input(
/// //    |   ---------------------- required by a bound introduced by this call
/// // 32 | /     Output::new(peripherals.GPIO0,
/// // 33 | |     Level::Low,
/// // 34 | |     Default::default()),
/// //    | |_______________________^ the trait `InputPin` is not implemented for `Output<'_>`
/// // FIXME: due to <https://github.com/rust-lang/rust/issues/139924> this test may be ineffective.
/// //        It can be manually verified by changing it to `no_run` for a `run-doc-tests` run.
/// # {before_snippet}
/// use esp_hal::gpio::{Output, Level, interconnect::PeripheralInput};
///
/// fn function_expects_input<'d>(_: impl PeripheralInput<'d>) {}
///
/// function_expects_input(
///     Output::new(peripherals.GPIO0,
///     Level::Low,
///     Default::default()),
/// );
///
/// # {after_snippet}
/// ```
fn _compile_tests() {}
