#![cfg_attr(docsrs, procmacros::doc_replace)]
//! # Parallel IO (PARL_IO)
//!
//! ## Overview
//! The Parallel IO peripheral is a general purpose parallel interface that can
//! be used to connect to external devices such as LED matrix, LCD display,
//! Printer and Camera. The peripheral has independent TX and RX units. Each
//! unit can have up to 8 or 16 data signals (depending on your target hardware)
//! plus 1 or 2 clock signals.
//!
//! ## Configuration
//! The driver uses DMA (Direct Memory Access) for efficient data transfer.
//!
//! ## Examples
//! ### Initialization for RX
//!
//! ```rust, no_run
//! # {before_snippet}
//! # use esp_hal::delay::Delay;
//! # use esp_hal::dma_buffers;
//! # use esp_hal::dma::DmaRxBuf;
//! # use esp_hal::gpio::NoPin;
//! # use esp_hal::parl_io::{BitPackOrder, ParlIo, RxConfig, RxFourBits};
//!
//! // Initialize DMA buffer and descriptors for data reception
//! let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0);
//! let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?;
//! let dma_channel = peripherals.DMA_CH0;
//!
//! // Configure the 4-bit input pins and clock pin
//! let mut rx_pins = RxFourBits::new(
//!     peripherals.GPIO1,
//!     peripherals.GPIO2,
//!     peripherals.GPIO3,
//!     peripherals.GPIO4,
//! );
//! let mut rx_clk_pin = NoPin;
//!
//! // Set up Parallel IO for 1MHz data input, with DMA and bit packing
//! //  configuration
//! let parl_io = ParlIo::new(peripherals.PARL_IO, dma_channel)?;
//!
//! let config = RxConfig::default()
//!     .with_frequency(Rate::from_mhz(1))
//!     .with_bit_order(BitPackOrder::Msb)
//!     .with_timeout_ticks(0xfff);
//!
//! let mut parl_io_rx = parl_io.rx.with_config(rx_pins, rx_clk_pin, config)?;
//!
//! // Initialize the buffer and delay
//! dma_rx_buf.as_mut_slice().fill(0u8);
//! let delay = Delay::new();
//!
//! loop {
//!     // Read data via DMA and print received values
//!     let transfer = parl_io_rx.read(Some(dma_rx_buf.len()), dma_rx_buf)?;
//!     (_, parl_io_rx, dma_rx_buf) = transfer.wait();
//!
//!     delay.delay_millis(500);
//! }
//! # }
//! ```
//!
//! ### Initialization for TX
//! ```rust, no_run
//! # {before_snippet}
//! # use esp_hal::delay::Delay;
//! # use esp_hal::dma_tx_buffer;
//! # use esp_hal::parl_io::{BitPackOrder, ParlIo, TxFourBits, ClkOutPin, TxConfig, TxPinConfigWithValidPin};
//!
//! // Initialize DMA buffer and descriptors for data reception
//! let mut dma_tx_buf = dma_tx_buffer!(32000).unwrap();
//! let dma_channel = peripherals.DMA_CH0;
//!
//! // Configure the 4-bit input pins and clock pin
//! let tx_pins = TxFourBits::new(
//!     peripherals.GPIO1,
//!     peripherals.GPIO2,
//!     peripherals.GPIO3,
//!     peripherals.GPIO4,
//! );
//!
//! let mut pin_conf = TxPinConfigWithValidPin::new(tx_pins, peripherals.GPIO5);
//!
//! // Set up Parallel IO for 1MHz data input, with DMA and bit packing
//! //  configuration
//!  let parl_io = ParlIo::new(
//!     peripherals.PARL_IO,
//!     dma_channel,
//! )?;
//!
//! let mut clock_pin = ClkOutPin::new(peripherals.GPIO8);
//! let config = TxConfig::default()
//!     .with_frequency(Rate::from_mhz(1))
//!     .with_bit_order(BitPackOrder::Msb);
//!
//! let mut parl_io_tx = parl_io
//!     .tx
//!     .with_config(
//!         pin_conf,
//!         clock_pin,
//!         config,
//!     )?;
//!
//! for i in 0..dma_tx_buf.len() {
//!      dma_tx_buf.as_mut_slice()[i] = (i % 255) as u8;
//! }
//!
//! let delay = Delay::new();
//! loop {
//!     let transfer = parl_io_tx.write(dma_tx_buf.len(), dma_tx_buf)?;
//!     (_, parl_io_tx, dma_tx_buf) = transfer.wait();
//!     delay.delay_millis(500);
//! }
//! # }
//! ```

use core::{
    mem::ManuallyDrop,
    ops::{Deref, DerefMut},
};

use enumset::{EnumSet, EnumSetType};
use private::*;

use crate::{
    Async,
    Blocking,
    DriverMode,
    dma::{
        Channel,
        ChannelRx,
        ChannelTx,
        DmaChannelFor,
        DmaError,
        DmaPeripheral,
        DmaRxBuffer,
        DmaTxBuffer,
        PeripheralRxChannel,
        PeripheralTxChannel,
    },
    gpio::{
        self,
        InputSignal,
        NoPin,
        OutputSignal,
        interconnect::{self, PeripheralInput, PeripheralOutput},
    },
    interrupt::InterruptHandler,
    parl_io::asynch::interrupt_handler,
    peripherals::{Interrupt, PARL_IO, PCR},
    system::{self, GenericPeripheralGuard},
    time::Rate,
};

const MAX_DMA_SIZE: usize = 65535;

/// Interrupts generated by the peripheral
#[derive(Debug, EnumSetType)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ParlIoInterrupt {
    /// Triggered when TX FIFO is empty. This interrupt indicates that there
    /// might be error in the data sent by TX.
    TxFifoReEmpty,
    /// Triggered when RX FIFO is full. This interrupt indicates that there
    /// might be error in the data received by RX.
    RxFifoWOvf,
    /// Triggered when TX finishes sending a complete frame of data.
    TxEof,
}

/// Parallel IO errors
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
pub enum Error {
    /// General DMA error
    DmaError(DmaError),
    /// Maximum transfer size (32736) exceeded
    MaxDmaTransferSizeExceeded,
}

impl From<DmaError> for Error {
    fn from(value: DmaError) -> Self {
        Error::DmaError(value)
    }
}

/// Parallel IO sample edge
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SampleEdge {
    /// Positive edge
    Normal = 0,
    /// Negative edge
    Invert = 1,
}

/// Parallel IO bit packing order
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BitPackOrder {
    /// Bit pack order: MSB
    Msb = 0,
    /// Bit pack order: LSB
    Lsb = 1,
}

#[cfg(esp32c6)]
/// Enable Mode
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum EnableMode {
    /// Enable at high level
    HighLevel,
    /// Enable at low level
    LowLevel,
    /// Positive pulse start (data bit included) & Positive pulse end (data bit
    /// included)
    PulseMode1,
    /// Positive pulse start (data bit included) & Positive pulse end (data bit
    /// excluded)
    PulseMode2,
    /// Positive pulse start (data bit excluded) & Positive pulse end (data bit
    /// included)
    PulseMode3,
    /// Positive pulse start (data bit excluded) & Positive pulse end (data bit
    /// excluded)
    PulseMode4,
    /// Positive pulse start (data bit included) & Length end
    PulseMode5,
    /// Positive pulse start (data bit excluded) & Length end
    PulseMode6,
    /// Negative pulse start (data bit included) & Negative pulse end(data bit
    /// included)
    PulseMode7,
    /// Negative pulse start (data bit included) & Negative pulse end (data bit
    /// excluded)
    PulseMode8,
    /// Negative pulse start (data bit excluded) & Negative pulse end (data bit
    /// included)
    PulseMode9,
    /// Negative pulse start (data bit excluded) & Negative pulse end (data bit
    /// excluded)
    PulseMode10,
    /// Negative pulse start (data bit included) & Length end
    PulseMode11,
    /// Negative pulse start (data bit excluded) & Length end
    PulseMode12,
}

