//! # Serial Peripheral Interface - Master Mode
//!
//! ## Overview
//!
//! In this mode, the SPI acts as master and initiates the SPI transactions.
//!
//! ## Configuration
//!
//! The peripheral can be used in full-duplex and half-duplex mode and can
//! leverage DMA for data transfers. It can also be used in blocking or async.
//!
//! ### Exclusive access to the SPI bus
//!
//! If all you want to do is to communicate to a single device, and you initiate
//! transactions yourself, there are a number of ways to achieve this:
//!
//! - Use the [`SpiBus`] or [`SpiBusAsync`] trait and its associated functions to initiate
//!   transactions with simultaneous reads and writes, or
//! - Use the `ExclusiveDevice` struct from [`embedded-hal-bus`] or `SpiDevice` from
//!   [`embassy-embedded-hal`].
//!
//! ### Shared SPI access
//!
//! If you have multiple devices on the same SPI bus that each have their own CS
//! line (and optionally, configuration), you may want to have a look at the
//! implementations provided by [`embedded-hal-bus`] and
//! [`embassy-embedded-hal`].
//!
//! ## Usage
//!
//! The module implements several third-party traits from embedded-hal@1.x.x
//! and [`embassy-embedded-hal`].
//!
//! [`embedded-hal-bus`]: https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/spi/index.html
//! [`embassy-embedded-hal`]: embassy_embedded_hal::shared_bus

#[cfg(esp32)]
use core::cell::Cell;
use core::{
    cell::UnsafeCell,
    future::Future,
    marker::PhantomData,
    mem::MaybeUninit,
    pin::Pin,
    task::{Context, Poll},
};

#[cfg(spi_master_supports_dma)]
mod dma;

#[instability::unstable]
#[cfg(spi_master_supports_dma)]
pub use dma::*;
use embedded_hal::spi::SpiBus;
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
use enumset::{EnumSet, EnumSetType, enum_set};
use procmacros::doc_replace;

use super::{BitOrder, Error, Mode};
use crate::{
    Async,
    Blocking,
    DriverMode,
    RegisterToggle,
    asynch::AtomicWaker,
    clock::Clocks,
    gpio::{
        InputConfig,
        InputSignal,
        NoPin,
        OutputConfig,
        OutputSignal,
        PinGuard,
        interconnect::{self, PeripheralInput, PeripheralOutput},
    },
    handler,
    interrupt::InterruptHandler,
    pac::spi2::RegisterBlock,
    private::{self, DropGuard, Sealed},
    ram,
    system::PeripheralGuard,
    time::Rate,
};

/// Enumeration of possible SPI interrupt events.
#[derive(Debug, Hash, EnumSetType)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
#[instability::unstable]
pub enum SpiInterrupt {
    /// Indicates that the SPI transaction has completed successfully.
    ///
    /// This interrupt is triggered when an SPI transaction has finished
    /// transmitting and receiving data.
    TransferDone,

    /// Triggered at the end of configurable segmented transfer.
    #[cfg(spi_master_has_dma_segmented_transfer)]
    DmaSegmentedTransferDone,

    /// Used and triggered by software. Only used for user defined function.
    #[cfg(spi_master_has_app_interrupts)]
    App2,

    /// Used and triggered by software. Only used for user defined function.
    #[cfg(spi_master_has_app_interrupts)]
    App1,
}

/// The size of the FIFO buffer for SPI
const FIFO_SIZE: usize = if cfg!(esp32s2) { 72 } else { 64 };

/// Padding byte for empty write transfers
const EMPTY_WRITE_PAD: u8 = 0x00;

/// SPI commands, each consisting of a 16-bit command value and a data mode.
///
/// Used to define specific commands sent over the SPI bus.
/// Can be [Command::None] if command phase should be suppressed.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
pub enum Command {
    /// No command is sent.
    None,
    /// A 1-bit command.
    _1Bit(u16, DataMode),
    /// A 2-bit command.
    _2Bit(u16, DataMode),
    /// A 3-bit command.
    _3Bit(u16, DataMode),
    /// A 4-bit command.
    _4Bit(u16, DataMode),
    /// A 5-bit command.
    _5Bit(u16, DataMode),
    /// A 6-bit command.
    _6Bit(u16, DataMode),
    /// A 7-bit command.
    _7Bit(u16, DataMode),
    /// A 8-bit command.
    _8Bit(u16, DataMode),
    /// A 9-bit command.
    _9Bit(u16, DataMode),
    /// A 10-bit command.
    _10Bit(u16, DataMode),
    /// A 11-bit command.
    _11Bit(u16, DataMode),
    /// A 12-bit command.
    _12Bit(u16, DataMode),
    /// A 13-bit command.
    _13Bit(u16, DataMode),
    /// A 14-bit command.
    _14Bit(u16, DataMode),
    /// A 15-bit command.
    _15Bit(u16, DataMode),
    /// A 16-bit command.
    _16Bit(u16, DataMode),
}

impl Command {
    fn width(&self) -> usize {
        match self {
            Command::None => 0,
            Command::_1Bit(_, _) => 1,
            Command::_2Bit(_, _) => 2,
            Command::_3Bit(_, _) => 3,
            Command::_4Bit(_, _) => 4,
            Command::_5Bit(_, _) => 5,
            Command::_6Bit(_, _) => 6,
            Command::_7Bit(_, _) => 7,
            Command::_8Bit(_, _) => 8,
            Command::_9Bit(_, _) => 9,
            Command::_10Bit(_, _) => 10,
            Command::_11Bit(_, _) => 11,
            Command::_12Bit(_, _) => 12,
            Command::_13Bit(_, _) => 13,
            Command::_14Bit(_, _) => 14,
            Command::_15Bit(_, _) => 15,
            Command::_16Bit(_, _) => 16,
        }
    }

    fn value(&self) -> u16 {
        match self {
            Command::None => 0,
            Command::_1Bit(value, _)
            | Command::_2Bit(value, _)
            | Command::_3Bit(value, _)
            | Command::_4Bit(value, _)
            | Command::_5Bit(value, _)
            | Command::_6Bit(value, _)
            | Command::_7Bit(value, _)
            | Command::_8Bit(value, _)
            | Command::_9Bit(value, _)
            | Command::_10Bit(value, _)
            | Command::_11Bit(value, _)
            | Command::_12Bit(value, _)
            | Command::_13Bit(value, _)
            | Command::_14Bit(value, _)
            | Command::_15Bit(value, _)
            | Command::_16Bit(value, _) => *value,
        }
    }

    fn mode(&self) -> DataMode {
        match self {
            Command::None => DataMode::SingleTwoDataLines,
            Command::_1Bit(_, mode)
            | Command::_2Bit(_, mode)
            | Command::_3Bit(_, mode)
            | Command::_4Bit(_, mode)
            | Command::_5Bit(_, mode)
            | Command::_6Bit(_, mode)
            | Command::_7Bit(_, mode)
            | Command::_8Bit(_, mode)
            | Command::_9Bit(_, mode)
            | Command::_10Bit(_, mode)
            | Command::_11Bit(_, mode)
            | Command::_12Bit(_, mode)
            | Command::_13Bit(_, mode)
            | Command::_14Bit(_, mode)
            | Command::_15Bit(_, mode)
            | Command::_16Bit(_, mode) => *mode,
        }
    }

    fn is_none(&self) -> bool {
        matches!(self, Command::None)
    }
}

/// SPI address, ranging from 1 to 32 bits, paired with a data mode.
///
/// This can be used to specify the address phase of SPI transactions.
/// Can be [Address::None] if address phase should be suppressed.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
pub enum Address {
    /// No address phase.
    None,
    /// A 1-bit address.
    _1Bit(u32, DataMode),
    /// A 2-bit address.
    _2Bit(u32, DataMode),
    /// A 3-bit address.
    _3Bit(u32, DataMode),
    /// A 4-bit address.
    _4Bit(u32, DataMode),
    /// A 5-bit address.
    _5Bit(u32, DataMode),
    /// A 6-bit address.
    _6Bit(u32, DataMode),
    /// A 7-bit address.
    _7Bit(u32, DataMode),
    /// A 8-bit address.
    _8Bit(u32, DataMode),
    /// A 9-bit address.
    _9Bit(u32, DataMode),
    /// A 10-bit address.
    _10Bit(u32, DataMode),
    /// A 11-bit address.
    _11Bit(u32, DataMode),
    /// A 12-bit address.
    _12Bit(u32, DataMode),
    /// A 13-bit address.
    _13Bit(u32, DataMode),
    /// A 14-bit address.
    _14Bit(u32, DataMode),
    /// A 15-bit address.
    _15Bit(u32, DataMode),
    /// A 16-bit address.
    _16Bit(u32, DataMode),
    /// A 17-bit address.
    _17Bit(u32, DataMode),
    /// A 18-bit address.
    _18Bit(u32, DataMode),
    /// A 19-bit address.
    _19Bit(u32, DataMode),
    /// A 20-bit address.
    _20Bit(u32, DataMode),
    /// A 21-bit address.
    _21Bit(u32, DataMode),
    /// A 22-bit address.
    _22Bit(u32, DataMode),
    /// A 23-bit address.
    _23Bit(u32, DataMode),
    /// A 24-bit address.
    _24Bit(u32, DataMode),
    /// A 25-bit address.
    _25Bit(u32, DataMode),
    /// A 26-bit address.
    _26Bit(u32, DataMode),
    /// A 27-bit address.
    _27Bit(u32, DataMode),
    /// A 28-bit address.
    _28Bit(u32, DataMode),
    /// A 29-bit address.
    _29Bit(u32, DataMode),
    /// A 30-bit address.
    _30Bit(u32, DataMode),
    /// A 31-bit address.
    _31Bit(u32, DataMode),
    /// A 32-bit address.
    _32Bit(u32, DataMode),
}

