//! # 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
#![doc = crate::before_snippet!()]
//! # use esp_hal::delay::Delay;
//! # use esp_hal::dma_buffers;
//! # use esp_hal::gpio::NoPin;
//! # use esp_hal::parl_io::{BitPackOrder, ParlIoRxOnly, RxFourBits};
//!
//! // Initialize DMA buffer and descriptors for data reception
//! let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0);
//! 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 = ParlIoRxOnly::new(
//!     peripherals.PARL_IO,
//!     dma_channel,
//!     rx_descriptors,
//!     1.MHz(),
//! )
//! .unwrap();
//!
//! let mut parl_io_rx = parl_io
//!     .rx
//!     .with_config(
//!         &mut rx_pins,
//!         &mut rx_clk_pin,
//!         BitPackOrder::Msb,
//!         Some(0xfff),
//!     )
//!     .unwrap();
//!
//! // Initialize the buffer and delay
//! let mut buffer = rx_buffer;
//! buffer.fill(0u8);
//! let delay = Delay::new();
//!
//! loop {
//!     // Read data via DMA and print received values
//!     let transfer = parl_io_rx.read_dma(&mut buffer).unwrap();
//!     transfer.wait().unwrap();
//!
//!     delay.delay_millis(500);
//! }
//! # }
//! ```
//! 
//! ### Initialization for TX
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! # use esp_hal::delay::Delay;
//! # use esp_hal::dma_buffers;
//! # use esp_hal::parl_io::{BitPackOrder, ParlIoTxOnly, TxFourBits, SampleEdge, ClkOutPin, TxPinConfigWithValidPin};
//!
//! // Initialize DMA buffer and descriptors for data reception
//! let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000);
//! 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 = ParlIoTxOnly::new(
//!     peripherals.PARL_IO,
//!     dma_channel,
//!     tx_descriptors,
//!     1.MHz(),
//! )
//! .unwrap();
//!
//! let mut clock_pin = ClkOutPin::new(peripherals.GPIO6);
//! let mut parl_io_tx = parl_io
//!     .tx
//!     .with_config(
//!         &mut pin_conf,
//!         &mut clock_pin,
//!         0,
//!         SampleEdge::Normal,
//!         BitPackOrder::Msb,
//!     )
//! .unwrap();
//!
//! let buffer = tx_buffer;
//! for i in 0..buffer.len() {
//!      buffer[i] = (i % 255) as u8;
//! }
//!
//! let delay = Delay::new();
//! loop {
//!     let transfer = parl_io_tx.write_dma(&buffer).unwrap();
//!     transfer.wait().unwrap();
//!     delay.delay_millis(500);
//! }
//! # }
//! ```

use enumset::{EnumSet, EnumSetType};
use fugit::HertzU32;
use peripheral::PeripheralRef;
use private::*;

use crate::{
    dma::{
        dma_private::{DmaSupport, DmaSupportRx, DmaSupportTx},
        Channel,
        ChannelRx,
        ChannelTx,
        DescriptorChain,
        DmaChannelFor,
        DmaDescriptor,
        DmaError,
        DmaPeripheral,
        DmaTransferRx,
        DmaTransferTx,
        PeripheralRxChannel,
        PeripheralTxChannel,
        ReadBuffer,
        Rx,
        RxChannelFor,
        Tx,
        TxChannelFor,
        WriteBuffer,
    },
    gpio::{
        interconnect::{InputConnection, OutputConnection, PeripheralInput, PeripheralOutput},
        NoPin,
    },
    interrupt::{InterruptConfigurable, InterruptHandler},
    peripheral::{self, Peripheral},
    peripherals::{Interrupt, PARL_IO},
    system::{self, GenericPeripheralGuard},
    Async,
    Blocking,
    DriverMode,
};

#[allow(unused)]
const MAX_DMA_SIZE: usize = 32736;

/// 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,
    /// Trying to use an impossible clock rate
    UnreachableClockRate,
}

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),
        }
    }
}

/// Generation of GDMA SUC EOF
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum EofMode {
    /// Generate GDMA SUC EOF by data byte length
    ByteLen,
    /// Generate GDMA SUC EOF by the external enable signal
    EnableSignal,
}