#[cfg(esp32c6)]
impl EnableMode {
    fn pulse_submode_sel(&self) -> Option<u8> {
        match self {
            EnableMode::PulseMode1 => Some(0),
            EnableMode::PulseMode2 => Some(1),
            EnableMode::PulseMode3 => Some(2),
            EnableMode::PulseMode4 => Some(3),
            EnableMode::PulseMode5 => Some(4),
            EnableMode::PulseMode6 => Some(5),
            EnableMode::PulseMode7 => Some(6),
            EnableMode::PulseMode8 => Some(7),
            EnableMode::PulseMode9 => Some(8),
            EnableMode::PulseMode10 => Some(9),
            EnableMode::PulseMode11 => Some(10),
            EnableMode::PulseMode12 => Some(11),
            _ => None,
        }
    }

    fn level_submode_sel(&self) -> Option<u8> {
        match self {
            EnableMode::HighLevel => Some(0),
            EnableMode::LowLevel => Some(1),
            _ => None,
        }
    }

    fn smp_model_sel(&self) -> Option<self::private::SampleMode> {
        match self {
            EnableMode::HighLevel => Some(self::private::SampleMode::ExternalLevel),
            EnableMode::LowLevel => Some(self::private::SampleMode::ExternalLevel),
            _ => Some(self::private::SampleMode::ExternalPulse),
        }
    }
}

#[cfg(esp32h2)]
/// Enable Mode
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum EnableMode {
    /// Enable at high level
    HighLevel,
    /// Positive pulse start (data bit included) & Positive pulse end (data bit
    /// included)
    PulseMode1,
    /// Positive pulse start (data bit included) & Positive pulse end (data bit
    /// excluded)
    PulseMode2,
    /// Positive pulse start (data bit excluded) & Positive pulse end (data bit
    /// included)
    PulseMode3,
    /// Positive pulse start (data bit excluded) & Positive pulse end (data bit
    /// excluded)
    PulseMode4,
    /// Positive pulse start (data bit included) & Length end
    PulseMode5,
    /// Positive pulse start (data bit excluded) & Length end
    PulseMode6,
}

#[cfg(esp32h2)]
impl EnableMode {
    fn pulse_submode_sel(&self) -> Option<u8> {
        match self {
            EnableMode::PulseMode1 => Some(0),
            EnableMode::PulseMode2 => Some(1),
            EnableMode::PulseMode3 => Some(2),
            EnableMode::PulseMode4 => Some(3),
            EnableMode::PulseMode5 => Some(4),
            EnableMode::PulseMode6 => Some(5),
            _ => None,
        }
    }

    fn level_submode_sel(&self) -> Option<u8> {
        match self {
            EnableMode::HighLevel => Some(0),
            _ => None,
        }
    }

    fn smp_model_sel(&self) -> Option<self::private::SampleMode> {
        match self {
            EnableMode::HighLevel => Some(self::private::SampleMode::ExternalLevel),
            _ => Some(self::private::SampleMode::ExternalPulse),
        }
    }
}

/// PARL_IO RX configuration
#[derive(Debug, Clone, Copy, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct RxConfig {
    /// The target frequency.
    frequency: Rate,

    /// Configures the packing order to pack bits into 1 byte when data
    /// bus width is 4/2/1 bit.
    bit_order: BitPackOrder,

    /// RX threshold of a timeout counter.
    /// When the timeout is triggered, a GDMA ERR EOF signal will be
    /// generated and sent to the GDMA interface to indicate the end of the
    /// receiving
    ///
    /// In units of AHB clock cycles.
    timeout_ticks: Option<u16>,
}

impl Default for RxConfig {
    fn default() -> Self {
        Self {
            frequency: Rate::from_khz(100),
            bit_order: BitPackOrder::Msb,
            timeout_ticks: None,
        }
    }
}

/// PARL_IO TX configuration
#[derive(Debug, Clone, Copy, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct TxConfig {
    /// The target frequency.
    frequency: Rate,

    /// Configures the data value on TX bus when in idle state.
    idle_value: u16,

    /// Configures whether to invert the TX output clock.
    sample_edge: SampleEdge,

    /// Configures the unpacking order to unpack bits from 1 byte when
    /// data bus width is 4/2/1 bit.
    bit_order: BitPackOrder,
}

impl Default for TxConfig {
    fn default() -> Self {
        Self {
            frequency: Rate::from_khz(100),
            idle_value: 0,
            sample_edge: SampleEdge::Normal,
            bit_order: BitPackOrder::Msb,
        }
    }
}

/// Configuration errors.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
    /// Trying to use an impossible clock rate
    UnreachableClockRate,
}

impl core::error::Error for ConfigError {}

impl core::fmt::Display for ConfigError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            ConfigError::UnreachableClockRate => {
                write!(f, "The requested frequency is not supported")
            }
        }
    }
}

/// Used to configure no pin as clock output
impl TxClkPin for NoPin {
    fn configure(&mut self) {
        OutputSignal::PARL_TX_CLK.connect_to(self);
    }
}
impl RxClkPin for NoPin {
    fn configure(&mut self) {
        InputSignal::PARL_RX_CLK.connect_to(self);
    }
}

/// Wraps a GPIO pin which will be used as the clock output signal
pub struct ClkOutPin<'d> {
    pin: interconnect::OutputSignal<'d>,
}
impl<'d> ClkOutPin<'d> {
    /// Create a ClkOutPin
    pub fn new(pin: impl PeripheralOutput<'d>) -> Self {
        Self { pin: pin.into() }
    }
}
impl TxClkPin for ClkOutPin<'_> {
    fn configure(&mut self) {
        self.pin.apply_output_config(&gpio::OutputConfig::default());
        self.pin.set_output_enable(true);

        OutputSignal::PARL_TX_CLK.connect_to(&self.pin);
    }
}

/// Wraps a GPIO pin which will be used as the TX clock input signal
pub struct ClkInPin<'d> {
    pin: interconnect::InputSignal<'d>,
}
impl<'d> ClkInPin<'d> {
    /// Create a new ClkInPin
    pub fn new(pin: impl PeripheralInput<'d>) -> Self {
        Self { pin: pin.into() }
    }
}
impl TxClkPin for ClkInPin<'_> {
    fn configure(&mut self) {
        let pcr = PCR::regs();
        pcr.parl_clk_tx_conf()
            .modify(|_, w| unsafe { w.parl_clk_tx_sel().bits(3).parl_clk_tx_div_num().bits(0) }); // PAD_CLK_TX, no divider

        self.pin.apply_input_config(&gpio::InputConfig::default());
        self.pin.set_input_enable(true);
        InputSignal::PARL_TX_CLK.connect_to(&self.pin);
    }
}

/// Wraps a GPIO pin which will be used as the RX clock input signal
pub struct RxClkInPin<'d> {
    pin: interconnect::InputSignal<'d>,
    sample_edge: SampleEdge,
}
impl<'d> RxClkInPin<'d> {
    /// Create a new RxClkInPin
    pub fn new(pin: impl PeripheralInput<'d>, sample_edge: SampleEdge) -> Self {
        Self {
            pin: pin.into(),
            sample_edge,
        }
    }
}
impl RxClkPin for RxClkInPin<'_> {
    fn configure(&mut self) {
        let pcr = PCR::regs();
        pcr.parl_clk_rx_conf()
            .modify(|_, w| unsafe { w.parl_clk_rx_sel().bits(3).parl_clk_rx_div_num().bits(0) }); // PAD_CLK_TX, no divider

        self.pin.apply_input_config(&gpio::InputConfig::default());
        self.pin.set_input_enable(true);

        InputSignal::PARL_RX_CLK.connect_to(&self.pin);

        Instance::set_rx_clk_edge_sel(self.sample_edge);
    }
}

/// Pin configuration with an additional pin for the valid signal.
pub struct TxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd,
{
    tx_pins: P,
    valid_pin: interconnect::OutputSignal<'d>,
}