impl Address {
    fn width(&self) -> usize {
        match self {
            Address::None => 0,
            Address::_1Bit(_, _) => 1,
            Address::_2Bit(_, _) => 2,
            Address::_3Bit(_, _) => 3,
            Address::_4Bit(_, _) => 4,
            Address::_5Bit(_, _) => 5,
            Address::_6Bit(_, _) => 6,
            Address::_7Bit(_, _) => 7,
            Address::_8Bit(_, _) => 8,
            Address::_9Bit(_, _) => 9,
            Address::_10Bit(_, _) => 10,
            Address::_11Bit(_, _) => 11,
            Address::_12Bit(_, _) => 12,
            Address::_13Bit(_, _) => 13,
            Address::_14Bit(_, _) => 14,
            Address::_15Bit(_, _) => 15,
            Address::_16Bit(_, _) => 16,
            Address::_17Bit(_, _) => 17,
            Address::_18Bit(_, _) => 18,
            Address::_19Bit(_, _) => 19,
            Address::_20Bit(_, _) => 20,
            Address::_21Bit(_, _) => 21,
            Address::_22Bit(_, _) => 22,
            Address::_23Bit(_, _) => 23,
            Address::_24Bit(_, _) => 24,
            Address::_25Bit(_, _) => 25,
            Address::_26Bit(_, _) => 26,
            Address::_27Bit(_, _) => 27,
            Address::_28Bit(_, _) => 28,
            Address::_29Bit(_, _) => 29,
            Address::_30Bit(_, _) => 30,
            Address::_31Bit(_, _) => 31,
            Address::_32Bit(_, _) => 32,
        }
    }

    fn value(&self) -> u32 {
        match self {
            Address::None => 0,
            Address::_1Bit(value, _)
            | Address::_2Bit(value, _)
            | Address::_3Bit(value, _)
            | Address::_4Bit(value, _)
            | Address::_5Bit(value, _)
            | Address::_6Bit(value, _)
            | Address::_7Bit(value, _)
            | Address::_8Bit(value, _)
            | Address::_9Bit(value, _)
            | Address::_10Bit(value, _)
            | Address::_11Bit(value, _)
            | Address::_12Bit(value, _)
            | Address::_13Bit(value, _)
            | Address::_14Bit(value, _)
            | Address::_15Bit(value, _)
            | Address::_16Bit(value, _)
            | Address::_17Bit(value, _)
            | Address::_18Bit(value, _)
            | Address::_19Bit(value, _)
            | Address::_20Bit(value, _)
            | Address::_21Bit(value, _)
            | Address::_22Bit(value, _)
            | Address::_23Bit(value, _)
            | Address::_24Bit(value, _)
            | Address::_25Bit(value, _)
            | Address::_26Bit(value, _)
            | Address::_27Bit(value, _)
            | Address::_28Bit(value, _)
            | Address::_29Bit(value, _)
            | Address::_30Bit(value, _)
            | Address::_31Bit(value, _)
            | Address::_32Bit(value, _) => *value,
        }
    }

    fn is_none(&self) -> bool {
        matches!(self, Address::None)
    }

    fn mode(&self) -> DataMode {
        match self {
            Address::None => DataMode::SingleTwoDataLines,
            Address::_1Bit(_, mode)
            | Address::_2Bit(_, mode)
            | Address::_3Bit(_, mode)
            | Address::_4Bit(_, mode)
            | Address::_5Bit(_, mode)
            | Address::_6Bit(_, mode)
            | Address::_7Bit(_, mode)
            | Address::_8Bit(_, mode)
            | Address::_9Bit(_, mode)
            | Address::_10Bit(_, mode)
            | Address::_11Bit(_, mode)
            | Address::_12Bit(_, mode)
            | Address::_13Bit(_, mode)
            | Address::_14Bit(_, mode)
            | Address::_15Bit(_, mode)
            | Address::_16Bit(_, mode)
            | Address::_17Bit(_, mode)
            | Address::_18Bit(_, mode)
            | Address::_19Bit(_, mode)
            | Address::_20Bit(_, mode)
            | Address::_21Bit(_, mode)
            | Address::_22Bit(_, mode)
            | Address::_23Bit(_, mode)
            | Address::_24Bit(_, mode)
            | Address::_25Bit(_, mode)
            | Address::_26Bit(_, mode)
            | Address::_27Bit(_, mode)
            | Address::_28Bit(_, mode)
            | Address::_29Bit(_, mode)
            | Address::_30Bit(_, mode)
            | Address::_31Bit(_, mode)
            | Address::_32Bit(_, mode) => *mode,
        }
    }
}

/// SPI clock source.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
pub enum ClockSource {
    /// Use the APB clock.
    Apb,
    // #[cfg(any(esp32c2, esp32c3, esp32s3))]
    // Xtal,
}

/// SPI peripheral configuration
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Config {
    /// The precomputed clock configuration register value.
    ///
    /// Clock divider calculations are relatively expensive, and the SPI
    /// peripheral is commonly expected to be used in a shared bus
    /// configuration, where different devices may need different bus clock
    /// frequencies. To reduce the time required to reconfigure the bus, we
    /// cache clock register's value here, for each configuration.
    ///
    /// This field is not intended to be set by the user, and is only used
    /// internally.
    #[builder_lite(skip)]
    reg: Result<u32, ConfigError>,

    /// The target frequency
    #[builder_lite(skip_setter)]
    frequency: Rate,

    /// The clock source
    #[builder_lite(unstable)]
    #[builder_lite(skip_setter)]
    clock_source: ClockSource,

    /// SPI sample/shift mode.
    mode: Mode,

    /// Bit order of the read data.
    read_bit_order: BitOrder,

    /// Bit order of the written data.
    write_bit_order: BitOrder,
}

impl Default for Config {
    fn default() -> Self {
        let mut this = Config {
            reg: Ok(0),
            frequency: Rate::from_mhz(1),
            clock_source: ClockSource::Apb,
            mode: Mode::_0,
            read_bit_order: BitOrder::MsbFirst,
            write_bit_order: BitOrder::MsbFirst,
        };

        this.reg = this.recalculate();

        this
    }
}

impl Config {
    /// Set the frequency of the SPI bus clock.
    pub fn with_frequency(mut self, frequency: Rate) -> Self {
        self.frequency = frequency;
        self.reg = self.recalculate();

        self
    }

    /// Set the clock source of the SPI bus.
    #[instability::unstable]
    pub fn with_clock_source(mut self, clock_source: ClockSource) -> Self {
        self.clock_source = clock_source;
        self.reg = self.recalculate();

        self
    }

    fn clock_source_freq_hz(&self) -> Rate {
        let clocks = Clocks::get();

        // FIXME: take clock source into account
        cfg_if::cfg_if! {
            if #[cfg(esp32h2)] {
                // ESP32-H2 is using PLL_48M_CLK source instead of APB_CLK
                let _clocks = clocks;
                Rate::from_mhz(48)
            } else if #[cfg(any(esp32c5, esp32c61))] {
                // We select the 160MHz PLL as the clock source in the driver. There is a by-2 divider
                // configured between the PLL and the SPI clock (spi2_clkm_div_num).
                let _clocks = clocks;
                Rate::from_mhz(80) // clk_spi2_mst must be <= 80MHz
            } else if #[cfg(esp32c6)] {
                // We select the 80MHz PLL as the clock source in the driver
                // FIXME we state that the default clock source is APB, which just isn't true
                let _clocks = clocks;
                Rate::from_mhz(80)
            } else {
                clocks.apb_clock
            }
        }
    }

    fn recalculate(&self) -> Result<u32, ConfigError> {
        // taken from https://github.com/apache/incubator-nuttx/blob/8267a7618629838231256edfa666e44b5313348e/arch/risc-v/src/esp32c3/esp32c3_spi.c#L496
        let source_freq = self.clock_source_freq_hz();

        let reg_val: u32;
        let duty_cycle = 128;

        // In HW, n, h and l fields range from 1 to 64, pre ranges from 1 to 8K.
        // The value written to register is one lower than the used value.

        if self.frequency > ((source_freq / 4) * 3) {
            // Using APB frequency directly will give us the best result here.
            reg_val = 1 << 31;
        } else {
            // For best duty cycle resolution, we want n to be as close to 32 as
            // possible, but we also need a pre/n combo that gets us as close as
            // possible to the intended frequency. To do this, we bruteforce n and
            // calculate the best pre to go along with that. If there's a choice
            // between pre/n combos that give the same result, use the one with the
            // higher n.

            let mut pre: i32;
            let mut bestn: i32 = -1;
            let mut bestpre: i32 = -1;
            let mut besterr: i32 = 0;
            let mut errval: i32;

            let target_freq_hz = self.frequency.as_hz() as i32;
            let source_freq_hz = source_freq.as_hz() as i32;

            // Start at n = 2. We need to be able to set h/l so we have at least
            // one high and one low pulse.

            for n in 2..=64 {
                // Effectively, this does:
                // pre = round((APB_CLK_FREQ / n) / frequency)

                pre = ((source_freq_hz / n) + (target_freq_hz / 2)) / target_freq_hz;

                if pre <= 0 {
                    pre = 1;
                }

                if pre > 16 {
                    pre = 16;
                }

                errval = (source_freq_hz / (pre * n) - target_freq_hz).abs();
                if bestn == -1 || errval <= besterr {
                    besterr = errval;
                    bestn = n;
                    bestpre = pre;
                }
            }

            let n: i32 = bestn;
            pre = bestpre;
            let l: i32 = n;

            // Effectively, this does:
            // h = round((duty_cycle * n) / 256)

            let mut h: i32 = (duty_cycle * n + 127) / 256;
            if h <= 0 {
                h = 1;
            }

            reg_val = (l as u32 - 1)
                | ((h as u32 - 1) << 6)
                | ((n as u32 - 1) << 12)
                | ((pre as u32 - 1) << 18);
        }

        Ok(reg_val)
    }

    fn raw_clock_reg_value(&self) -> Result<u32, ConfigError> {
        self.reg
    }

    fn validate(&self) -> Result<(), ConfigError> {
        let source_freq = self.clock_source_freq_hz();
        let min_divider = 1;
        // FIXME: while ESP32 and S2 support pre dividers as large as 8192,
        // those values are not currently supported by the divider calculation.
        let max_divider = 16 * 64; // n * pre

        if self.frequency < source_freq / max_divider || self.frequency > source_freq / min_divider
        {
            return Err(ConfigError::FrequencyOutOfRange);
        }

        Ok(())
    }
}