/// Used to configure no pin as clock output
impl TxClkPin for NoPin {
    fn configure(&mut self) {
        crate::gpio::OutputSignal::PARL_TX_CLK.connect_to(self);
    }
}
impl RxClkPin for NoPin {
    fn configure(&mut self) {
        crate::gpio::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: PeripheralRef<'d, OutputConnection>,
}
impl<'d> ClkOutPin<'d> {
    /// Create a ClkOutPin
    pub fn new(pin: impl Peripheral<P = impl PeripheralOutput> + 'd) -> Self {
        crate::into_mapped_ref!(pin);
        Self { pin }
    }
}
impl TxClkPin for ClkOutPin<'_> {
    fn configure(&mut self) {
        self.pin.set_to_push_pull_output(crate::private::Internal);
        crate::gpio::OutputSignal::PARL_TX_CLK.connect_to(&mut self.pin);
    }
}

/// Wraps a GPIO pin which will be used as the TX clock input signal
pub struct ClkInPin<'d> {
    pin: PeripheralRef<'d, InputConnection>,
}
impl<'d> ClkInPin<'d> {
    /// Create a new ClkInPin
    pub fn new(pin: impl Peripheral<P = impl PeripheralInput> + 'd) -> Self {
        crate::into_mapped_ref!(pin);
        Self { pin }
    }
}
impl TxClkPin for ClkInPin<'_> {
    fn configure(&mut self) {
        let pcr = unsafe { &*crate::peripherals::PCR::PTR };
        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
            .init_input(crate::gpio::Pull::None, crate::private::Internal);
        crate::gpio::InputSignal::PARL_TX_CLK.connect_to(&mut self.pin);
    }
}

/// Wraps a GPIO pin which will be used as the RX clock input signal
pub struct RxClkInPin<'d> {
    pin: PeripheralRef<'d, InputConnection>,
    sample_edge: SampleEdge,
}
impl<'d> RxClkInPin<'d> {
    /// Create a new RxClkInPin
    pub fn new(
        pin: impl Peripheral<P = impl PeripheralInput> + 'd,
        sample_edge: SampleEdge,
    ) -> Self {
        crate::into_mapped_ref!(pin);
        Self { pin, sample_edge }
    }
}
impl RxClkPin for RxClkInPin<'_> {
    fn configure(&mut self) {
        let pcr = unsafe { &*crate::peripherals::PCR::PTR };
        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
            .init_input(crate::gpio::Pull::None, crate::private::Internal);
        crate::gpio::InputSignal::PARL_RX_CLK.connect_to(&mut 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,
{
    tx_pins: P,
    valid_pin: PeripheralRef<'d, OutputConnection>,
}

impl<'d, P> TxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + TxPins + ConfigurePins,
{
    /// Create a [TxPinConfigWithValidPin]
    pub fn new(tx_pins: P, valid_pin: impl Peripheral<P = impl PeripheralOutput> + 'd) -> Self {
        crate::into_mapped_ref!(valid_pin);
        Self { tx_pins, valid_pin }
    }
}

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

impl<P> ConfigurePins for TxPinConfigWithValidPin<'_, P>
where
    P: NotContainsValidSignalPin + TxPins + ConfigurePins,
{
    fn configure(&mut self) -> Result<(), Error> {
        self.tx_pins.configure()?;
        self.valid_pin
            .set_to_push_pull_output(crate::private::Internal);
        Instance::tx_valid_pin_signal().connect_to(&mut self.valid_pin);
        Instance::set_tx_hw_valid_en(true);
        Ok(())
    }
}

/// 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) -> Result<(), Error> {
        self.tx_pins.configure()?;
        Instance::set_tx_hw_valid_en(true);
        Ok(())
    }
}

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 >] : PeripheralRef<'d, OutputConnection>,
                )+
            }

            impl<'d> $name<'d>
            {
                /// Create a new TX pin
                #[allow(clippy::too_many_arguments)]
                pub fn new(
                    $(
                        [< pin_ $pin:lower >] : impl Peripheral<P = impl PeripheralOutput > + 'd,
                    )+
                ) -> Self {
                    crate::into_mapped_ref!($( [< pin_ $pin:lower >] ),+);
                    Self { $( [< pin_ $pin:lower >] ),+ }
                }
            }

            impl ConfigurePins for $name<'_>
            {
                fn configure(&mut self) -> Result<(), Error>{
                    $(
                        self.[< pin_ $pin:lower >].set_to_push_pull_output(crate::private::Internal);
                        crate::gpio::OutputSignal::$signal.connect_to(&mut self.[< pin_ $pin:lower >]);
                    )+

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

            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 FullDuplex for TxOneBit<'_> {}
impl FullDuplex for TxTwoBits<'_> {}
impl FullDuplex for TxFourBits<'_> {}
impl FullDuplex for TxEightBits<'_> {}

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: PeripheralRef<'d, InputConnection>,
    enable_mode: EnableMode,
    eof_mode: EofMode,
}

impl<'d, P> RxPinConfigWithValidPin<'d, P>
where
    P: NotContainsValidSignalPin + RxPins + ConfigurePins,
{
    /// Create a new [RxPinConfigWithValidPin]
    pub fn new(
        rx_pins: P,
        valid_pin: impl Peripheral<P = impl PeripheralInput> + 'd,
        enable_mode: EnableMode,
        eof_mode: EofMode,
    ) -> Self {
        crate::into_mapped_ref!(valid_pin);
        Self {
            rx_pins,
            valid_pin,
            enable_mode,
            eof_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) -> Result<(), Error> {
        self.rx_pins.configure()?;
        self.valid_pin
            .init_input(crate::gpio::Pull::None, crate::private::Internal);
        Instance::rx_valid_pin_signal().connect_to(&mut 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);
        }
        Instance::set_eof_gen_sel(self.eof_mode);

        Ok(())
    }
}

/// 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,
    eof_mode: EofMode,
}