impl<'d, P> TxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd,
{
    /// Create a [TxPinConfigWithValidPin]
    pub fn new(tx_pins: P, valid_pin: impl PeripheralOutput<'d>) -> Self {
        Self {
            tx_pins,
            valid_pin: valid_pin.into(),
        }
    }
}

impl<'d, P> TxPins for TxPinConfigWithValidPin<'d, P> where
    P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd
{
}

impl<'d, P> ConfigurePins for TxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + TxPins + ConfigurePins + 'd,
{
    fn configure(&mut self) {
        self.tx_pins.configure();

        self.valid_pin
            .apply_output_config(&gpio::OutputConfig::default());
        self.valid_pin.set_output_enable(true);

        Instance::tx_valid_pin_signal().connect_to(&self.valid_pin);
        Instance::set_tx_hw_valid_en(true);
    }
}

/// Pin configuration where the pin for the valid signal is the MSB pin.
pub struct TxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + TxPins + ConfigurePins,
{
    tx_pins: P,
}

impl<P> TxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + TxPins + ConfigurePins,
{
    /// Create a new [TxPinConfigIncludingValidPin]
    pub fn new(tx_pins: P) -> Self {
        Self { tx_pins }
    }
}

impl<P> TxPins for TxPinConfigIncludingValidPin<P> where
    P: ContainsValidSignalPin + TxPins + ConfigurePins
{
}

impl<P> ConfigurePins for TxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + TxPins + ConfigurePins,
{
    fn configure(&mut self) {
        self.tx_pins.configure();
        Instance::set_tx_hw_valid_en(true);
    }
}

macro_rules! tx_pins {
    ($name:ident, $width:literal, $($pin:ident = $signal:ident),+ ) => {
        paste::paste! {
            #[doc = "Data pin configuration for "]
            #[doc = stringify!($width)]
            #[doc = "bit output mode"]
            pub struct $name<'d> {
                $(
                    [< pin_ $pin:lower >] : interconnect::OutputSignal<'d>,
                )+
            }

            impl<'d> $name<'d>
            {
                /// Create a new TX pin
                #[allow(clippy::too_many_arguments)]
                pub fn new(
                    $(
                        [< pin_ $pin:lower >] : impl PeripheralOutput<'d>,
                    )+
                ) -> Self {
                    Self { $( [< pin_ $pin:lower >]: [< pin_ $pin:lower >].into() ),+ }
                }
            }

            impl ConfigurePins for $name<'_>
            {
                fn configure(&mut self) {
                    $(
                        self.[< pin_ $pin:lower >].apply_output_config(&gpio::OutputConfig::default());
                        self.[< pin_ $pin:lower >].set_output_enable(true);

                        OutputSignal::$signal.connect_to(&mut self.[< pin_ $pin:lower >]);
                    )+

                    private::Instance::set_tx_bit_width( private::WidSel::[< Bits $width >]);
                }
            }

            impl TxPins for $name<'_> {}
        }
    };
}

tx_pins!(TxOneBit, 1, P0 = PARL_TX_DATA0);
tx_pins!(TxTwoBits, 2, P0 = PARL_TX_DATA0, P1 = PARL_TX_DATA1);
tx_pins!(
    TxFourBits,
    4,
    P0 = PARL_TX_DATA0,
    P1 = PARL_TX_DATA1,
    P2 = PARL_TX_DATA2,
    P3 = PARL_TX_DATA3
);
tx_pins!(
    TxEightBits,
    8,
    P0 = PARL_TX_DATA0,
    P1 = PARL_TX_DATA1,
    P2 = PARL_TX_DATA2,
    P3 = PARL_TX_DATA3,
    P4 = PARL_TX_DATA4,
    P5 = PARL_TX_DATA5,
    P6 = PARL_TX_DATA6,
    P7 = PARL_TX_DATA7
);
#[cfg(esp32c6)]
tx_pins!(
    TxSixteenBits,
    16,
    P0 = PARL_TX_DATA0,
    P1 = PARL_TX_DATA1,
    P2 = PARL_TX_DATA2,
    P3 = PARL_TX_DATA3,
    P4 = PARL_TX_DATA4,
    P5 = PARL_TX_DATA5,
    P6 = PARL_TX_DATA6,
    P7 = PARL_TX_DATA7,
    P8 = PARL_TX_DATA8,
    P9 = PARL_TX_DATA9,
    P10 = PARL_TX_DATA10,
    P11 = PARL_TX_DATA11,
    P12 = PARL_TX_DATA12,
    P13 = PARL_TX_DATA13,
    P14 = PARL_TX_DATA14,
    P15 = PARL_TX_DATA15
);

impl NotContainsValidSignalPin for TxOneBit<'_> {}
impl NotContainsValidSignalPin for TxTwoBits<'_> {}
impl NotContainsValidSignalPin for TxFourBits<'_> {}

#[cfg(esp32c6)]
impl NotContainsValidSignalPin for TxEightBits<'_> {}

#[cfg(esp32h2)]
impl ContainsValidSignalPin for TxEightBits<'_> {}

#[cfg(esp32c6)]
impl ContainsValidSignalPin for TxSixteenBits<'_> {}

/// Pin configuration with an additional pin for the valid signal.
pub struct RxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + RxPins + ConfigurePins,
{
    rx_pins: P,
    valid_pin: interconnect::InputSignal<'d>,
    enable_mode: EnableMode,
}

impl<'d, P> RxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + RxPins + ConfigurePins,
{
    /// Create a new [RxPinConfigWithValidPin]
    pub fn new(rx_pins: P, valid_pin: impl PeripheralInput<'d>, enable_mode: EnableMode) -> Self {
        Self {
            rx_pins,
            valid_pin: valid_pin.into(),
            enable_mode,
        }
    }
}

impl<P> RxPins for RxPinConfigWithValidPin<'_, P> where
    P: NotContainsValidSignalPin + RxPins + ConfigurePins
{
}

impl<P> ConfigurePins for RxPinConfigWithValidPin<'_, P>
where
    P: NotContainsValidSignalPin + RxPins + ConfigurePins,
{
    fn configure(&mut self) {
        self.rx_pins.configure();

        self.valid_pin
            .apply_input_config(&gpio::InputConfig::default());
        self.valid_pin.set_input_enable(true);

        Instance::rx_valid_pin_signal().connect_to(&self.valid_pin);
        Instance::set_rx_sw_en(false);
        if let Some(sel) = self.enable_mode.pulse_submode_sel() {
            Instance::set_rx_pulse_submode_sel(sel);
        }
        if let Some(sel) = self.enable_mode.level_submode_sel() {
            Instance::set_rx_level_submode_sel(sel);
        }
        if let Some(sel) = self.enable_mode.smp_model_sel() {
            Instance::set_rx_sample_mode(sel);
        }
    }
}

/// Pin configuration where the pin for the valid signal is the MSB pin.
pub struct RxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + RxPins + ConfigurePins,
{
    rx_pins: P,
    enable_mode: EnableMode,
}

impl<P> RxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + RxPins + ConfigurePins,
{
    /// Create a new [RxPinConfigIncludingValidPin]
    pub fn new(rx_pins: P, enable_mode: EnableMode) -> Self {
        Self {
            rx_pins,
            enable_mode,
        }
    }
}

impl<P> RxPins for RxPinConfigIncludingValidPin<P> where
    P: ContainsValidSignalPin + RxPins + ConfigurePins
{
}

impl<P> ConfigurePins for RxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + RxPins + ConfigurePins,
{
    fn configure(&mut self) {
        self.rx_pins.configure();
        Instance::set_rx_sw_en(false);
        if let Some(sel) = self.enable_mode.pulse_submode_sel() {
            Instance::set_rx_pulse_submode_sel(sel);
        }
        if let Some(sel) = self.enable_mode.level_submode_sel() {
            Instance::set_rx_level_submode_sel(sel);
        }
        if let Some(sel) = self.enable_mode.smp_model_sel() {
            Instance::set_rx_sample_mode(sel);
        }
    }
}