const SIO_PIN_COUNT: usize = 4 + cfg!(spi_master_has_octal) as usize * 4;

#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct SpiPinGuard {
    sclk_pin: PinGuard,
    cs_pin: PinGuard,
    sio_pins: [PinGuard; SIO_PIN_COUNT],
}

impl SpiPinGuard {
    const fn new_unconnected() -> Self {
        Self {
            sclk_pin: PinGuard::new_unconnected(),
            cs_pin: PinGuard::new_unconnected(),
            sio_pins: [const { PinGuard::new_unconnected() }; SIO_PIN_COUNT],
        }
    }
}

/// Configuration errors.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
    /// The requested frequency is not in the supported range.
    FrequencyOutOfRange,
}

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::FrequencyOutOfRange => {
                write!(f, "The requested frequency is not in the supported range")
            }
        }
    }
}

#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct SpiWrapper<'d> {
    spi: AnySpi<'d>,
    _guard: PeripheralGuard,
}

impl<'d> SpiWrapper<'d> {
    fn new(spi: impl Instance + 'd) -> Self {
        let p = spi.info().peripheral;
        let this = Self {
            spi: spi.degrade(),
            _guard: PeripheralGuard::new(p),
        };

        // Initialize state
        unsafe {
            this.state()
                .pins
                .get()
                .write(MaybeUninit::new(SpiPinGuard::new_unconnected()))
        }

        this
    }

    fn info(&self) -> &'static Info {
        self.spi.info()
    }

    fn state(&self) -> &'static State {
        self.spi.state()
    }

    fn disable_peri_interrupt_on_all_cores(&self) {
        self.spi.disable_peri_interrupt_on_all_cores();
    }

    fn set_interrupt_handler(&self, handler: InterruptHandler) {
        self.spi.set_interrupt_handler(handler);
    }

    fn pins(&mut self) -> &mut SpiPinGuard {
        unsafe {
            // SAFETY: we "own" the state, we are allowed to borrow it mutably
            self.state().pins()
        }
    }
}

impl Drop for SpiWrapper<'_> {
    fn drop(&mut self) {
        unsafe {
            // SAFETY: we "own" the state, we are allowed to deinit it
            self.spi.state().deinit();
        }
    }
}

#[procmacros::doc_replace]
/// SPI peripheral driver
///
/// ## Example
///
/// ```rust, no_run
/// # {before_snippet}
/// use esp_hal::spi::{
///     Mode,
///     master::{Config, Spi},
/// };
/// let mut spi = Spi::new(
///     peripherals.SPI2,
///     Config::default()
///         .with_frequency(Rate::from_khz(100))
///         .with_mode(Mode::_0),
/// )?
/// .with_sck(peripherals.GPIO0)
/// .with_mosi(peripherals.GPIO1)
/// .with_miso(peripherals.GPIO2);
/// # {after_snippet}
/// ```
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Spi<'d, Dm: DriverMode> {
    spi: SpiWrapper<'d>,
    _mode: PhantomData<Dm>,
}

impl<Dm: DriverMode> Sealed for Spi<'_, Dm> {}

impl<'d> Spi<'d, Blocking> {
    #[procmacros::doc_replace]
    /// Constructs an SPI instance in 8bit dataframe mode.
    ///
    /// ## Errors
    ///
    /// See [`Spi::apply_config`].
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?
    ///     .with_sck(peripherals.GPIO0)
    ///     .with_mosi(peripherals.GPIO1)
    ///     .with_miso(peripherals.GPIO2);
    /// # {after_snippet}
    /// ```
    pub fn new(spi: impl Instance + 'd, config: Config) -> Result<Self, ConfigError> {
        let mut this = Spi {
            _mode: PhantomData,
            spi: SpiWrapper::new(spi),
        };

        this.driver().init();
        this.apply_config(&config)?;

        let this = this.with_sck(NoPin).with_cs(NoPin);

        for sio in 0..8 {
            if let Some(signal) = this.driver().info.opt_sio_input(sio) {
                signal.connect_to(&NoPin);
            }
            if let Some(signal) = this.driver().info.opt_sio_output(sio) {
                signal.connect_to(&NoPin);
            }
        }

        Ok(this)
    }

    /// Reconfigures the driver to operate in [`Async`] mode.
    ///
    /// See the [`Async`] documentation for an example on how to use this
    /// method.
    pub fn into_async(mut self) -> Spi<'d, Async> {
        self.set_interrupt_handler(self.spi.info().async_handler);
        Spi {
            spi: self.spi,
            _mode: PhantomData,
        }
    }

    #[doc_replace(
        "peripheral_on" => {
            cfg(multi_core) => "peripheral on the current core",
            _ => "peripheral",
        }
    )]
    /// # Registers an interrupt handler for the __peripheral_on__.
    ///
    /// Note that this will replace any previously registered interrupt
    /// handlers.
    ///
    /// You can restore the default/unhandled interrupt handler by using
    /// [crate::interrupt::DEFAULT_INTERRUPT_HANDLER]
    ///
    /// # Panics
    ///
    /// Panics if passed interrupt handler is invalid (e.g. has priority
    /// `None`)
    #[instability::unstable]
    pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
        self.spi.set_interrupt_handler(handler);
    }
}

#[instability::unstable]
impl crate::interrupt::InterruptConfigurable for Spi<'_, Blocking> {
    /// Sets the interrupt handler
    ///
    /// Interrupts are not enabled at the peripheral level here.
    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
        self.set_interrupt_handler(handler);
    }
}

impl<'d> Spi<'d, Async> {
    /// Reconfigures the driver to operate in [`Blocking`] mode.
    ///
    /// See the [`Blocking`] documentation for an example on how to use this
    /// method.
    pub fn into_blocking(self) -> Spi<'d, Blocking> {
        self.spi.disable_peri_interrupt_on_all_cores();
        Spi {
            spi: self.spi,
            _mode: PhantomData,
        }
    }

    #[procmacros::doc_replace]
    /// Waits for the completion of previous operations.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?
    ///     .with_sck(peripherals.GPIO0)
    ///     .with_mosi(peripherals.GPIO1)
    ///     .with_miso(peripherals.GPIO2)
    ///     .into_async();
    ///
    /// let mut buffer = [0; 10];
    /// spi.transfer_in_place_async(&mut buffer).await?;
    /// spi.flush_async().await?;
    ///
    /// # {after_snippet}
    /// ```
    pub async fn flush_async(&mut self) -> Result<(), Error> {
        self.driver().flush_async().await;
        Ok(())
    }

    #[procmacros::doc_replace]
    /// Sends `words` to the slave. Returns the `words` received from the slave.
    ///
    /// This function aborts the transfer when its Future is dropped. Some
    /// amount of data may have been transferred before the Future is
    /// dropped. Dropping the future may block for a short while to ensure
    /// the transfer is aborted.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?
    ///     .with_sck(peripherals.GPIO0)
    ///     .with_mosi(peripherals.GPIO1)
    ///     .with_miso(peripherals.GPIO2)
    ///     .into_async();
    ///
    /// let mut buffer = [0; 10];
    /// spi.transfer_in_place_async(&mut buffer).await?;
    ///
    /// # {after_snippet}
    /// ```
    pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
        // We need to flush because the blocking transfer functions may return while a
        // transfer is still in progress.
        self.driver().flush_async().await;
        self.driver().setup_full_duplex()?;

        self.driver().transfer_in_place_async(words).await
    }

    // TODO: These inherent methods should be public

    async fn read_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
        // We need to flush because the blocking transfer functions may return while a
        // transfer is still in progress.
        self.driver().flush_async().await;
        self.driver().setup_full_duplex()?;

        self.driver().read_async(words).await
    }

    async fn write_async(&mut self, words: &[u8]) -> Result<(), Error> {
        // We need to flush because the blocking transfer functions may return while a
        // transfer is still in progress.
        self.driver().flush_async().await;
        self.driver().setup_full_duplex()?;

        self.driver().write_async(words).await
    }
}