impl<P> RxPinConfigIncludingValidPin<P>
where
    P: ContainsValidSignalPin + RxPins + ConfigurePins,
{
    /// Create a new [RxPinConfigIncludingValidPin]
    pub fn new(rx_pins: P, enable_mode: EnableMode, eof_mode: EofMode) -> Self {
        Self {
            rx_pins,
            enable_mode,
            eof_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) -> Result<(), Error> {
        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);
        }
        Instance::set_eof_gen_sel(self.eof_mode);

        Ok(())
    }
}

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 >] : PeripheralRef<'d, InputConnection>,
                )+
            }

            impl<'d> $name<'d>
            {
                /// Create a new RX pin
                #[allow(clippy::too_many_arguments)]
                pub fn new(
                    $(
                        [< pin_ $pin:lower >] : impl Peripheral<P = impl PeripheralInput > + 'd,
                    )+
                ) -> Self {
                    crate::into_mapped_ref!($( [< pin_ $pin:lower >] ),+);
                    Self { $( [< pin_ $pin:lower >] ),+ }
                }
            }

            impl ConfigurePins for $name<'_>
            {
                fn configure(&mut self)  -> Result<(), Error> {
                    $(
                        self.[< pin_ $pin:lower >].init_input(crate::gpio::Pull::None, crate::private::Internal);
                        crate::gpio::InputSignal::$signal.connect_to(&mut self.[< pin_ $pin:lower >]);
                    )+

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

            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 FullDuplex for RxOneBit<'_> {}
impl FullDuplex for RxTwoBits<'_> {}
impl FullDuplex for RxFourBits<'_> {}
impl FullDuplex for RxEightBits<'_> {}

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> TxCreatorFullDuplex<'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,
        idle_value: u16,
        sample_edge: SampleEdge,
        bit_order: BitPackOrder,
    ) -> Result<ParlIoTx<'d, Dm>, Error>
    where
        P: FullDuplex + TxPins + ConfigurePins,
        CP: TxClkPin,
    {
        tx_pins.configure()?;
        clk_pin.configure();

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

        Ok(ParlIoTx {
            tx_channel: self.tx_channel,
            tx_chain: DescriptorChain::new(self.descriptors),
            _guard: self._guard,
        })
    }
}

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,
        tx_pins: &'d mut P,
        clk_pin: &'d mut CP,
        idle_value: u16,
        sample_edge: SampleEdge,
        bit_order: BitPackOrder,
    ) -> Result<ParlIoTx<'d, Dm>, Error>
    where
        P: TxPins + ConfigurePins,
        CP: TxClkPin,
    {
        tx_pins.configure()?;
        clk_pin.configure();

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

        Ok(ParlIoTx {
            tx_channel: self.tx_channel,
            tx_chain: DescriptorChain::new(self.descriptors),
            _guard: self._guard,
        })
    }
}

