//! SPI Half Duplex Write Test

//% CHIPS: esp32 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable

#![no_std]
#![no_main]

use esp_hal::{
    Blocking,
    dma::{DmaRxBuf, DmaTxBuf},
    dma_buffers,
    gpio::interconnect::InputSignal,
    pcnt::{Pcnt, channel::EdgeMode, unit::Unit},
    spi::{
        DataMode,
        Mode,
        master::{Address, Command, Config, Spi, SpiDma},
    },
    time::Rate,
};
use hil_test as _;

esp_bootloader_esp_idf::esp_app_desc!();

struct Context {
    spi: SpiDma<'static, Blocking>,
    pcnt_unit: Unit<'static, 0>,
    pcnt_source: InputSignal<'static>,
}

fn perform_spi_writes_are_correctly_by_pcnt(ctx: Context, mode: DataMode) {
    const DMA_BUFFER_SIZE: usize = 4;

    let (_, _, buffer, descriptors) = dma_buffers!(0, DMA_BUFFER_SIZE);
    let mut dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap();

    let unit = ctx.pcnt_unit;
    let mut spi = ctx.spi;

    unit.channel0.set_edge_signal(ctx.pcnt_source);
    unit.channel0
        .set_input_mode(EdgeMode::Hold, EdgeMode::Increment);

    // Fill the buffer where each byte has 3 pos edges.
    dma_tx_buf.fill(&[0b0110_1010; DMA_BUFFER_SIZE]);

    let transfer = spi
        .half_duplex_write(
            mode,
            Command::None,
            Address::None,
            0,
            dma_tx_buf.len(),
            dma_tx_buf,
        )
        .map_err(|e| e.0)
        .unwrap();
    (spi, dma_tx_buf) = transfer.wait();

    assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _);

    let transfer = spi
        .half_duplex_write(
            mode,
            Command::None,
            Address::None,
            0,
            dma_tx_buf.len(),
            dma_tx_buf,
        )
        .map_err(|e| e.0)
        .unwrap();
    // dropping SPI would make us see an additional edge - so let's keep SPI alive
    let (_spi, _) = transfer.wait();

    assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _);
}

fn perform_spidmabus_writes_are_correctly_by_pcnt(ctx: Context, mode: DataMode) {
    const DMA_BUFFER_SIZE: usize = 4;

    let (rx, rxd, buffer, descriptors) = dma_buffers!(4, DMA_BUFFER_SIZE);
    let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap();
    let dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap();

    let unit = ctx.pcnt_unit;
    let mut spi = ctx.spi.with_buffers(dma_rx_buf, dma_tx_buf);

    unit.channel0.set_edge_signal(ctx.pcnt_source);
    unit.channel0
        .set_input_mode(EdgeMode::Hold, EdgeMode::Increment);

    let buffer = [0b0110_1010; DMA_BUFFER_SIZE];
    // Write the buffer where each byte has 3 pos edges.
    spi.half_duplex_write(mode, Command::None, Address::None, 0, &buffer)
        .unwrap();

    assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _);

    spi.half_duplex_write(mode, Command::None, Address::None, 0, &buffer)
        .unwrap();

    assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _);
}

#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
    use super::*;

    #[init]
    fn init() -> Context {
        let peripherals = esp_hal::init(esp_hal::Config::default());

        let sclk = peripherals.GPIO0;
        let (mosi, _) = hil_test::common_test_pins!(peripherals);

        let pcnt = Pcnt::new(peripherals.PCNT);

        cfg_if::cfg_if! {
            if #[cfg(pdma)] {
                let dma_channel = peripherals.DMA_SPI2;
            } else {
                let dma_channel = peripherals.DMA_CH0;
            }
        }

        let (mosi_loopback, mosi) = unsafe { mosi.split() };

        let spi = Spi::new(
            peripherals.SPI2,
            Config::default()
                .with_frequency(Rate::from_khz(100))
                .with_mode(Mode::_0),
        )
        .unwrap()
        .with_sck(sclk)
        .with_sio0(mosi)
        .with_dma(dma_channel);

        Context {
            spi,
            pcnt_unit: pcnt.unit0,
            pcnt_source: mosi_loopback,
        }
    }

    #[test]
    fn test_spi_writes_are_correctly_by_pcnt(ctx: Context) {
        super::perform_spi_writes_are_correctly_by_pcnt(ctx, DataMode::SingleTwoDataLines);
    }

    #[test]
    fn test_spidmabus_writes_are_correctly_by_pcnt(ctx: Context) {
        super::perform_spidmabus_writes_are_correctly_by_pcnt(ctx, DataMode::SingleTwoDataLines);
    }

    #[test]
    fn test_spi_writes_are_correctly_by_pcnt_tree_wire(ctx: Context) {
        super::perform_spi_writes_are_correctly_by_pcnt(ctx, DataMode::SingleTwoDataLines);
    }

    #[test]
    fn test_spidmabus_writes_are_correctly_by_pcnt_tree_wire(ctx: Context) {
        super::perform_spidmabus_writes_are_correctly_by_pcnt(ctx, DataMode::SingleTwoDataLines);
    }

    #[test]
    fn test_spi_writes_are_correctly_by_pcnt_four_wire(ctx: Context) {
        super::perform_spi_writes_are_correctly_by_pcnt(ctx, DataMode::Single);
    }

    #[test]
    fn test_spidmabus_writes_are_correctly_by_pcnt_four_wire(ctx: Context) {
        super::perform_spidmabus_writes_are_correctly_by_pcnt(ctx, DataMode::Single);
    }
}