macro_rules! def_with_sio_pin {
    ($fn:ident, $n:literal) => {
        #[doc = concat!(" Assign the SIO", stringify!($n), " pin for the SPI instance.")]
        #[doc = " "]
        #[doc = " Enables both input and output functionality for the pin, and connects it"]
        #[doc = concat!(" to the SIO", stringify!($n), " output and input signals.")]
        #[instability::unstable]
        pub fn $fn(mut self, sio: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self {
            self.spi.pins().sio_pins[$n] = self.connect_sio_pin(sio.into(), $n);

            self
        }
    };
}

impl<'d, Dm> Spi<'d, Dm>
where
    Dm: DriverMode,
{
    fn connect_sio_pin(&self, pin: interconnect::OutputSignal<'d>, n: usize) -> PinGuard {
        let in_signal = self.spi.info().sio_input(n);
        let out_signal = self.spi.info().sio_output(n);

        pin.apply_input_config(&InputConfig::default());
        pin.apply_output_config(&OutputConfig::default());

        pin.set_input_enable(true);
        pin.set_output_enable(false);

        in_signal.connect_to(&pin);
        pin.connect_with_guard(out_signal)
    }

    fn connect_sio_output_pin(&self, pin: interconnect::OutputSignal<'d>, n: usize) -> PinGuard {
        let out_signal = self.spi.info().sio_output(n);

        self.connect_output_pin(pin, out_signal)
    }

    fn connect_output_pin(
        &self,
        pin: interconnect::OutputSignal<'d>,
        signal: OutputSignal,
    ) -> PinGuard {
        pin.apply_output_config(&OutputConfig::default());
        pin.set_output_enable(true); // TODO turn this bool into a Yes/No/PeripheralControl trio

        pin.connect_with_guard(signal)
    }

    #[procmacros::doc_replace]
    /// Assign the SCK (Serial Clock) pin for the SPI instance.
    ///
    /// Configures the specified pin to push-pull output and connects it to the
    /// SPI clock signal.
    ///
    /// Disconnects the previous pin that was assigned with `with_sck`.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?.with_sck(peripherals.GPIO0);
    ///
    /// # {after_snippet}
    /// ```
    pub fn with_sck(mut self, sclk: impl PeripheralOutput<'d>) -> Self {
        let info = self.spi.info();
        self.spi.pins().sclk_pin = self.connect_output_pin(sclk.into(), info.sclk);

        self
    }

    #[procmacros::doc_replace]
    /// Assign the MOSI (Master Out Slave In) pin for the SPI instance.
    ///
    /// Enables output functionality for the pin, and connects it as the MOSI
    /// signal. You want to use this for full-duplex SPI or
    /// if you intend to use [DataMode::SingleTwoDataLines].
    ///
    /// Disconnects the previous pin that was assigned with `with_mosi` or
    /// `with_sio0`.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?.with_mosi(peripherals.GPIO1);
    ///
    /// # {after_snippet}
    /// ```
    pub fn with_mosi(mut self, mosi: impl PeripheralOutput<'d>) -> Self {
        self.spi.pins().sio_pins[0] = self.connect_sio_output_pin(mosi.into(), 0);
        self
    }

    #[procmacros::doc_replace]
    /// Assign the MISO (Master In Slave Out) pin for the SPI instance.
    ///
    /// Enables input functionality for the pin, and connects it to the MISO
    /// signal.
    ///
    /// You want to use this for full-duplex SPI or
    /// [DataMode::SingleTwoDataLines]
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?.with_miso(peripherals.GPIO2);
    ///
    /// # {after_snippet}
    /// ```
    pub fn with_miso(self, miso: impl PeripheralInput<'d>) -> Self {
        let miso = miso.into();

        miso.apply_input_config(&InputConfig::default());
        miso.set_input_enable(true);

        self.driver().info.sio_input(1).connect_to(&miso);

        self
    }

    /// Assign the SIO0 pin for the SPI instance.
    ///
    /// Enables both input and output functionality for the pin, and connects it
    /// to the MOSI output signal and SIO0 input signal.
    ///
    /// Disconnects the previous pin that was assigned with `with_sio0` or
    /// `with_mosi`.
    ///
    /// Use this if any of the devices on the bus use half-duplex SPI.
    ///
    /// See also [Self::with_mosi] when you only need a one-directional MOSI
    /// signal.
    #[instability::unstable]
    pub fn with_sio0(mut self, mosi: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self {
        self.spi.pins().sio_pins[0] = self.connect_sio_pin(mosi.into(), 0);

        self
    }

    /// Assign the SIO1/MISO pin for the SPI instance.
    ///
    /// Enables both input and output functionality for the pin, and connects it
    /// to the MISO input signal and SIO1 output signal.
    ///
    /// Disconnects the previous pin that was assigned with `with_sio1`.
    ///
    /// Use this if any of the devices on the bus use half-duplex SPI.
    ///
    /// See also [Self::with_miso] when you only need a one-directional MISO
    /// signal.
    #[instability::unstable]
    pub fn with_sio1(mut self, sio1: impl PeripheralInput<'d> + PeripheralOutput<'d>) -> Self {
        self.spi.pins().sio_pins[1] = self.connect_sio_pin(sio1.into(), 1);

        self
    }

    def_with_sio_pin!(with_sio2, 2);
    def_with_sio_pin!(with_sio3, 3);

    #[cfg(spi_master_has_octal)]
    def_with_sio_pin!(with_sio4, 4);

    #[cfg(spi_master_has_octal)]
    def_with_sio_pin!(with_sio5, 5);

    #[cfg(spi_master_has_octal)]
    def_with_sio_pin!(with_sio6, 6);

    #[cfg(spi_master_has_octal)]
    def_with_sio_pin!(with_sio7, 7);

    /// Assign the CS (Chip Select) pin for the SPI instance.
    ///
    /// Configures the specified pin to push-pull output and connects it to the
    /// SPI CS signal.
    ///
    /// Disconnects the previous pin that was assigned with `with_cs`.
    ///
    /// # Current Stability Limitations
    /// The hardware chip select functionality is limited; only one CS line can
    /// be set, regardless of the total number available. There is no
    /// mechanism to select which CS line to use.
    #[instability::unstable]
    pub fn with_cs(mut self, cs: impl PeripheralOutput<'d>) -> Self {
        let info = self.spi.info();
        self.spi.pins().cs_pin = self.connect_output_pin(cs.into(), info.cs(0));

        self
    }

    #[doc_replace(
        "max_frequency" => {
            cfg(esp32h2) => "48MHz",
            _ => "80MHz",
        }
    )]
    /// Change the bus configuration.
    ///
    /// # Errors
    ///
    /// If frequency passed in config exceeds __max_frequency__ or is below 70kHz,
    /// [`ConfigError::FrequencyOutOfRange`] error will be returned.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?;
    ///
    /// spi.apply_config(&Config::default().with_frequency(Rate::from_khz(100)));
    /// #
    /// # {after_snippet}
    /// ```
    pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
        self.driver().apply_config(config)
    }

    #[procmacros::doc_replace]
    /// Write bytes to SPI. After writing, flush is called to ensure all data
    /// has been transmitted.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?
    ///     .with_sck(peripherals.GPIO0)
    ///     .with_mosi(peripherals.GPIO1)
    ///     .with_miso(peripherals.GPIO2)
    ///     .into_async();
    ///
    /// let buffer = [0; 10];
    /// spi.write(&buffer)?;
    ///
    /// # {after_snippet}
    /// ```
    pub fn write(&mut self, words: &[u8]) -> Result<(), Error> {
        self.driver().flush()?;
        self.driver().setup_full_duplex()?;

        for chunk in words.chunks(FIFO_SIZE) {
            self.driver().write_one(chunk)?;
            // NOTE: While we don't need to flush after the last chunk, changing
            // that would change the behavior of the function.
            // https://github.com/esp-rs/esp-hal/issues/5257
            self.driver().flush()?;
        }

        Ok(())
    }

    #[procmacros::doc_replace]
    /// Read bytes from SPI. The provided slice is filled with data received
    /// from the slave.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?
    ///     .with_sck(peripherals.GPIO0)
    ///     .with_mosi(peripherals.GPIO1)
    ///     .with_miso(peripherals.GPIO2)
    ///     .into_async();
    ///
    /// let mut buffer = [0; 10];
    /// spi.read(&mut buffer)?;
    ///
    /// # {after_snippet}
    /// ```
    pub fn read(&mut self, words: &mut [u8]) -> Result<(), Error> {
        self.driver().flush()?;
        self.driver().setup_full_duplex()?;
        self.driver().read(words)
    }

    #[procmacros::doc_replace]
    /// Sends `words` to the slave. The received data will be written to
    /// `words`, overwriting its contents.
    ///
    /// ## Example
    ///
    /// ```rust, no_run
    /// # {before_snippet}
    /// use esp_hal::spi::{
    ///     Mode,
    ///     master::{Config, Spi},
    /// };
    /// let mut spi = Spi::new(peripherals.SPI2, Config::default())?
    ///     .with_sck(peripherals.GPIO0)
    ///     .with_mosi(peripherals.GPIO1)
    ///     .with_miso(peripherals.GPIO2)
    ///     .into_async();
    ///
    /// let mut buffer = [0; 10];
    /// spi.transfer(&mut buffer)?;
    ///
    /// # {after_snippet}
    /// ```
    pub fn transfer(&mut self, words: &mut [u8]) -> Result<(), Error> {
        self.driver().flush()?;
        self.driver().setup_full_duplex()?;
        self.driver().transfer_in_place(words)
    }

    /// Half-duplex read.
    ///
    /// # Errors
    ///
    /// [`Error::FifoSizeExeeded`] or [`Error::Unsupported`] will be returned if
    /// passed buffer is bigger than FIFO size or if buffer is empty (currently
    /// unsupported). `DataMode::Single` cannot be combined with any other
    /// [`DataMode`], otherwise [`Error::Unsupported`] will be returned.
    #[instability::unstable]
    pub fn half_duplex_read(
        &mut self,
        data_mode: DataMode,
        cmd: Command,
        address: Address,
        dummy: u8,
        buffer: &mut [u8],
    ) -> Result<(), Error> {
        if buffer.len() > FIFO_SIZE {
            return Err(Error::FifoSizeExeeded);
        }

        if buffer.is_empty() {
            error!("Half-duplex mode does not support empty buffer");
            return Err(Error::Unsupported);
        }

        self.flush()?;
        self.driver().setup_half_duplex(
            false,
            cmd,
            address,
            false,
            dummy,
            buffer.is_empty(),
            data_mode,
        )?;

        self.driver().configure_datalen(buffer.len(), 0);
        self.driver().start_operation();
        self.driver().flush()?;
        self.driver().read_from_fifo(buffer)
    }

    /// Half-duplex write.
    ///
    /// # Errors
    ///
    /// [`Error::FifoSizeExeeded`] will be returned if
    /// passed buffer is bigger than FIFO size.
    #[cfg_attr(
        esp32,
        doc = "Dummy phase configuration is currently not supported, only value `0` is valid (see issue [#2240](https://github.com/esp-rs/esp-hal/issues/2240))."
    )]
    #[instability::unstable]
    pub fn half_duplex_write(
        &mut self,
        data_mode: DataMode,
        cmd: Command,
        address: Address,
        dummy: u8,
        buffer: &[u8],
    ) -> Result<(), Error> {
        if buffer.len() > FIFO_SIZE {
            return Err(Error::FifoSizeExeeded);
        }

        self.flush()?;

        cfg_if::cfg_if! {
            if #[cfg(all(esp32, spi_address_workaround))] {
                let mut buffer = buffer;
                let mut data_mode = data_mode;
                let mut address = address;
                let addr_bytes;
                if buffer.is_empty() && !address.is_none() {
                    // If the buffer is empty, we need to send a dummy byte
                    // to trigger the address phase.
                    let bytes_to_write = address.width().div_ceil(8);
                    // The address register is read in big-endian order,
                    // we have to prepare the emulated write in the same way.
                    addr_bytes = address.value().to_be_bytes();
                    buffer = &addr_bytes[4 - bytes_to_write..][..bytes_to_write];
                    data_mode = address.mode();
                    address = Address::None;
                }

                if dummy > 0 {
                    // FIXME: https://github.com/esp-rs/esp-hal/issues/2240
                    error!("Dummy bits are not supported without data");
                    return Err(Error::Unsupported);
                }
            }
        }

        self.driver().setup_half_duplex(
            true,
            cmd,
            address,
            false,
            dummy,
            buffer.is_empty(),
            data_mode,
        )?;

        if !buffer.is_empty() {
            self.driver().configure_datalen(0, buffer.len());
            self.driver().fill_fifo(buffer);
        }

        self.driver().start_operation();

        self.driver().flush()
    }

    fn driver(&self) -> Driver {
        Driver {
            info: self.spi.info(),
            state: self.spi.state(),
        }
    }
}