macro_rules! rx_pins {
    ($name:ident, $width:literal, $($pin:ident = $signal:ident),+ ) => {
        paste::paste! {
            #[doc = "Data pin configuration for "]
            #[doc = stringify!($width)]
            #[doc = "bit input mode"]
            pub struct $name<'d> {
                $(
                    [< pin_ $pin:lower >] : interconnect::InputSignal<'d>,
                )+
            }

            impl<'d> $name<'d>
            {
                /// Create a new RX pin
                #[allow(clippy::too_many_arguments)]
                pub fn new(
                    $(
                        [< pin_ $pin:lower >] : impl PeripheralInput<'d>,
                    )+
                ) -> Self {
                    Self { $( [< pin_ $pin:lower >]: [< pin_ $pin:lower >].into() ),+ }
                }
            }

            impl ConfigurePins for $name<'_>
            {
                fn configure(&mut self) {
                    $(
                        self.[< pin_ $pin:lower >].apply_input_config(&gpio::InputConfig::default());
                        self.[< pin_ $pin:lower >].set_input_enable(true);
                        InputSignal::$signal.connect_to(&self.[< pin_ $pin:lower >]);
                    )+

                    private::Instance::set_rx_bit_width( private::WidSel::[< Bits $width >]);
                }
            }

            impl RxPins for $name<'_> {}
        }
    };
}

rx_pins!(RxOneBit, 1, P0 = PARL_RX_DATA0);
rx_pins!(RxTwoBits, 2, P0 = PARL_RX_DATA0, P1 = PARL_RX_DATA1);
rx_pins!(
    RxFourBits,
    4,
    P0 = PARL_RX_DATA0,
    P1 = PARL_RX_DATA1,
    P2 = PARL_RX_DATA2,
    P3 = PARL_RX_DATA3
);
rx_pins!(
    RxEightBits,
    8,
    P0 = PARL_RX_DATA0,
    P1 = PARL_RX_DATA1,
    P2 = PARL_RX_DATA2,
    P3 = PARL_RX_DATA3,
    P4 = PARL_RX_DATA4,
    P5 = PARL_RX_DATA5,
    P6 = PARL_RX_DATA6,
    P7 = PARL_RX_DATA7
);
#[cfg(esp32c6)]
rx_pins!(
    RxSixteenBits,
    16,
    P0 = PARL_RX_DATA0,
    P1 = PARL_RX_DATA1,
    P2 = PARL_RX_DATA2,
    P3 = PARL_RX_DATA3,
    P4 = PARL_RX_DATA4,
    P5 = PARL_RX_DATA5,
    P6 = PARL_RX_DATA6,
    P7 = PARL_RX_DATA7,
    P8 = PARL_RX_DATA8,
    P9 = PARL_RX_DATA9,
    P10 = PARL_RX_DATA10,
    P11 = PARL_RX_DATA11,
    P12 = PARL_RX_DATA12,
    P13 = PARL_RX_DATA13,
    P14 = PARL_RX_DATA14,
    P15 = PARL_RX_DATA15
);

impl NotContainsValidSignalPin for RxOneBit<'_> {}
impl NotContainsValidSignalPin for RxTwoBits<'_> {}
impl NotContainsValidSignalPin for RxFourBits<'_> {}

#[cfg(esp32c6)]
impl NotContainsValidSignalPin for RxEightBits<'_> {}

#[cfg(esp32h2)]
impl ContainsValidSignalPin for RxEightBits<'_> {}

#[cfg(esp32c6)]
impl ContainsValidSignalPin for RxSixteenBits<'_> {}

impl<'d, Dm> TxCreator<'d, Dm>
where
    Dm: DriverMode,
{
    /// Configure TX to use the given pins and settings
    pub fn with_config<P, CP>(
        self,
        mut tx_pins: P,
        mut clk_pin: CP,
        config: TxConfig,
    ) -> Result<ParlIoTx<'d, Dm>, ConfigError>
    where
        P: TxPins + ConfigurePins + 'd,
        CP: TxClkPin + 'd,
    {
        tx_pins.configure();
        clk_pin.configure();

        let mut this = ParlIoTx {
            tx_channel: self.tx_channel,
            _guard: self._guard,
        };
        this.apply_config(&config)?;

        Ok(this)
    }
}

/// Parallel IO TX channel
#[instability::unstable]
pub struct ParlIoTx<'d, Dm>
where
    Dm: DriverMode,
{
    tx_channel: ChannelTx<Dm, PeripheralTxChannel<PARL_IO<'d>>>,
    _guard: GenericPeripheralGuard<{ crate::system::Peripheral::ParlIo as u8 }>,
}

impl<Dm> core::fmt::Debug for ParlIoTx<'_, Dm>
where
    Dm: DriverMode,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("ParlIoTx").finish()
    }
}

impl<'d, Dm> RxCreator<'d, Dm>
where
    Dm: DriverMode,
{
    /// Configure RX to use the given pins and settings
    pub fn with_config<P, CP>(
        self,
        mut rx_pins: P,
        mut clk_pin: CP,
        config: RxConfig,
    ) -> Result<ParlIoRx<'d, Dm>, ConfigError>
    where
        P: RxPins + ConfigurePins + 'd,
        CP: RxClkPin + 'd,
    {
        Instance::set_rx_sw_en(true);
        Instance::set_rx_sample_mode(SampleMode::InternalSoftwareEnable);

        rx_pins.configure();
        clk_pin.configure();

        let mut this = ParlIoRx {
            rx_channel: self.rx_channel,
            _guard: self._guard,
        };
        this.apply_config(&config)?;

        Ok(this)
    }
}

/// Parallel IO RX channel
#[instability::unstable]
pub struct ParlIoRx<'d, Dm>
where
    Dm: DriverMode,
{
    rx_channel: ChannelRx<Dm, PeripheralRxChannel<PARL_IO<'d>>>,
    _guard: GenericPeripheralGuard<{ crate::system::Peripheral::ParlIo as u8 }>,
}

impl<Dm> core::fmt::Debug for ParlIoRx<'_, Dm>
where
    Dm: DriverMode,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("ParlIoTx").finish()
    }
}

fn internal_set_interrupt_handler(handler: InterruptHandler) {
    let peri = unsafe { PARL_IO::steal() };
    #[cfg(esp32c6)]
    {
        peri.disable_peri_interrupt();
        internal_listen(EnumSet::all(), false);
        internal_clear_interrupts(EnumSet::all());
        peri.bind_peri_interrupt(handler.handler());

        peri.enable_peri_interrupt(handler.priority());
    }
    #[cfg(esp32h2)]
    {
        peri.disable_rx_interrupt();
        peri.disable_tx_interrupt();
        internal_listen(EnumSet::all(), false);
        internal_clear_interrupts(EnumSet::all());
        peri.bind_rx_interrupt(handler.handler());
        peri.bind_tx_interrupt(handler.handler());

        peri.enable_rx_interrupt(handler.priority());
        peri.enable_tx_interrupt(handler.priority());
    }
}

fn internal_listen(interrupts: EnumSet<ParlIoInterrupt>, enable: bool) {
    PARL_IO::regs().int_ena().write(|w| {
        for interrupt in interrupts {
            match interrupt {
                ParlIoInterrupt::TxFifoReEmpty => w.tx_fifo_rempty().bit(enable),
                ParlIoInterrupt::RxFifoWOvf => w.rx_fifo_wovf().bit(enable),
                ParlIoInterrupt::TxEof => w.tx_eof().bit(enable),
            };
        }
        w
    });
}

fn internal_interrupts() -> EnumSet<ParlIoInterrupt> {
    let mut res = EnumSet::new();
    let parl_io = PARL_IO::regs();
    let ints = parl_io.int_st().read();
    if ints.tx_fifo_rempty().bit() {
        res.insert(ParlIoInterrupt::TxFifoReEmpty);
    }
    if ints.rx_fifo_wovf().bit() {
        res.insert(ParlIoInterrupt::RxFifoWOvf);
    }
    if ints.tx_eof().bit() {
        res.insert(ParlIoInterrupt::TxEof);
    }

    res
}