/// Parallel IO TX channel
pub struct ParlIoTx<'d, Dm>
where
    Dm: DriverMode,
{
    tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>,
    tx_chain: DescriptorChain,
    _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> RxCreatorFullDuplex<'d, Dm>
where
    Dm: DriverMode,
{
    /// Configure RX to use the given pins and settings
    pub fn with_config<P, CP>(
        self,
        rx_pins: &'d mut P,
        clk_pin: &'d mut CP,
        bit_order: BitPackOrder,
        timeout_ticks: Option<u16>,
    ) -> Result<ParlIoRx<'d, Dm>, Error>
    where
        P: FullDuplex + RxPins + ConfigurePins,
        CP: RxClkPin,
    {
        let guard = GenericPeripheralGuard::new();

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

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

        Ok(ParlIoRx {
            rx_channel: self.rx_channel,
            rx_chain: DescriptorChain::new(self.descriptors),
            _guard: guard,
        })
    }
}

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,
        rx_pins: &'d mut P,
        clk_pin: &'d mut CP,
        bit_order: BitPackOrder,
        timeout_ticks: Option<u16>,
    ) -> Result<ParlIoRx<'d, Dm>, Error>
    where
        P: RxPins + ConfigurePins,
        CP: RxClkPin,
    {
        rx_pins.configure()?;
        clk_pin.configure();

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

        Ok(ParlIoRx {
            rx_channel: self.rx_channel,
            rx_chain: DescriptorChain::new(self.descriptors),
            _guard: self._guard,
        })
    }
}

/// Parallel IO RX channel
pub struct ParlIoRx<'d, Dm>
where
    Dm: DriverMode,
{
    rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
    rx_chain: DescriptorChain,
    _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) {
    #[cfg(esp32c6)]
    {
        for core in crate::Cpu::other() {
            crate::interrupt::disable(core, Interrupt::PARL_IO);
        }
        internal_listen(EnumSet::all(), false);
        internal_clear_interrupts(EnumSet::all());
        unsafe { PARL_IO::steal() }.bind_parl_io_interrupt(handler.handler());

        unwrap!(crate::interrupt::enable(
            Interrupt::PARL_IO,
            handler.priority()
        ));
    }
    #[cfg(esp32h2)]
    {
        for core in crate::Cpu::other() {
            crate::interrupt::disable(core, Interrupt::PARL_IO_RX);
            crate::interrupt::disable(core, Interrupt::PARL_IO_TX);
        }
        internal_listen(EnumSet::all(), false);
        internal_clear_interrupts(EnumSet::all());
        unsafe { PARL_IO::steal() }.bind_parl_io_tx_interrupt(handler.handler());
        unsafe { PARL_IO::steal() }.bind_parl_io_rx_interrupt(handler.handler());

        unwrap!(crate::interrupt::enable(
            Interrupt::PARL_IO_TX,
            handler.priority(),
        ));
        unwrap!(crate::interrupt::enable(
            Interrupt::PARL_IO_RX,
            handler.priority(),
        ));
    }
}