#[instability::unstable]
impl<Dm> embassy_embedded_hal::SetConfig for Spi<'_, Dm>
where
    Dm: DriverMode,
{
    type Config = Config;
    type ConfigError = ConfigError;

    fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
        self.apply_config(config)
    }
}

impl<Dm> embedded_hal::spi::ErrorType for Spi<'_, Dm>
where
    Dm: DriverMode,
{
    type Error = Error;
}

impl<Dm> SpiBus for Spi<'_, Dm>
where
    Dm: DriverMode,
{
    fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
        self.read(words)
    }

    fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
        // Do not call the inherent `write` method. The trait impl does not flush after.
        // Flush before starting to ensure the bus is clear before we reconfigure to full duplex.
        self.driver().flush()?;
        self.driver().setup_full_duplex()?;
        for chunk in words.chunks(FIFO_SIZE) {
            self.driver().flush()?;
            self.driver().write_one(chunk)?;
        }
        Ok(())
    }

    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
        self.driver().flush()?;
        self.driver().setup_full_duplex()?;

        if read.is_empty() {
            self.driver().write(write)
        } else if write.is_empty() {
            self.driver().read(read)
        } else {
            self.driver().transfer(read, write)
        }
    }

    fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
        self.driver().flush()?;
        self.driver().setup_full_duplex()?;
        self.driver().transfer_in_place(words)
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        self.driver().flush()
    }
}

impl SpiBusAsync for Spi<'_, Async> {
    async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
        self.read_async(words).await
    }

    async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
        self.write_async(words).await
    }

    async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
        self.driver().flush_async().await;
        self.driver().setup_full_duplex()?;

        if read.is_empty() {
            self.driver().write_async(write).await
        } else if write.is_empty() {
            self.driver().read_async(read).await
        } else {
            self.driver().transfer_async(read, write).await
        }
    }

    async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
        self.transfer_in_place_async(words).await
    }

    async fn flush(&mut self) -> Result<(), Self::Error> {
        self.flush_async().await
    }
}

/// SPI peripheral instance.
pub trait Instance: private::Sealed + any::Degrade {
    #[doc(hidden)]
    /// Returns the peripheral data and state describing this instance.
    fn parts(&self) -> (&'static Info, &'static State);

    /// Returns the peripheral data describing this instance.
    #[doc(hidden)]
    #[inline(always)]
    fn info(&self) -> &'static Info {
        self.parts().0
    }

    /// Returns the peripheral state for this instance.
    #[doc(hidden)]
    #[inline(always)]
    fn state(&self) -> &'static State {
        self.parts().1
    }
}

/// Marker trait for QSPI-capable SPI peripherals.
#[doc(hidden)]
pub trait QspiInstance: Instance {}

/// Peripheral data describing a particular SPI instance.
#[doc(hidden)]
#[non_exhaustive]
#[allow(private_interfaces, reason = "Unstable details")]
pub struct Info {
    /// Pointer to the register block for this SPI instance.
    ///
    /// Use [Self::register_block] to access the register block.
    pub register_block: *const RegisterBlock,

    /// The system peripheral marker.
    pub peripheral: crate::system::Peripheral,

    /// Interrupt handler for the asynchronous operations.
    pub async_handler: InterruptHandler,

    /// SCLK signal.
    pub sclk: OutputSignal,

    /// Chip select signals.
    pub cs: &'static [OutputSignal],

    pub sio_inputs: &'static [InputSignal],
    pub sio_outputs: &'static [OutputSignal],
}

impl Info {
    fn cs(&self, n: usize) -> OutputSignal {
        *unwrap!(self.cs.get(n), "CS{} is not defined", n)
    }

    fn opt_sio_input(&self, n: usize) -> Option<InputSignal> {
        self.sio_inputs.get(n).copied()
    }

    fn opt_sio_output(&self, n: usize) -> Option<OutputSignal> {
        self.sio_outputs.get(n).copied()
    }

    fn sio_input(&self, n: usize) -> InputSignal {
        unwrap!(self.opt_sio_input(n), "SIO{} is not defined", n)
    }

    fn sio_output(&self, n: usize) -> OutputSignal {
        unwrap!(self.opt_sio_output(n), "SIO{} is not defined", n)
    }
}

struct Driver {
    info: &'static Info,
    state: &'static State,
}

// private implementation bits
// FIXME: split this up into peripheral versions
impl Driver {
    /// Returns the register block for this SPI instance.
    pub fn regs(&self) -> &RegisterBlock {
        unsafe { &*self.info.register_block }
    }