fn internal_clear_interrupts(interrupts: EnumSet<ParlIoInterrupt>) {
    let parl_io = PARL_IO::regs();
    parl_io.int_clr().write(|w| {
        for interrupt in interrupts {
            match interrupt {
                ParlIoInterrupt::TxFifoReEmpty => w.tx_fifo_rempty().clear_bit_by_one(),
                ParlIoInterrupt::RxFifoWOvf => w.rx_fifo_wovf().clear_bit_by_one(),
                ParlIoInterrupt::TxEof => w.tx_eof().clear_bit_by_one(),
            };
        }
        w
    });
}

/// Parallel IO
pub struct ParlIo<'d, Dm>
where
    Dm: DriverMode,
{
    /// The transmitter (TX) channel responsible for handling DMA transfers in
    /// the parallel I/O operation.
    pub tx: TxCreator<'d, Dm>,
    /// The receiver (RX) channel responsible for handling DMA transfers in the
    /// parallel I/O operation.
    pub rx: RxCreator<'d, Dm>,
}

impl<'d> ParlIo<'d, Blocking> {
    /// Create a new instance of [ParlIo]
    pub fn new(
        _parl_io: PARL_IO<'d>,
        dma_channel: impl DmaChannelFor<PARL_IO<'d>>,
    ) -> Result<Self, Error> {
        let tx_guard = GenericPeripheralGuard::new();
        let rx_guard = GenericPeripheralGuard::new();
        let dma_channel = Channel::new(dma_channel.degrade());

        Ok(Self {
            tx: TxCreator {
                tx_channel: dma_channel.tx,
                _guard: tx_guard,
            },
            rx: RxCreator {
                rx_channel: dma_channel.rx,
                _guard: rx_guard,
            },
        })
    }

    /// Convert to an async version.
    pub fn into_async(self) -> ParlIo<'d, Async> {
        for core in crate::system::Cpu::other() {
            #[cfg(esp32c6)]
            {
                crate::interrupt::disable(core, Interrupt::PARL_IO);
            }
            #[cfg(esp32h2)]
            {
                crate::interrupt::disable(core, Interrupt::PARL_IO_RX);
                crate::interrupt::disable(core, Interrupt::PARL_IO_TX);
            }
        }

        #[cfg(esp32c6)]
        {
            unsafe {
                crate::interrupt::bind_interrupt(Interrupt::PARL_IO, interrupt_handler.handler());
            }
            unwrap!(crate::interrupt::enable(
                Interrupt::PARL_IO,
                interrupt_handler.priority()
            ));
        }
        #[cfg(esp32h2)]
        {
            unsafe {
                crate::interrupt::bind_interrupt(
                    Interrupt::PARL_IO_TX,
                    interrupt_handler.handler(),
                );
            }
            unwrap!(crate::interrupt::enable(
                Interrupt::PARL_IO_TX,
                interrupt_handler.priority()
            ));
        }

        ParlIo {
            tx: TxCreator {
                tx_channel: self.tx.tx_channel.into_async(),
                _guard: self.tx._guard,
            },
            rx: RxCreator {
                rx_channel: self.rx.rx_channel.into_async(),
                _guard: self.rx._guard,
            },
        }
    }

    /// Sets the interrupt handler, enables it with
    /// [crate::interrupt::Priority::min()]
    ///
    /// Interrupts are not enabled at the peripheral level here.
    #[instability::unstable]
    pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
        internal_set_interrupt_handler(handler);
    }

    /// Listen for the given interrupts
    pub fn listen(&mut self, interrupts: impl Into<EnumSet<ParlIoInterrupt>>) {
        internal_listen(interrupts.into(), true);
    }

    /// Unlisten the given interrupts
    pub fn unlisten(&mut self, interrupts: impl Into<EnumSet<ParlIoInterrupt>>) {
        internal_listen(interrupts.into(), false);
    }

    /// Gets asserted interrupts
    pub fn interrupts(&mut self) -> EnumSet<ParlIoInterrupt> {
        internal_interrupts()
    }

    /// Resets asserted interrupts
    pub fn clear_interrupts(&mut self, interrupts: EnumSet<ParlIoInterrupt>) {
        internal_clear_interrupts(interrupts);
    }
}

impl crate::private::Sealed for ParlIo<'_, Blocking> {}

#[instability::unstable]
impl crate::interrupt::InterruptConfigurable for ParlIo<'_, Blocking> {
    fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
        ParlIo::set_interrupt_handler(self, handler);
    }
}

impl<'d> ParlIo<'d, Async> {
    /// Convert to a blocking version.
    pub fn into_blocking(self) -> ParlIo<'d, Blocking> {
        ParlIo {
            tx: TxCreator {
                tx_channel: self.tx.tx_channel.into_blocking(),
                _guard: self.tx._guard,
            },
            rx: RxCreator {
                rx_channel: self.rx.rx_channel.into_blocking(),
                _guard: self.rx._guard,
            },
        }
    }
}

impl<'d, Dm> ParlIoTx<'d, Dm>
where
    Dm: DriverMode,
{
    /// Perform a DMA write.
    ///
    /// This will return a [ParlIoTxTransfer]
    ///
    /// The maximum amount of data to be sent is 32736 bytes.
    pub fn write<BUF>(
        mut self,
        number_of_bytes: usize,
        mut buffer: BUF,
    ) -> Result<ParlIoTxTransfer<'d, BUF, Dm>, (Error, Self, BUF)>
    where
        BUF: DmaTxBuffer,
    {
        if number_of_bytes > MAX_DMA_SIZE {
            return Err((Error::MaxDmaTransferSizeExceeded, self, buffer));
        }

        PCR::regs()
            .parl_clk_tx_conf()
            .modify(|_, w| w.parl_tx_rst_en().set_bit());

        Instance::clear_tx_interrupts();
        Instance::set_tx_bytes(number_of_bytes as u16);

        let result = unsafe {
            self.tx_channel
                .prepare_transfer(DmaPeripheral::ParlIo, &mut buffer)
                .and_then(|_| self.tx_channel.start_transfer())
        };
        if let Err(err) = result {
            return Err((Error::DmaError(err), self, buffer));
        }

        while !Instance::is_tx_ready() {}

        Instance::set_tx_start(true);

        PCR::regs()
            .parl_clk_tx_conf()
            .modify(|_, w| w.parl_tx_rst_en().clear_bit());

        Ok(ParlIoTxTransfer {
            parl_io: ManuallyDrop::new(self),
            buf_view: ManuallyDrop::new(buffer.into_view()),
        })
    }

    /// Change the bus configuration.
    pub fn apply_config(&mut self, config: &TxConfig) -> Result<(), ConfigError> {
        if config.frequency.as_hz() > 40_000_000 {
            return Err(ConfigError::UnreachableClockRate);
        }

        let divider = crate::soc::constants::PARL_IO_SCLK / config.frequency.as_hz();
        if divider > 0xFFFF {
            return Err(ConfigError::UnreachableClockRate);
        }
        let divider = divider as u16;

        PCR::regs().parl_clk_tx_conf().modify(|_, w| unsafe {
            w.parl_clk_tx_en().set_bit();
            w.parl_clk_tx_sel().bits(1); // PLL
            w.parl_clk_tx_div_num().bits(divider)
        });

        Instance::set_tx_idle_value(config.idle_value);
        Instance::set_tx_sample_edge(config.sample_edge);
        Instance::set_tx_bit_order(config.bit_order);

        Ok(())
    }
}

/// Represents an ongoing (or potentially finished) transfer using the PARL_IO
/// TX.
pub struct ParlIoTxTransfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> {
    parl_io: ManuallyDrop<ParlIoTx<'d, Dm>>,
    buf_view: ManuallyDrop<BUF::View>,
}

impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> ParlIoTxTransfer<'d, BUF, Dm> {
    /// Returns true when [Self::wait] will not block.
    pub fn is_done(&self) -> bool {
        Instance::is_tx_eof()
    }

    /// Waits for the transfer to finish and returns the peripheral and buffer.
    pub fn wait(mut self) -> (Result<(), DmaError>, ParlIoTx<'d, Dm>, BUF::Final) {
        while !self.is_done() {}

        Instance::set_tx_start(false);
        Instance::clear_is_tx_done();

        // Stop the DMA as it doesn't know that the parl io has stopped.
        self.parl_io.tx_channel.stop_transfer();

        let (parl_io, view) = self.release();

        let result = if parl_io.tx_channel.has_error() {
            Err(DmaError::DescriptorError)
        } else {
            Ok(())
        };

        (result, parl_io, BUF::from_view(view))
    }

    fn release(mut self) -> (ParlIoTx<'d, Dm>, BUF::View) {
        let (parl_io, view) = unsafe {
            (
                ManuallyDrop::take(&mut self.parl_io),
                ManuallyDrop::take(&mut self.buf_view),
            )
        };
        core::mem::forget(self);
        (parl_io, view)
    }
}

impl<BUF: DmaTxBuffer, Dm: DriverMode> Deref for ParlIoTxTransfer<'_, BUF, Dm> {
    type Target = BUF::View;

    fn deref(&self) -> &Self::Target {
        &self.buf_view
    }
}

impl<BUF: DmaTxBuffer, Dm: DriverMode> DerefMut for ParlIoTxTransfer<'_, BUF, Dm> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.buf_view
    }
}

impl<BUF: DmaTxBuffer, Dm: DriverMode> Drop for ParlIoTxTransfer<'_, BUF, Dm> {
    fn drop(&mut self) {
        // There's no documented way to cancel the PARL IO transfer, so we'll just stop
        // the DMA to stop the memory access.
        self.parl_io.tx_channel.stop_transfer();

        // SAFETY: This is Drop, we know that self.parl_io and self.buf_view
        // won't be touched again.
        let view = unsafe {
            ManuallyDrop::drop(&mut self.parl_io);
            ManuallyDrop::take(&mut self.buf_view)
        };
        let _ = BUF::from_view(view);
    }
}

impl<'d, Dm> ParlIoRx<'d, Dm>
where
    Dm: DriverMode,
{
    /// Perform a DMA read.
    ///
    /// This will return a [ParlIoRxTransfer]
    ///
    /// When the number of bytes is specified, the maximum amount of data is
    /// 32736 bytes and the transfer ends when the number of specified bytes
    /// is received.
    ///
    /// When the number of bytes is unspecified, there's no limit the amount of
    /// data transferred and the transfer ends when the enable signal
    /// signals the end or the DMA buffer runs out of space.
    pub fn read<BUF>(
        mut self,
        number_of_bytes: Option<usize>,
        mut buffer: BUF,
    ) -> Result<ParlIoRxTransfer<'d, BUF, Dm>, (Error, Self, BUF)>
    where
        BUF: DmaRxBuffer,
    {
        PCR::regs()
            .parl_clk_rx_conf()
            .modify(|_, w| w.parl_rx_rst_en().set_bit());
        PCR::regs()
            .parl_clk_rx_conf()
            .modify(|_, w| w.parl_rx_rst_en().clear_bit());

        Instance::clear_rx_interrupts();
        if let Some(number_of_bytes) = number_of_bytes {
            if number_of_bytes > MAX_DMA_SIZE {
                return Err((Error::MaxDmaTransferSizeExceeded, self, buffer));
            }
            Instance::set_rx_bytes(number_of_bytes as u16);
            Instance::set_eof_gen_sel(EofMode::ByteLen);
        } else {
            Instance::set_eof_gen_sel(EofMode::EnableSignal);
        }

        let result = unsafe {
            self.rx_channel
                .prepare_transfer(DmaPeripheral::ParlIo, &mut buffer)
                .and_then(|_| self.rx_channel.start_transfer())
        };
        if let Err(err) = result {
            return Err((Error::DmaError(err), self, buffer));
        }

        Instance::set_rx_reg_update();

        Instance::set_rx_start(true);

        Ok(ParlIoRxTransfer {
            parl_io: ManuallyDrop::new(self),
            buf_view: ManuallyDrop::new(buffer.into_view()),
            dma_result: None,
        })
    }

    /// Change the bus configuration.
    pub fn apply_config(&mut self, config: &RxConfig) -> Result<(), ConfigError> {
        if config.frequency.as_hz() > 40_000_000 {
            return Err(ConfigError::UnreachableClockRate);
        }

        let divider = crate::soc::constants::PARL_IO_SCLK / config.frequency.as_hz();
        if divider > 0xffff {
            return Err(ConfigError::UnreachableClockRate);
        }
        let divider = divider as u16;

        PCR::regs().parl_clk_rx_conf().modify(|_, w| unsafe {
            w.parl_clk_rx_en().set_bit();
            w.parl_clk_rx_sel().bits(1); // PLL
            w.parl_clk_rx_div_num().bits(divider)
        });

        Instance::set_rx_bit_order(config.bit_order);
        Instance::set_rx_timeout_ticks(config.timeout_ticks);

        Ok(())
    }
}

/// Represents an ongoing (or potentially finished) transfer using the PARL_IO
/// TX.
pub struct ParlIoRxTransfer<'d, BUF: DmaRxBuffer, Dm: DriverMode> {
    parl_io: ManuallyDrop<ParlIoRx<'d, Dm>>,
    buf_view: ManuallyDrop<BUF::View>,
    // Needed to use DmaRxFuture, which clear the bits we check in is_done()
    dma_result: Option<Result<(), DmaError>>,
}

impl<'d, BUF: DmaRxBuffer, Dm: DriverMode> ParlIoRxTransfer<'d, BUF, Dm> {
    /// Returns true when [Self::wait] will not block.
    pub fn is_done(&self) -> bool {
        if self.dma_result.is_some() {
            return true;
        }
        let ch = &self.parl_io.rx_channel;
        ch.is_done() || ch.has_eof_error() || ch.has_dscr_empty_error()
    }

    /// Waits for the transfer to finish and returns the peripheral and buffer.
    pub fn wait(mut self) -> (Result<(), DmaError>, ParlIoRx<'d, Dm>, BUF::Final) {
        while !self.is_done() {}

        Instance::set_rx_start(false);

        // Stop the DMA as it doesn't know that the parl io has stopped.
        self.parl_io.rx_channel.stop_transfer();

        let dma_result = self.dma_result.take();
        let (parl_io, view) = self.release();

        let result = if parl_io.rx_channel.has_error() {
            Err(DmaError::DescriptorError)
        } else {
            dma_result.unwrap_or(Ok(()))
        };

        (result, parl_io, BUF::from_view(view))
    }

    fn release(mut self) -> (ParlIoRx<'d, Dm>, BUF::View) {
        let (parl_io, view) = unsafe {
            (
                ManuallyDrop::take(&mut self.parl_io),
                ManuallyDrop::take(&mut self.buf_view),
            )
        };
        core::mem::forget(self);
        (parl_io, view)
    }
}

impl<BUF: DmaRxBuffer, Dm: DriverMode> Deref for ParlIoRxTransfer<'_, BUF, Dm> {
    type Target = BUF::View;

    fn deref(&self) -> &Self::Target {
        &self.buf_view
    }
}

impl<BUF: DmaRxBuffer, Dm: DriverMode> DerefMut for ParlIoRxTransfer<'_, BUF, Dm> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.buf_view
    }
}