fn internal_listen(interrupts: EnumSet<ParlIoInterrupt>, enable: bool) {
    let parl_io = unsafe { PARL_IO::steal() };
    parl_io.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 = unsafe { PARL_IO::steal() };
    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 = unsafe { PARL_IO::steal() };
    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 in full duplex mode
///
/// Full duplex mode might limit the maximum possible bit width.
pub struct ParlIoFullDuplex<'d, Dm>
where
    Dm: DriverMode,
{
    /// The transmitter (TX) channel responsible for handling DMA transfers in
    /// the parallel I/O full-duplex operation.
    pub tx: TxCreatorFullDuplex<'d, Dm>,
    /// The receiver (RX) channel responsible for handling DMA transfers in the
    /// parallel I/O full-duplex operation.
    pub rx: RxCreatorFullDuplex<'d, Dm>,
}

impl<'d> ParlIoFullDuplex<'d, Blocking> {
    /// Create a new instance of [ParlIoFullDuplex]
    pub fn new<CH>(
        _parl_io: impl Peripheral<P = PARL_IO> + 'd,
        dma_channel: impl Peripheral<P = CH> + 'd,
        tx_descriptors: &'static mut [DmaDescriptor],
        rx_descriptors: &'static mut [DmaDescriptor],
        frequency: HertzU32,
    ) -> Result<Self, Error>
    where
        CH: DmaChannelFor<PARL_IO>,
    {
        let tx_guard = GenericPeripheralGuard::new();
        let rx_guard = GenericPeripheralGuard::new();
        let dma_channel = Channel::new(dma_channel.map(|ch| ch.degrade()));
        internal_init(frequency)?;

        Ok(Self {
            tx: TxCreatorFullDuplex {
                tx_channel: dma_channel.tx,
                descriptors: tx_descriptors,
                _guard: tx_guard,
            },
            rx: RxCreatorFullDuplex {
                rx_channel: dma_channel.rx,
                descriptors: rx_descriptors,
                _guard: rx_guard,
            },
        })
    }

    /// Convert to an async version.
    pub fn into_async(self) -> ParlIoFullDuplex<'d, Async> {
        for core in crate::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);
            }
        }
        ParlIoFullDuplex {
            tx: TxCreatorFullDuplex {
                tx_channel: self.tx.tx_channel.into_async(),
                descriptors: self.tx.descriptors,
                _guard: self.tx._guard,
            },
            rx: RxCreatorFullDuplex {
                rx_channel: self.rx.rx_channel.into_async(),
                descriptors: self.rx.descriptors,
                _guard: self.rx._guard,
            },
        }
    }

    /// Sets the interrupt handler, enables it with
    /// [crate::interrupt::Priority::min()]
    ///
    /// Interrupts are not enabled at the peripheral level here.
    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 ParlIoFullDuplex<'_, Blocking> {}

impl InterruptConfigurable for ParlIoFullDuplex<'_, Blocking> {
    fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
        ParlIoFullDuplex::set_interrupt_handler(self, handler);
    }
}

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

/// Parallel IO in half duplex / TX only mode
pub struct ParlIoTxOnly<'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>,
}

impl<'d> ParlIoTxOnly<'d, Blocking> {
    /// Creates a new [ParlIoTxOnly]
    pub fn new<CH>(
        _parl_io: impl Peripheral<P = PARL_IO> + 'd,
        dma_channel: impl Peripheral<P = CH> + 'd,
        descriptors: &'static mut [DmaDescriptor],
        frequency: HertzU32,
    ) -> Result<Self, Error>
    where
        CH: TxChannelFor<PARL_IO>,
    {
        let guard = GenericPeripheralGuard::new();
        let tx_channel = ChannelTx::new(dma_channel.map(|ch| ch.degrade()));
        internal_init(frequency)?;

        Ok(Self {
            tx: TxCreator {
                tx_channel,
                descriptors,
                _guard: guard,
            },
        })
    }

    /// Converts to Async mode.
    pub fn into_async(self) -> ParlIoTxOnly<'d, Async> {
        for core in crate::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);
            }
        }
        ParlIoTxOnly {
            tx: TxCreator {
                tx_channel: self.tx.tx_channel.into_async(),
                descriptors: self.tx.descriptors,
                _guard: self.tx._guard,
            },
        }
    }

    /// Sets the interrupt handler, enables it with
    /// [crate::interrupt::Priority::min()]
    ///
    /// Interrupts are not enabled at the peripheral level here.
    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: impl Into<EnumSet<ParlIoInterrupt>>) {
        internal_clear_interrupts(interrupts.into());
    }
}

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

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

impl InterruptConfigurable for ParlIoTxOnly<'_, Blocking> {
    fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
        ParlIoTxOnly::set_interrupt_handler(self, handler);
    }
}