    fn abort_transfer(&self) {
        // Note(danielb): This method came later than DmaDriver::abort_transfer. That
        // function works for DMA so I have left it unchanged, but does not work
        // for CPU-controlled transfers on the ESP32. Toggling slave mode works on
        // ESP32, but not on later chips.
        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                self.regs().slave().toggle(|w, en| w.mode().bit(en));
            } else {
                self.configure_datalen(1, 1);
            }
        }
        self.update();
    }

    /// Initialize for full-duplex 1 bit mode
    fn init(&self) {
        self.regs().user().modify(|_, w| {
            w.usr_miso_highpart().clear_bit();
            w.usr_mosi_highpart().clear_bit();
            w.doutdin().set_bit();
            w.usr_miso().set_bit();
            w.usr_mosi().set_bit();
            w.cs_hold().set_bit();
            w.usr_dummy_idle().set_bit();
            w.usr_addr().clear_bit();
            w.usr_command().clear_bit()
        });

        #[cfg(not(any(esp32, esp32s2)))]
        self.regs().clk_gate().modify(|_, w| {
            w.clk_en().set_bit();
            w.mst_clk_active().set_bit();
            w.mst_clk_sel().set_bit()
        });

        #[cfg(soc_has_pcr)]
        unsafe {
            // use default clock source
            crate::peripherals::PCR::regs()
                .spi2_clkm_conf()
                .modify(|_, w| {
                    #[cfg(spi_master_has_clk_pre_div)]
                    w.spi2_clkm_div_num().bits(1);
                    w.spi2_clkm_sel().bits(1);
                    w.spi2_clkm_en().set_bit()
                });
        }

        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                self.regs().ctrl().modify(|_, w| w.wp().clear_bit());
            } else {
                self.regs().ctrl().modify(|_, w| {
                    w.q_pol().clear_bit();
                    w.d_pol().clear_bit();
                    #[cfg(esp32s2)]
                    w.wp().clear_bit();
                    #[cfg(not(esp32s2))]
                    w.hold_pol().clear_bit();
                    w
                });
                self.regs().misc().write(|w| unsafe { w.bits(0) });
            }
        }

        self.regs().slave().write(|w| unsafe { w.bits(0) });
    }

    #[cfg(not(esp32))]
    fn init_spi_data_mode(
        &self,
        cmd_mode: DataMode,
        address_mode: DataMode,
        data_mode: DataMode,
    ) -> Result<(), Error> {
        self.regs().ctrl().modify(|_, w| {
            w.fcmd_dual().bit(cmd_mode == DataMode::Dual);
            w.fcmd_quad().bit(cmd_mode == DataMode::Quad);
            w.faddr_dual().bit(address_mode == DataMode::Dual);
            w.faddr_quad().bit(address_mode == DataMode::Quad);
            w.fread_dual().bit(data_mode == DataMode::Dual);
            w.fread_quad().bit(data_mode == DataMode::Quad)
        });
        self.regs().user().modify(|_, w| {
            w.fwrite_dual().bit(data_mode == DataMode::Dual);
            w.fwrite_quad().bit(data_mode == DataMode::Quad)
        });
        Ok(())
    }

    #[cfg(esp32)]
    fn init_spi_data_mode(
        &self,
        cmd_mode: DataMode,
        address_mode: DataMode,
        data_mode: DataMode,
    ) -> Result<(), Error> {
        match cmd_mode {
            DataMode::Single => (),
            DataMode::SingleTwoDataLines => (),
            // FIXME: more detailed error - Only 1-bit commands are supported.
            _ => {
                error!("Commands must be single bit wide");
                return Err(Error::Unsupported);
            }
        }

        match address_mode {
            DataMode::Single | DataMode::SingleTwoDataLines => {
                self.regs().ctrl().modify(|_, w| {
                    w.fastrd_mode()
                        .bit(matches!(data_mode, DataMode::Dual | DataMode::Quad));
                    w.fread_dio().clear_bit();
                    w.fread_qio().clear_bit();
                    w.fread_dual().bit(data_mode == DataMode::Dual);
                    w.fread_quad().bit(data_mode == DataMode::Quad)
                });

                self.regs().user().modify(|_, w| {
                    w.fwrite_dio().clear_bit();
                    w.fwrite_qio().clear_bit();
                    w.fwrite_dual().bit(data_mode == DataMode::Dual);
                    w.fwrite_quad().bit(data_mode == DataMode::Quad)
                });
            }
            address_mode if address_mode == data_mode => {
                self.regs().ctrl().modify(|_, w| {
                    w.fastrd_mode()
                        .bit(matches!(data_mode, DataMode::Dual | DataMode::Quad));
                    w.fread_dio().bit(address_mode == DataMode::Dual);
                    w.fread_qio().bit(address_mode == DataMode::Quad);
                    w.fread_dual().clear_bit();
                    w.fread_quad().clear_bit()
                });

                self.regs().user().modify(|_, w| {
                    w.fwrite_dio().bit(address_mode == DataMode::Dual);
                    w.fwrite_qio().bit(address_mode == DataMode::Quad);
                    w.fwrite_dual().clear_bit();
                    w.fwrite_quad().clear_bit()
                });
            }
            _ => {
                // FIXME: more detailed error - Unsupported combination of data-modes
                error!("Address mode must be single bit wide or equal to the data mode");
                return Err(Error::Unsupported);
            }
        }

        Ok(())
    }

    /// Enable or disable listening for the given interrupts.
    #[cfg_attr(not(feature = "unstable"), allow(dead_code))]
    fn enable_listen(&self, interrupts: EnumSet<SpiInterrupt>, enable: bool) {
        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                self.regs().slave().modify(|_, w| {
                    for interrupt in interrupts {
                        match interrupt {
                            SpiInterrupt::TransferDone => w.trans_inten().bit(enable),
                        };
                    }
                    w
                });
            } else if #[cfg(esp32s2)] {
                self.regs().slave().modify(|_, w| {
                    for interrupt in interrupts {
                        match interrupt {
                            SpiInterrupt::TransferDone => w.int_trans_done_en().bit(enable),
                            SpiInterrupt::DmaSegmentedTransferDone => w.int_dma_seg_trans_en().bit(enable),
                        };
                    }
                    w
                });
            } else {
                self.regs().dma_int_ena().modify(|_, w| {
                    for interrupt in interrupts {
                        match interrupt {
                            SpiInterrupt::TransferDone => w.trans_done().bit(enable),
                            #[cfg(spi_master_has_dma_segmented_transfer)]
                            SpiInterrupt::DmaSegmentedTransferDone => w.dma_seg_trans_done().bit(enable),
                            #[cfg(spi_master_has_app_interrupts)]
                            SpiInterrupt::App2 => w.app2().bit(enable),
                            #[cfg(spi_master_has_app_interrupts)]
                            SpiInterrupt::App1 => w.app1().bit(enable),
                        };
                    }
                    w
                });
            }
        }
    }

    /// Gets asserted interrupts
    #[cfg_attr(not(feature = "unstable"), allow(dead_code))]
    fn interrupts(&self) -> EnumSet<SpiInterrupt> {
        let mut res = EnumSet::new();

        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                if self.regs().slave().read().trans_done().bit() {
                    res.insert(SpiInterrupt::TransferDone);
                }
            } else if #[cfg(esp32s2)] {
                if self.regs().slave().read().trans_done().bit() {
                    res.insert(SpiInterrupt::TransferDone);
                }
                if self.regs().hold().read().dma_seg_trans_done().bit() {
                    res.insert(SpiInterrupt::DmaSegmentedTransferDone);
                }
            } else {
                let ints = self.regs().dma_int_raw().read();

                if ints.trans_done().bit() {
                    res.insert(SpiInterrupt::TransferDone);
                }
                #[cfg(spi_master_has_dma_segmented_transfer)]
                if ints.dma_seg_trans_done().bit() {
                    res.insert(SpiInterrupt::DmaSegmentedTransferDone);
                }
                #[cfg(spi_master_has_app_interrupts)]
                if ints.app2().bit() {
                    res.insert(SpiInterrupt::App2);
                }
                #[cfg(spi_master_has_app_interrupts)]
                if ints.app1().bit() {
                    res.insert(SpiInterrupt::App1);
                }
            }
        }

        res
    }

    /// Resets asserted interrupts
    fn clear_interrupts(&self, interrupts: EnumSet<SpiInterrupt>) {
        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                for interrupt in interrupts {
                    match interrupt {
                        SpiInterrupt::TransferDone => {
                            self.regs().slave().modify(|_, w| w.trans_done().clear_bit());
                        }
                    }
                }
            } else if #[cfg(esp32s2)] {
                for interrupt in interrupts {
                    match interrupt {
                        SpiInterrupt::TransferDone => {
                            self.regs().slave().modify(|_, w| w.trans_done().clear_bit());
                        }

                        SpiInterrupt::DmaSegmentedTransferDone => {
                            self.regs()
                                .hold()
                                .modify(|_, w| w.dma_seg_trans_done().clear_bit());
                        }
                    }
                }
            } else {
                self.regs().dma_int_clr().write(|w| {
                    for interrupt in interrupts {
                        match interrupt {
                            SpiInterrupt::TransferDone => w.trans_done().clear_bit_by_one(),
                            #[cfg(spi_master_has_dma_segmented_transfer)]
                            SpiInterrupt::DmaSegmentedTransferDone => w.dma_seg_trans_done().clear_bit_by_one(),
                            #[cfg(spi_master_has_app_interrupts)]
                            SpiInterrupt::App2 => w.app2().clear_bit_by_one(),
                            #[cfg(spi_master_has_app_interrupts)]
                            SpiInterrupt::App1 => w.app1().clear_bit_by_one(),
                        };
                    }
                    w
                });
            }
        }
    }

    fn apply_config(&self, config: &Config) -> Result<(), ConfigError> {
        config.validate()?;
        self.ch_bus_freq(config)?;
        self.set_bit_order(config.read_bit_order, config.write_bit_order);
        self.set_data_mode(config.mode);

        #[cfg(esp32)]
        self.calculate_half_duplex_values(config);

        Ok(())
    }

    #[cfg(esp32)]
    fn calculate_half_duplex_values(&self, config: &Config) {
        let f_apb = 80_000_000;
        let source_freq_hz = match config.clock_source {
            ClockSource::Apb => f_apb,
        };

        let clock_reg = self.regs().clock().read();
        let eff_clk = if clock_reg.clk_equ_sysclk().bit_is_set() {
            f_apb
        } else {
            let pre = clock_reg.clkdiv_pre().bits() as i32 + 1;
            let n = clock_reg.clkcnt_n().bits() as i32 + 1;
            f_apb / (pre * n)
        };

        let apbclk_khz = source_freq_hz / 1000;
        // how many apb clocks a period has
        let spiclk_apb_n = source_freq_hz / eff_clk;

        // How many apb clocks the delay is, the 1 is to compensate in case
        // ``input_delay_ns`` is rounded off. Change from esp-idf: we use
        // `input_delay_ns` to also represent the GPIO matrix delay.
        let input_delay_ns = 25; // TODO: allow configuring input delay.
        let delay_apb_n = (1 + input_delay_ns) * apbclk_khz / 1000 / 1000;

        let dummy_required = delay_apb_n / spiclk_apb_n;
        let timing_miso_delay = if dummy_required > 0 {
            // due to the clock delay between master and slave, there's a range in which
            // data is random give MISO a delay if needed to make sure we
            // sample at the time MISO is stable
            Some(((dummy_required + 1) * spiclk_apb_n - delay_apb_n - 1) as u8)
        } else if delay_apb_n * 4 <= spiclk_apb_n {
            // if the dummy is not required, maybe we should also delay half a SPI clock if
            // the data comes too early
            None
        } else {
            Some(0)
        };

        self.state.esp32_hack.extra_dummy.set(dummy_required as u8);
        self.state
            .esp32_hack
            .timing_miso_delay
            .set(timing_miso_delay);
    }

    fn set_data_mode(&self, data_mode: Mode) {
        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                let pin_reg = self.regs().pin();
            } else {
                let pin_reg = self.regs().misc();
            }
        };

        pin_reg.modify(|_, w| {
            w.ck_idle_edge()
                .bit(matches!(data_mode, Mode::_2 | Mode::_3))
        });
        self.regs().user().modify(|_, w| {
            w.ck_out_edge()
                .bit(matches!(data_mode, Mode::_1 | Mode::_2))
        });
    }

    fn ch_bus_freq(&self, bus_clock_config: &Config) -> Result<(), ConfigError> {
        fn enable_clocks(_reg_block: &RegisterBlock, _enable: bool) {
            #[cfg(not(any(esp32, esp32s2)))]
            _reg_block.clk_gate().modify(|_, w| {
                w.clk_en().bit(_enable);
                w.mst_clk_active().bit(_enable);
                w.mst_clk_sel().bit(true) // TODO: support XTAL clock source
            });
        }

        // Change clock frequency
        let raw = bus_clock_config.raw_clock_reg_value()?;

        enable_clocks(self.regs(), false);
        self.regs().clock().write(|w| unsafe { w.bits(raw) });
        enable_clocks(self.regs(), true);

        Ok(())
    }

    #[cfg(not(any(esp32, esp32c3, esp32s2)))]
    fn set_bit_order(&self, read_order: BitOrder, write_order: BitOrder) {
        let read_value = match read_order {
            BitOrder::MsbFirst => 0,
            BitOrder::LsbFirst => 1,
        };
        let write_value = match write_order {
            BitOrder::MsbFirst => 0,
            BitOrder::LsbFirst => 1,
        };
        self.regs().ctrl().modify(|_, w| unsafe {
            w.rd_bit_order().bits(read_value);
            w.wr_bit_order().bits(write_value);
            w
        });
    }

    #[cfg(any(esp32, esp32c3, esp32s2))]
    fn set_bit_order(&self, read_order: BitOrder, write_order: BitOrder) {
        let read_value = match read_order {
            BitOrder::MsbFirst => false,
            BitOrder::LsbFirst => true,
        };
        let write_value = match write_order {
            BitOrder::MsbFirst => false,
            BitOrder::LsbFirst => true,
        };
        self.regs().ctrl().modify(|_, w| {
            w.rd_bit_order().bit(read_value);
            w.wr_bit_order().bit(write_value);
            w
        });
    }

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn fill_fifo(&self, chunk: &[u8]) {
        // TODO: replace with `array_chunks` and `from_le_bytes`
        let mut c_iter = chunk.chunks_exact(4);
        let mut w_iter = self.regs().w_iter();
        for c in c_iter.by_ref() {
            if let Some(w_reg) = w_iter.next() {
                let word = (c[0] as u32)
                    | ((c[1] as u32) << 8)
                    | ((c[2] as u32) << 16)
                    | ((c[3] as u32) << 24);
                w_reg.write(|w| w.buf().set(word));
            }
        }
        let rem = c_iter.remainder();
        if !rem.is_empty()
            && let Some(w_reg) = w_iter.next()
        {
            let word = match rem.len() {
                3 => (rem[0] as u32) | ((rem[1] as u32) << 8) | ((rem[2] as u32) << 16),
                2 => (rem[0] as u32) | ((rem[1] as u32) << 8),
                1 => rem[0] as u32,
                _ => unreachable!(),
            };
            w_reg.write(|w| w.buf().set(word));
        }
    }

    /// Write bytes to SPI.
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn write_one(&self, words: &[u8]) -> Result<(), Error> {
        if words.len() > FIFO_SIZE {
            return Err(Error::FifoSizeExeeded);
        }
        self.configure_datalen(0, words.len());
        self.fill_fifo(words);
        self.start_operation();
        Ok(())
    }

    /// Write bytes to SPI.
    ///
    /// This function will return before all bytes of the last chunk to transmit
    /// have been sent to the wire. If you must ensure that the whole
    /// messages was written correctly, use [`Self::flush`].
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn write(&self, words: &[u8]) -> Result<(), Error> {
        for chunk in words.chunks(FIFO_SIZE) {
            self.flush()?;
            self.write_one(chunk)?;
        }
        Ok(())
    }

    /// Write bytes to SPI.
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    async fn write_async(&self, words: &[u8]) -> Result<(), Error> {
        for chunk in words.chunks(FIFO_SIZE) {
            self.write_one(chunk)?;
            self.flush_async().await;
        }
        Ok(())
    }

    /// Read bytes from SPI.
    ///
    /// Sends out a stuffing byte for every byte to read. This function doesn't
    /// perform flushing. If you want to read the response to something you
    /// have written before, consider using [`Self::transfer`] instead.
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn read(&self, words: &mut [u8]) -> Result<(), Error> {
        let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE];

        for chunk in words.chunks_mut(FIFO_SIZE) {
            self.write_one(&empty_array[0..chunk.len()])?;
            self.flush()?;
            self.read_from_fifo(chunk)?;
        }
        Ok(())
    }

    /// Read bytes from SPI.
    ///
    /// Sends out a stuffing byte for every byte to read. If you want to read
    /// the response to something you have written before, consider using
    /// [`Self::transfer`] instead.
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    async fn read_async(&self, words: &mut [u8]) -> Result<(), Error> {
        let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE];

        for chunk in words.chunks_mut(FIFO_SIZE) {
            self.write_one(&empty_array[0..chunk.len()])?;
            self.flush_async().await;
            self.read_from_fifo(chunk)?;
        }
        Ok(())
    }

    /// Read received bytes from SPI FIFO.
    ///
    /// Copies the contents of the SPI receive FIFO into `words`. This function
    /// doesn't perform any data transfer. If you want to read the response to
    /// something you have written before, consider using [`Self::transfer`]
    /// instead.
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn read_from_fifo(&self, words: &mut [u8]) -> Result<(), Error> {
        if words.len() > FIFO_SIZE {
            return Err(Error::FifoSizeExeeded);
        }

        for (chunk, w_reg) in words.chunks_mut(4).zip(self.regs().w_iter()) {
            let reg_val = w_reg.read().bits();
            let bytes = reg_val.to_le_bytes();

            let len = chunk.len();
            chunk.copy_from_slice(&bytes[..len]);
        }

        Ok(())
    }

    fn busy(&self) -> bool {
        self.regs().cmd().read().usr().bit_is_set()
    }

    // Check if the bus is busy and if it is wait for it to be idle
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn flush_async(&self) -> impl Future<Output = ()> {
        SpiFuture { driver: self }
    }

    // Check if the bus is busy and if it is wait for it to be idle
    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn flush(&self) -> Result<(), Error> {
        while self.busy() {
            // wait for bus to be clear
        }
        Ok(())
    }

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn transfer_in_place(&self, words: &mut [u8]) -> Result<(), Error> {
        for chunk in words.chunks_mut(FIFO_SIZE) {
            self.write_one(chunk)?;
            self.flush()?;
            self.read_from_fifo(chunk)?;
        }

        Ok(())
    }

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn transfer(&self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
        let mut write_from = 0;
        let mut read_from = 0;

        loop {
            // How many bytes we write in this chunk
            let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from);
            // How many bytes we read in this chunk
            let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from);

            if (write_inc == 0) && (read_inc == 0) {
                break;
            }

            self.flush()?;

            if write_inc < read_inc {
                // Read more than we write, must pad writing part with zeros
                let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE];
                empty[0..write_inc].copy_from_slice(&write[write_from..][..write_inc]);
                self.write_one(&empty[..read_inc])?;
            } else {
                self.write_one(&write[write_from..][..write_inc])?;
            }

            if read_inc > 0 {
                self.flush()?;
                self.read_from_fifo(&mut read[read_from..][..read_inc])?;
            }

            write_from += write_inc;
            read_from += read_inc;
        }
        Ok(())
    }

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    async fn transfer_in_place_async(&self, words: &mut [u8]) -> Result<(), Error> {
        for chunk in words.chunks_mut(FIFO_SIZE) {
            // Cut the transfer short if the future is dropped. We'll block for a short
            // while to ensure the peripheral is idle.
            let cancel_on_drop = DropGuard::new((), |_| {
                self.abort_transfer();
                while self.busy() {}
            });
            let res = self.write_one(chunk);
            self.flush_async().await;
            cancel_on_drop.defuse();
            res?;

            self.read_from_fifo(chunk)?;
        }

        Ok(())
    }

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    async fn transfer_async(&self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
        let mut write_from = 0;
        let mut read_from = 0;

        loop {
            // How many bytes we write in this chunk
            let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from);
            // How many bytes we read in this chunk
            let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from);

            if (write_inc == 0) && (read_inc == 0) {
                break;
            }

            self.flush_async().await;

            if write_inc < read_inc {
                // Read more than we write, must pad writing part with zeros
                let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE];
                empty[0..write_inc].copy_from_slice(&write[write_from..][..write_inc]);
                self.write_one(&empty[..read_inc])?;
            } else {
                self.write_one(&write[write_from..][..write_inc])?;
            }

            // Preserve previous semantics - see https://github.com/esp-rs/esp-hal/issues/5257
            self.flush_async().await;
            if read_inc > 0 {
                self.read_from_fifo(&mut read[read_from..][..read_inc])?;
            }

            write_from += write_inc;
            read_from += read_inc;
        }
        Ok(())
    }

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn start_operation(&self) {
        self.update();
        self.clear_interrupts(SpiInterrupt::TransferDone.into());
        self.regs().cmd().modify(|_, w| w.usr().set_bit());
    }

    fn setup_full_duplex(&self) -> Result<(), Error> {
        self.regs().user().modify(|_, w| {
            w.usr_miso().set_bit();
            w.usr_mosi().set_bit();
            w.doutdin().set_bit();
            w.usr_dummy().clear_bit();
            w.sio().clear_bit()
        });

        self.init_spi_data_mode(
            DataMode::SingleTwoDataLines,
            DataMode::SingleTwoDataLines,
            DataMode::SingleTwoDataLines,
        )?;

        // For full-duplex, we don't need compensation according to esp-idf.
        #[cfg(esp32)]
        self.regs().ctrl2().modify(|_, w| unsafe {
            w.miso_delay_mode().bits(0);
            w.miso_delay_num().bits(0)
        });

        Ok(())
    }

    #[expect(clippy::too_many_arguments)]
    fn setup_half_duplex(
        &self,
        is_write: bool,
        cmd: Command,
        address: Address,
        dummy_idle: bool,
        dummy: u8,
        no_mosi_miso: bool,
        data_mode: DataMode,
    ) -> Result<(), Error> {
        self.init_spi_data_mode(cmd.mode(), address.mode(), data_mode)?;

        #[cfg(esp32)]
        let mut dummy = dummy;

        #[cfg(esp32)]
        self.regs().ctrl2().modify(|_, w| {
            let mut delay_mode = 0;
            let mut delay_num = 0;

            if !is_write {
                // Values are set up in apply_config
                let timing_miso_delay = self.state.esp32_hack.timing_miso_delay.get();
                let extra_dummy = self.state.esp32_hack.extra_dummy.get();
                dummy += extra_dummy;

                if let Some(delay) = timing_miso_delay {
                    delay_num = if extra_dummy > 0 { delay } else { 0 };
                } else {
                    let out_edge = self.regs().user().read().ck_out_edge().bit_is_set();
                    // SPI modes 1 and 2 need delay mode 1 according to esp-idf.
                    delay_mode = if out_edge { 1 } else { 2 };
                }
            }

            unsafe {
                w.miso_delay_mode().bits(delay_mode);
                w.miso_delay_num().bits(delay_num)
            }
        });

        let reg_block = self.regs();
        reg_block.user().modify(|_, w| {
            w.usr_miso_highpart().clear_bit();
            w.usr_mosi_highpart().clear_bit();
            // This bit tells the hardware whether we use Single or SingleTwoDataLines
            w.sio().bit(data_mode == DataMode::Single);
            w.doutdin().clear_bit();
            w.usr_miso().bit(!is_write && !no_mosi_miso);
            w.usr_mosi().bit(is_write && !no_mosi_miso);
            w.cs_hold().set_bit();
            w.usr_dummy_idle().bit(dummy_idle);
            w.usr_dummy().bit(dummy != 0);
            w.usr_addr().bit(!address.is_none());
            w.usr_command().bit(!cmd.is_none())
        });

        // FIXME why is clock config even here?
        #[cfg(not(any(esp32, esp32s2)))]
        reg_block.clk_gate().modify(|_, w| {
            w.clk_en().set_bit();
            w.mst_clk_active().set_bit();
            w.mst_clk_sel().set_bit()
        });

        #[cfg(soc_has_pcr)]
        // use default clock source
        crate::peripherals::PCR::regs()
            .spi2_clkm_conf()
            .modify(|_, w| unsafe { w.spi2_clkm_sel().bits(1) });

        #[cfg(not(esp32))]
        reg_block.misc().write(|w| unsafe { w.bits(0) });

        reg_block.slave().write(|w| unsafe { w.bits(0) });

        self.update();

        // set cmd, address, dummy cycles
        self.set_up_common_phases(cmd, address, dummy);

        Ok(())
    }

    fn set_up_common_phases(&self, cmd: Command, address: Address, dummy: u8) {
        let reg_block = self.regs();
        if !cmd.is_none() {
            reg_block.user2().modify(|_, w| unsafe {
                w.usr_command_bitlen().bits((cmd.width() - 1) as u8);
                w.usr_command_value().bits(cmd.value())
            });
        }

        if !address.is_none() {
            reg_block
                .user1()
                .modify(|_, w| unsafe { w.usr_addr_bitlen().bits((address.width() - 1) as u8) });

            let addr = address.value() << (32 - address.width());
            #[cfg(not(esp32))]
            reg_block
                .addr()
                .write(|w| unsafe { w.usr_addr_value().bits(addr) });
            #[cfg(esp32)]
            reg_block.addr().write(|w| unsafe { w.bits(addr) });
        }

        if dummy > 0 {
            reg_block
                .user1()
                .modify(|_, w| unsafe { w.usr_dummy_cyclelen().bits(dummy - 1) });
        }
    }

    fn update(&self) {
        cfg_if::cfg_if! {
            if #[cfg(not(any(esp32, esp32s2)))] {
                let reg_block = self.regs();

                reg_block.cmd().modify(|_, w| w.update().set_bit());

                while reg_block.cmd().read().update().bit_is_set() {
                    // wait
                }
            } else {
                // Doesn't seem to be needed for ESP32 and ESP32-S2
            }
        }
    }

    fn configure_datalen(&self, rx_len_bytes: usize, tx_len_bytes: usize) {
        let reg_block = self.regs();

        let rx_len = rx_len_bytes as u32 * 8;
        let tx_len = tx_len_bytes as u32 * 8;

        let rx_len = rx_len.saturating_sub(1);
        let tx_len = tx_len.saturating_sub(1);

        cfg_if::cfg_if! {
            if #[cfg(esp32)] {
                let len = rx_len.max(tx_len);
                reg_block
                    .mosi_dlen()
                    .write(|w| unsafe { w.usr_mosi_dbitlen().bits(len) });

                reg_block
                    .miso_dlen()
                    .write(|w| unsafe { w.usr_miso_dbitlen().bits(len) });
            } else if #[cfg(esp32s2)] {
                reg_block
                    .mosi_dlen()
                    .write(|w| unsafe { w.usr_mosi_dbitlen().bits(tx_len) });

                reg_block
                    .miso_dlen()
                    .write(|w| unsafe { w.usr_miso_dbitlen().bits(rx_len) });
            } else {
                reg_block
                    .ms_dlen()
                    .write(|w| unsafe { w.ms_data_bitlen().bits(rx_len.max(tx_len)) });
            }

        }
    }
}