impl<BUF: DmaRxBuffer, Dm: DriverMode> Drop for ParlIoRxTransfer<'_, BUF, Dm> {
    fn drop(&mut self) {
        // There's no documented way to cancel the PARL IO transfer, so we'll just stop
        // the DMA to stop the memory access.
        self.parl_io.rx_channel.stop_transfer();

        // SAFETY: This is Drop, we know that self.parl_io and self.buf_view
        // won't be touched again.
        let view = unsafe {
            ManuallyDrop::drop(&mut self.parl_io);
            ManuallyDrop::take(&mut self.buf_view)
        };
        let _ = BUF::from_view(view);
    }
}

/// Creates a TX channel
pub struct TxCreator<'d, Dm>
where
    Dm: DriverMode,
{
    tx_channel: ChannelTx<Dm, PeripheralTxChannel<PARL_IO<'d>>>,
    _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
}

/// Creates a RX channel
pub struct RxCreator<'d, Dm>
where
    Dm: DriverMode,
{
    rx_channel: ChannelRx<Dm, PeripheralRxChannel<PARL_IO<'d>>>,
    _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
}

// These three traits need to be public to allow the user to create functions
// that can take either 8 or 16 bit pins and call parl_io.tx.with_config() or
// parl_io.rx.with_config().
#[doc(hidden)]
pub trait TxPins {}
#[doc(hidden)]
pub trait RxPins {}
#[doc(hidden)]
pub trait ConfigurePins {
    fn configure(&mut self);
}

#[doc(hidden)]
pub mod asynch {
    use core::task::Poll;

    use procmacros::handler;

    use super::{ParlIoRxTransfer, ParlIoTxTransfer, private::Instance};
    use crate::{
        asynch::AtomicWaker,
        dma::{DmaRxBuffer, DmaTxBuffer, asynch::DmaRxFuture},
    };

    static TX_WAKER: AtomicWaker = AtomicWaker::new();

    #[must_use = "futures do nothing unless you `.await` or poll them"]
    struct TxDoneFuture {}

    impl TxDoneFuture {
        pub fn new() -> Self {
            Self {}
        }
    }

    impl core::future::Future for TxDoneFuture {
        type Output = ();

        fn poll(
            self: core::pin::Pin<&mut Self>,
            cx: &mut core::task::Context<'_>,
        ) -> Poll<Self::Output> {
            if Instance::is_tx_done_set() {
                Poll::Ready(())
            } else {
                TX_WAKER.register(cx.waker());
                Instance::listen_tx_done();
                Poll::Pending
            }
        }
    }

    impl Drop for TxDoneFuture {
        fn drop(&mut self) {
            Instance::unlisten_tx_done();
        }
    }

    #[handler]
    pub(super) fn interrupt_handler() {
        if Instance::is_tx_done_set() {
            Instance::unlisten_tx_done();
            TX_WAKER.wake()
        }
    }

    impl<BUF: DmaTxBuffer> ParlIoTxTransfer<'_, BUF, crate::Async> {
        /// Waits for [Self::is_done] to return true.
        pub async fn wait_for_done(&mut self) {
            let future = TxDoneFuture::new();
            future.await;
        }
    }

    impl<BUF: DmaRxBuffer> ParlIoRxTransfer<'_, BUF, crate::Async> {
        /// Waits for [Self::is_done] to return true.
        pub async fn wait_for_done(&mut self) {
            if self.dma_result.is_some() {
                return;
            }
            let future = DmaRxFuture::new(&mut self.parl_io.rx_channel);
            self.dma_result = Some(future.await);
        }
    }
}

mod private {
    use super::{BitPackOrder, SampleEdge};
    use crate::{
        gpio::{InputSignal, OutputSignal},
        peripherals::PARL_IO,
    };

    pub trait NotContainsValidSignalPin {}

    pub trait ContainsValidSignalPin {}

    pub trait TxClkPin {
        fn configure(&mut self);
    }

    pub trait RxClkPin {
        fn configure(&mut self);
    }

    #[cfg(esp32c6)]
    pub(super) enum WidSel {
        Bits16 = 0,
        Bits8  = 1,
        Bits4  = 2,
        Bits2  = 3,
        Bits1  = 4,
    }

    #[cfg(esp32h2)]
    pub(super) enum WidSel {
        Bits8 = 3,
        Bits4 = 2,
        Bits2 = 1,
        Bits1 = 0,
    }

    pub(super) enum SampleMode {
        ExternalLevel          = 0,
        ExternalPulse          = 1,
        InternalSoftwareEnable = 2,
    }

    /// Generation of GDMA SUC EOF
    pub(super) enum EofMode {
        /// Generate GDMA SUC EOF by data byte length
        ByteLen,
        /// Generate GDMA SUC EOF by the external enable signal
        EnableSignal,
    }

    pub(super) struct Instance;

    #[cfg(esp32c6)]
    impl Instance {
        pub fn set_tx_bit_width(width: WidSel) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_cfg0()
                .modify(|_, w| unsafe { w.tx_bus_wid_sel().bits(width as u8) });
        }

        pub fn set_tx_idle_value(value: u16) {
            let reg_block = PARL_IO::regs();
            reg_block
                .tx_cfg1()
                .modify(|_, w| unsafe { w.tx_idle_value().bits(value) });
        }

        pub fn set_tx_sample_edge(value: SampleEdge) {
            let reg_block = PARL_IO::regs();
            reg_block
                .tx_cfg0()
                .modify(|_, w| w.tx_smp_edge_sel().bit(value as u8 == 1));
        }

        pub fn set_tx_bit_order(value: BitPackOrder) {
            let reg_block = PARL_IO::regs();
            reg_block
                .tx_cfg0()
                .modify(|_, w| w.tx_bit_unpack_order().bit(value as u8 == 1));
        }

        pub fn clear_tx_interrupts() {
            let reg_block = PARL_IO::regs();

            reg_block.int_clr().write(|w| {
                w.tx_fifo_rempty()
                    .clear_bit_by_one()
                    .tx_eof()
                    .clear_bit_by_one()
            });
        }