/// Parallel IO in half duplex / RX only mode
pub struct ParlIoRxOnly<'d, Dm>
where
    Dm: DriverMode,
{
    /// The receiver (RX) channel responsible for handling DMA transfers in the
    /// parallel I/O operation.
    pub rx: RxCreator<'d, Dm>,
}

impl<'d> ParlIoRxOnly<'d, Blocking> {
    /// Create a new [ParlIoRxOnly] instance
    pub fn new<CH>(
        _parl_io: impl Peripheral<P = PARL_IO> + 'd,
        dma_channel: impl Peripheral<P = CH> + 'd,
        descriptors: &'static mut [DmaDescriptor],
        frequency: HertzU32,
    ) -> Result<Self, Error>
    where
        CH: RxChannelFor<PARL_IO>,
    {
        let guard = GenericPeripheralGuard::new();
        let rx_channel = ChannelRx::new(dma_channel.map(|ch| ch.degrade()));
        internal_init(frequency)?;

        Ok(Self {
            rx: RxCreator {
                rx_channel,
                descriptors,
                _guard: guard,
            },
        })
    }

    /// Converts to Async mode.
    pub fn into_async(self) -> ParlIoRxOnly<'d, Async> {
        for core in crate::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);
            }
        }

        ParlIoRxOnly {
            rx: RxCreator {
                rx_channel: self.rx.rx_channel.into_async(),
                descriptors: self.rx.descriptors,
                _guard: self.rx._guard,
            },
        }
    }

    /// Sets the interrupt handler, enables it with
    /// [crate::interrupt::Priority::min()]
    ///
    /// Interrupts are not enabled at the peripheral level here.
    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: impl Into<EnumSet<ParlIoInterrupt>>) {
        internal_clear_interrupts(interrupts.into());
    }
}

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

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

impl InterruptConfigurable for ParlIoRxOnly<'_, Blocking> {
    fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
        ParlIoRxOnly::set_interrupt_handler(self, handler);
    }
}

fn internal_init(frequency: HertzU32) -> Result<(), Error> {
    if frequency.raw() > 40_000_000 {
        return Err(Error::UnreachableClockRate);
    }

    let pcr = unsafe { &*crate::peripherals::PCR::PTR };

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

    pcr.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)
    });

    pcr.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_sw_en(true);
    Instance::set_rx_sample_mode(SampleMode::InternalSoftwareEnable);

    Ok(())
}

impl<Dm> ParlIoTx<'_, Dm>
where
    Dm: DriverMode,
{
    /// Perform a DMA write.
    ///
    /// This will return a [DmaTransferTx]
    ///
    /// The maximum amount of data to be sent is 32736 bytes.
    pub fn write_dma<'t, TXBUF>(
        &'t mut self,
        words: &'t TXBUF,
    ) -> Result<DmaTransferTx<'t, Self>, Error>
    where
        TXBUF: ReadBuffer,
    {
        let (ptr, len) = unsafe { words.read_buffer() };

        if len > MAX_DMA_SIZE {
            return Err(Error::MaxDmaTransferSizeExceeded);
        }

        self.start_write_bytes_dma(ptr, len)?;

        Ok(DmaTransferTx::new(self))
    }

    fn start_write_bytes_dma(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> {
        let pcr = unsafe { &*crate::peripherals::PCR::PTR };
        pcr.parl_clk_tx_conf()
            .modify(|_, w| w.parl_tx_rst_en().set_bit());

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

        self.tx_channel.is_done();

        unsafe {
            self.tx_chain.fill_for_tx(false, ptr, len)?;
            self.tx_channel
                .prepare_transfer_without_start(DmaPeripheral::ParlIo, &self.tx_chain)
                .and_then(|_| self.tx_channel.start_transfer())?;
        }

        while !Instance::is_tx_ready() {}

        Instance::set_tx_start(true);

        pcr.parl_clk_tx_conf()
            .modify(|_, w| w.parl_tx_rst_en().clear_bit());

        Ok(())
    }
}

impl<Dm> DmaSupport for ParlIoTx<'_, Dm>
where
    Dm: DriverMode,
{
    fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
        while !Instance::is_tx_eof() {}

        Instance::set_tx_start(false);
    }

    fn peripheral_dma_stop(&mut self) {
        unreachable!("unsupported")
    }
}