impl PartialEq for Info {
    fn eq(&self, other: &Self) -> bool {
        core::ptr::eq(self.register_block, other.register_block)
    }
}

unsafe impl Sync for Info {}

for_each_spi_master! {
    ($peri:ident, $sys:ident, $sclk:ident [$($cs:ident),+] [$($sio:ident),*] $(, $is_qspi:tt)?) => {
        impl Instance for crate::peripherals::$peri<'_> {
            #[inline(always)]
            fn parts(&self) -> (&'static Info, &'static State) {
                #[handler]
                #[ram]
                fn irq_handler() {
                    handle_async(&INFO, &STATE)
                }

                static INFO: Info = Info {
                    register_block: crate::peripherals::$peri::regs(),
                    peripheral: crate::system::Peripheral::$sys,
                    async_handler: irq_handler,
                    sclk: OutputSignal::$sclk,
                    cs: &[$(OutputSignal::$cs),+],
                    sio_inputs: &[$(InputSignal::$sio),*],
                    sio_outputs: &[$(OutputSignal::$sio),*],
                };

                static STATE: State = State {
                    waker: AtomicWaker::new(),
                    pins: UnsafeCell::new(MaybeUninit::uninit()),

                    #[cfg(esp32)]
                    esp32_hack: Esp32Hack {
                        timing_miso_delay: Cell::new(None),
                        extra_dummy: Cell::new(0),
                    },
                };

                (&INFO, &STATE)
            }
        }

        $(
            // If the extra pins are set, implement QspiInstance
            $crate::ignore!($is_qspi);
            impl QspiInstance for crate::peripherals::$peri<'_> {}
        )?
    };
}