        pub fn set_tx_bytes(len: u16) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_cfg0()
                .modify(|_, w| unsafe { w.tx_bytelen().bits(len) });
        }

        pub fn is_tx_ready() -> bool {
            let reg_block = PARL_IO::regs();

            reg_block.st().read().tx_ready().bit_is_set()
        }

        pub fn set_tx_start(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block.tx_cfg0().modify(|_, w| w.tx_start().bit(value));
        }

        pub fn is_tx_eof() -> bool {
            let reg_block = PARL_IO::regs();

            reg_block.int_raw().read().tx_eof().bit_is_set()
        }

        pub fn tx_valid_pin_signal() -> OutputSignal {
            OutputSignal::PARL_TX_DATA15
        }

        pub fn set_tx_hw_valid_en(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_cfg0()
                .modify(|_, w| w.tx_hw_valid_en().bit(value));
        }

        pub fn set_rx_bit_width(width: WidSel) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg0()
                .modify(|_, w| unsafe { w.rx_bus_wid_sel().bits(width as u8) });
        }

        pub fn rx_valid_pin_signal() -> InputSignal {
            InputSignal::PARL_RX_DATA15
        }

        pub fn set_rx_sw_en(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block.rx_cfg0().modify(|_, w| w.rx_sw_en().bit(value));
        }

        pub fn clear_rx_interrupts() {
            let reg_block = PARL_IO::regs();

            reg_block
                .int_clr()
                .write(|w| w.rx_fifo_wovf().clear_bit_by_one());
        }

        pub fn set_rx_bytes(len: u16) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg0()
                .modify(|_, w| unsafe { w.rx_data_bytelen().bits(len) });
        }

        pub fn set_rx_sample_mode(sample_mode: SampleMode) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg0()
                .modify(|_, w| unsafe { w.rx_smp_mode_sel().bits(sample_mode as u8) });
        }

        pub fn set_eof_gen_sel(mode: EofMode) {
            let reg_block = PARL_IO::regs();

            reg_block.rx_cfg0().modify(|_, w| {
                w.rx_eof_gen_sel()
                    .bit(matches!(mode, EofMode::EnableSignal))
            });
        }

        pub fn set_rx_pulse_submode_sel(sel: u8) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg0()
                .modify(|_, w| unsafe { w.rx_pulse_submode_sel().bits(sel) });
        }

        pub fn set_rx_level_submode_sel(sel: u8) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg0()
                .modify(|_, w| w.rx_level_submode_sel().bit(sel == 1));
        }

        pub fn set_rx_clk_edge_sel(edge: SampleEdge) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg0()
                .modify(|_, w| w.rx_clk_edge_sel().bit(edge as u8 == 1));
        }

        pub fn set_rx_start(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block.rx_cfg0().modify(|_, w| w.rx_start().bit(value));
        }

        pub fn set_rx_reg_update() {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_cfg1()
                .modify(|_, w| w.rx_reg_update().bit(true));
        }

        pub fn set_rx_bit_order(value: BitPackOrder) {
            let reg_block = PARL_IO::regs();
            reg_block
                .rx_cfg0()
                .modify(|_, w| w.rx_bit_pack_order().bit(value as u8 == 1));
        }

        pub fn set_rx_timeout_ticks(value: Option<u16>) {
            let reg_block = PARL_IO::regs();
            reg_block.rx_cfg1().modify(|_, w| unsafe {
                w.rx_timeout_en()
                    .bit(value.is_some())
                    .rx_timeout_threshold()
                    .bits(value.unwrap_or(0xfff))
            });
        }

        pub fn listen_tx_done() {
            let reg_block = PARL_IO::regs();

            reg_block.int_ena().modify(|_, w| w.tx_eof().set_bit());
        }

        pub fn unlisten_tx_done() {
            let reg_block = PARL_IO::regs();

            reg_block.int_ena().modify(|_, w| w.tx_eof().clear_bit());
        }

        pub fn is_tx_done_set() -> bool {
            let reg_block = PARL_IO::regs();

            reg_block.int_raw().read().tx_eof().bit()
        }

        pub fn clear_is_tx_done() {
            let reg_block = PARL_IO::regs();

            reg_block.int_clr().write(|w| w.tx_eof().clear_bit_by_one());
        }
    }

    #[cfg(esp32h2)]
    impl Instance {
        pub fn set_tx_bit_width(width: WidSel) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_data_cfg()
                .modify(|_, w| unsafe { w.tx_bus_wid_sel().bits(width as u8) });
        }

        pub fn set_tx_idle_value(value: u16) {
            let reg_block = PARL_IO::regs();
            reg_block
                .tx_genrl_cfg()
                .modify(|_, w| unsafe { w.tx_idle_value().bits(value) });
        }

        pub fn set_tx_sample_edge(value: SampleEdge) {
            let reg_block = PARL_IO::regs();
            reg_block.tx_clk_cfg().modify(|_, w| {
                w.tx_clk_i_inv()
                    .bit(value == SampleEdge::Invert)
                    .tx_clk_o_inv()
                    .bit(value == SampleEdge::Invert)
            });
        }

        pub fn set_tx_bit_order(value: BitPackOrder) {
            let reg_block = PARL_IO::regs();
            reg_block
                .tx_data_cfg()
                .modify(|_, w| w.tx_data_order_inv().bit(value as u8 == 1));
        }

        pub fn clear_tx_interrupts() {
            let reg_block = PARL_IO::regs();

            reg_block.int_clr().write(|w| {
                w.tx_fifo_rempty()
                    .clear_bit_by_one()
                    .tx_eof()
                    .clear_bit_by_one()
            });
        }

        pub fn set_tx_bytes(len: u16) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_data_cfg()
                .modify(|_, w| unsafe { w.tx_bitlen().bits((len as u32) * 8) });
        }

        pub fn is_tx_ready() -> bool {
            let reg_block = PARL_IO::regs();

            reg_block.st().read().tx_ready().bit_is_set()
        }

        pub fn set_tx_start(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_start_cfg()
                .modify(|_, w| w.tx_start().bit(value));
        }

        pub fn is_tx_eof() -> bool {
            let reg_block = PARL_IO::regs();

            reg_block.int_raw().read().tx_eof().bit_is_set()
        }

        pub fn tx_valid_pin_signal() -> OutputSignal {
            OutputSignal::PARL_TX_DATA7
        }

        pub fn set_tx_hw_valid_en(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block
                .tx_genrl_cfg()
                .modify(|_, w| w.tx_valid_output_en().bit(value));
        }

        pub fn set_rx_bit_width(width: WidSel) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_data_cfg()
                .modify(|_, w| unsafe { w.rx_bus_wid_sel().bits(width as u8) });
        }

        pub fn rx_valid_pin_signal() -> InputSignal {
            InputSignal::PARL_RX_DATA7
        }

        pub fn set_rx_sw_en(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_mode_cfg()
                .modify(|_, w| w.rx_sw_en().bit(value));
        }

        pub fn clear_rx_interrupts() {
            let reg_block = PARL_IO::regs();

            reg_block
                .int_clr()
                .write(|w| w.rx_fifo_wovf().clear_bit_by_one());
        }

        pub fn set_rx_bytes(len: u16) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_data_cfg()
                .modify(|_, w| unsafe { w.rx_bitlen().bits((len as u32) * 8) });
        }

        pub fn set_rx_sample_mode(sample_mode: SampleMode) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_mode_cfg()
                .modify(|_, w| unsafe { w.rx_smp_mode_sel().bits(sample_mode as u8) });
        }

        pub fn set_eof_gen_sel(mode: EofMode) {
            let reg_block = PARL_IO::regs();

            reg_block.rx_genrl_cfg().modify(|_, w| {
                w.rx_eof_gen_sel()
                    .bit(matches!(mode, EofMode::EnableSignal))
            });
        }

        pub fn set_rx_pulse_submode_sel(sel: u8) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_mode_cfg()
                .modify(|_, w| unsafe { w.rx_pulse_submode_sel().bits(sel) });
        }

        pub fn set_rx_level_submode_sel(_sel: u8) {
            // unsupported, always high
        }

        pub fn set_rx_clk_edge_sel(value: SampleEdge) {
            let reg_block = PARL_IO::regs();

            reg_block.rx_clk_cfg().modify(|_, w| {
                w.rx_clk_i_inv()
                    .bit(value == SampleEdge::Invert)
                    .rx_clk_o_inv()
                    .bit(value == SampleEdge::Invert)
            });
        }

        pub fn set_rx_start(value: bool) {
            let reg_block = PARL_IO::regs();

            reg_block
                .rx_start_cfg()
                .modify(|_, w| w.rx_start().bit(value));
        }

        pub fn set_rx_reg_update() {
            let reg_block = PARL_IO::regs();

            reg_block
                .reg_update()
                .write(|w| w.rx_reg_update().bit(true));
        }

        pub fn set_rx_bit_order(value: BitPackOrder) {
            let reg_block = PARL_IO::regs();
            reg_block
                .rx_data_cfg()
                .modify(|_, w| w.rx_data_order_inv().bit(value as u8 == 1));
        }

        pub fn set_rx_timeout_ticks(value: Option<u16>) {
            let reg_block = PARL_IO::regs();
            reg_block.rx_genrl_cfg().modify(|_, w| unsafe {
                w.rx_timeout_en()
                    .bit(value.is_some())
                    .rx_timeout_thres()
                    .bits(value.unwrap_or(0xfff))
            });
        }

        pub fn listen_tx_done() {
            let reg_block = PARL_IO::regs();

            reg_block.int_ena().modify(|_, w| w.tx_eof().set_bit());
        }

        pub fn unlisten_tx_done() {
            let reg_block = PARL_IO::regs();

            reg_block.int_ena().modify(|_, w| w.tx_eof().clear_bit());
        }

        pub fn is_tx_done_set() -> bool {
            let reg_block = PARL_IO::regs();

            reg_block.int_raw().read().tx_eof().bit()
        }

        pub fn clear_is_tx_done() {
            let reg_block = PARL_IO::regs();

            reg_block.int_clr().write(|w| w.tx_eof().clear_bit_by_one());
        }
    }
}