impl<'d, Dm> DmaSupportTx for ParlIoTx<'d, Dm>
where
    Dm: DriverMode,
{
    type TX = ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>;

    fn tx(&mut self) -> &mut Self::TX {
        &mut self.tx_channel
    }

    fn chain(&mut self) -> &mut DescriptorChain {
        &mut self.tx_chain
    }
}

impl<'d, Dm> ParlIoRx<'d, Dm>
where
    Dm: DriverMode,
{
    /// Perform a DMA read.
    ///
    /// This will return a [DmaTransferRx]
    ///
    /// The maximum amount of data is 32736 bytes when using [EofMode::ByteLen].
    ///
    /// It's only limited by the size of the DMA buffer when using
    /// [EofMode::EnableSignal].
    pub fn read_dma<'t, RXBUF>(
        &'t mut self,
        words: &'t mut RXBUF,
    ) -> Result<DmaTransferRx<'t, Self>, Error>
    where
        RXBUF: WriteBuffer,
    {
        let (ptr, len) = unsafe { words.write_buffer() };

        if !Instance::is_suc_eof_generated_externally() && len > MAX_DMA_SIZE {
            return Err(Error::MaxDmaTransferSizeExceeded);
        }

        Self::start_receive_bytes_dma(&mut self.rx_channel, &mut self.rx_chain, ptr, len)?;

        Ok(DmaTransferRx::new(self))
    }

    fn start_receive_bytes_dma(
        rx_channel: &mut ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
        rx_chain: &mut DescriptorChain,
        ptr: *mut u8,
        len: usize,
    ) -> Result<(), Error> {
        let pcr = unsafe { &*crate::peripherals::PCR::PTR };
        pcr.parl_clk_rx_conf()
            .modify(|_, w| w.parl_rx_rst_en().set_bit());
        pcr.parl_clk_rx_conf()
            .modify(|_, w| w.parl_rx_rst_en().clear_bit());

        Instance::clear_rx_interrupts();
        Instance::set_rx_bytes(len as u16);

        unsafe {
            rx_chain.fill_for_rx(false, ptr, len)?;
            rx_channel
                .prepare_transfer_without_start(DmaPeripheral::ParlIo, rx_chain)
                .and_then(|_| rx_channel.start_transfer())?;
        }

        Instance::set_rx_reg_update();

        Instance::set_rx_start(true);
        Ok(())
    }
}

impl<Dm> DmaSupport for ParlIoRx<'_, Dm>
where
    Dm: DriverMode,
{
    fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) {
        loop {
            if self.rx_channel.is_done()
                || self.rx_channel.has_eof_error()
                || self.rx_channel.has_dscr_empty_error()
            {
                break;
            }
        }

        Instance::set_rx_start(false);
    }

    fn peripheral_dma_stop(&mut self) {
        unreachable!("unsupported")
    }
}

impl<'d, Dm> DmaSupportRx for ParlIoRx<'d, Dm>
where
    Dm: DriverMode,
{
    type RX = ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>;

    fn rx(&mut self) -> &mut Self::RX {
        &mut self.rx_channel
    }

    fn chain(&mut self) -> &mut DescriptorChain {
        &mut self.rx_chain
    }
}

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

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

/// Creates a TX channel
pub struct TxCreatorFullDuplex<'d, Dm>
where
    Dm: DriverMode,
{
    tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel<PARL_IO>>,
    descriptors: &'static mut [DmaDescriptor],
    _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
}