impl QspiInstance for AnySpi<'_> {}

#[doc(hidden)]
pub struct State {
    waker: AtomicWaker,
    pins: UnsafeCell<MaybeUninit<SpiPinGuard>>,

    #[cfg(esp32)]
    esp32_hack: Esp32Hack,
}

impl State {
    // Syntactic helper to get a mutable reference to the pin guard.
    //
    // Intended to be called in `SpiWrapper::pins` only
    //
    // # Safety
    //
    // The caller must ensure that Rust's aliasing rules are upheld.
    #[allow(
        clippy::mut_from_ref,
        reason = "Safety requirements ensure this is okay"
    )]
    unsafe fn pins(&self) -> &mut SpiPinGuard {
        unsafe { (&mut *self.pins.get()).assume_init_mut() }
    }

    unsafe fn deinit(&self) {
        unsafe {
            let mut old = self.pins.get().replace(MaybeUninit::uninit());
            old.assume_init_drop();
        }
    }
}

#[cfg(esp32)]
struct Esp32Hack {
    timing_miso_delay: Cell<Option<u8>>,
    extra_dummy: Cell<u8>,
}

unsafe impl Sync for State {}

#[ram]
fn handle_async(info: &'static Info, state: &'static State) {
    let driver = Driver { info, state };
    if driver.interrupts().contains(SpiInterrupt::TransferDone) {
        driver.enable_listen(SpiInterrupt::TransferDone.into(), false);
        state.waker.wake();
    }
}

/// SPI data mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[instability::unstable]
pub enum DataMode {
    /// 1 bit, two data lines. (MOSI, MISO)
    SingleTwoDataLines,
    /// 1 bit, 1 data line (SIO0)
    Single,
    /// 2 bits, two data lines. (SIO0, SIO1)
    Dual,
    /// 4 bit, 4 data lines. (SIO0 .. SIO3)
    Quad,
    #[cfg(spi_master_has_octal)]
    /// 8 bit, 8 data lines. (SIO0 .. SIO7)
    Octal,
}

crate::any_peripheral! {
    /// Any SPI peripheral.
    pub peripheral AnySpi<'d> {
        #[cfg(spi_master_spi2)]
        Spi2(crate::peripherals::SPI2<'d>),
        #[cfg(spi_master_spi3)]
        Spi3(crate::peripherals::SPI3<'d>),
    }
}

impl Instance for AnySpi<'_> {
    #[inline]
    fn parts(&self) -> (&'static Info, &'static State) {
        any::delegate!(self, spi => { spi.parts() })
    }
}

impl AnySpi<'_> {
    fn bind_peri_interrupt(&self, handler: InterruptHandler) {
        any::delegate!(self, spi => { spi.bind_peri_interrupt(handler) })
    }

    fn disable_peri_interrupt_on_all_cores(&self) {
        any::delegate!(self, spi => { spi.disable_peri_interrupt_on_all_cores() })
    }

    fn set_interrupt_handler(&self, handler: InterruptHandler) {
        self.disable_peri_interrupt_on_all_cores();
        self.bind_peri_interrupt(handler);
    }
}

struct SpiFuture<'a> {
    driver: &'a Driver,
}

impl SpiFuture<'_> {
    const EVENTS: EnumSet<SpiInterrupt> = enum_set!(SpiInterrupt::TransferDone);
}

impl Future for SpiFuture<'_> {
    type Output = ();

    #[cfg_attr(place_spi_master_driver_in_ram, ram)]
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.driver.busy() {
            self.driver.state.waker.register(cx.waker());
            self.driver.enable_listen(Self::EVENTS, true);

            Poll::Pending
        } else {
            self.driver.clear_interrupts(Self::EVENTS);
            Poll::Ready(())
        }
    }
}

impl Drop for SpiFuture<'_> {
    fn drop(&mut self) {
        self.driver.enable_listen(Self::EVENTS, false);
    }
}