/// Creates a RX channel
pub struct RxCreatorFullDuplex<'d, Dm>
where
    Dm: DriverMode,
{
    rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel<PARL_IO>>,
    descriptors: &'static mut [DmaDescriptor],
    _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>,
}

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

    use procmacros::handler;

    use super::{private::Instance, Error, ParlIoRx, ParlIoTx, MAX_DMA_SIZE};
    use crate::{
        asynch::AtomicWaker,
        dma::{asynch::DmaRxFuture, ReadBuffer, WriteBuffer},
        peripherals::Interrupt,
    };

    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 {
            Instance::listen_tx_done();
            let mut parl_io = unsafe { crate::peripherals::PARL_IO::steal() };

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

            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> {
            TX_WAKER.register(cx.waker());
            if Instance::is_listening_tx_done() {
                Poll::Pending
            } else {
                Poll::Ready(())
            }
        }
    }

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

    impl ParlIoTx<'_, crate::Async> {
        /// Perform a DMA write.
        ///
        /// The maximum amount of data to be sent is 32736 bytes.
        pub async fn write_dma_async<TXBUF>(&mut self, words: &TXBUF) -> Result<(), Error>
        where
            TXBUF: ReadBuffer,
        {
            let (ptr, len) = unsafe { words.read_buffer() };

            if len > MAX_DMA_SIZE {
                return Err(Error::MaxDmaTransferSizeExceeded);
            }

            let future = TxDoneFuture::new();
            self.start_write_bytes_dma(ptr, len)?;
            future.await;

            Ok(())
        }
    }

    impl ParlIoRx<'_, crate::Async> {
        /// Perform a DMA write.
        ///
        /// The maximum amount of data to be sent is 32736 bytes.
        pub async fn read_dma_async<'t, RXBUF>(
            &'t mut self,
            words: &'t mut RXBUF,
        ) -> Result<(), Error>
        where
            RXBUF: WriteBuffer,
        {
            let (ptr, len) = unsafe { words.write_buffer() };

            if !Instance::is_suc_eof_generated_externally() && len > MAX_DMA_SIZE {
                return Err(Error::MaxDmaTransferSizeExceeded);
            }

            let future = DmaRxFuture::new(&mut self.rx_channel);
            Self::start_receive_bytes_dma(future.rx, &mut self.rx_chain, ptr, len)?;
            future.await?;

            Ok(())
        }
    }
}

mod private {
    use super::{BitPackOrder, EofMode, Error, SampleEdge};

    pub trait FullDuplex {}

    pub trait NotContainsValidSignalPin {}

    pub trait ContainsValidSignalPin {}

    pub trait TxPins {}

    pub trait RxPins {}

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

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

    pub trait ConfigurePins {
        fn configure(&mut self) -> Result<(), Error>;
    }

    #[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,
    }

    pub(super) struct Instance;

    #[cfg(esp32c6)]
    impl Instance {
        pub fn set_tx_bit_width(width: WidSel) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            reg_block
                .tx_cfg1()
                .modify(|_, w| unsafe { w.tx_idle_value().bits(value) });
        }

        pub fn set_tx_sample_edge(value: SampleEdge) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            reg_block
                .tx_cfg0()
                .modify(|_, w| w.tx_bit_unpack_order().bit(value as u8 == 1));
        }

        pub fn clear_tx_interrupts() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_tx_ready() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_tx_start(value: bool) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_tx_eof() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

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

        pub fn set_tx_hw_valid_en(value: bool) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_bit_width(width: WidSel) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

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

        pub fn set_rx_sw_en(value: bool) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn clear_rx_interrupts() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_bytes(len: u16) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_pulse_submode_sel(sel: u8) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_reg_update() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_bit_order(value: BitPackOrder) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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 is_suc_eof_generated_externally() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            reg_block.rx_cfg0().read().rx_eof_gen_sel().bit_is_set()
        }

        pub fn listen_tx_done() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn unlisten_tx_done() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_listening_tx_done() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_tx_done_set() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn clear_is_tx_done() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_tx_start(value: bool) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_tx_eof() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

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

        pub fn set_tx_hw_valid_en(value: bool) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

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

        pub fn set_rx_sw_en(value: bool) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn clear_rx_interrupts() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_bytes(len: u16) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_pulse_submode_sel(sel: u8) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_reg_update() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn set_rx_bit_order(value: BitPackOrder) {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };
            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 is_suc_eof_generated_externally() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

            reg_block
                .rx_genrl_cfg()
                .read()
                .rx_eof_gen_sel()
                .bit_is_set()
        }

        pub fn listen_tx_done() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn unlisten_tx_done() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_listening_tx_done() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn is_tx_done_set() -> bool {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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

        pub fn clear_is_tx_done() {
            let reg_block: crate::peripherals::PARL_IO =
                unsafe { crate::peripherals::PARL_IO::steal() };

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