//! This example shows how to read from and write to PIO using DMA.
//!
//! If a LED is connected to that pin, like on a Pico board, it will continuously output "HELLO
//! WORLD" in morse code. The example also tries to read the data back. If reading the data fails,
//! the message will only be shown once, and then the LED remains dark.
//!
//! See the top-level `README.md` file for Copyright and licence details.
#![no_std]
#![no_main]

use cortex_m::singleton;
use cortex_m_rt::entry;
use hal::dma::{double_buffer, single_buffer, DMAExt};
use hal::gpio::{FunctionPio0, Pin};
use hal::pac;
use hal::pio::PIOExt;
use hal::sio::Sio;
use panic_halt as _;
use rp2040_hal as hal;

#[link_section = ".boot2"]
#[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H;

#[entry]
fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap();

    let sio = Sio::new(pac.SIO);
    let pins = hal::gpio::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    // configure LED pin for Pio0.
    let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function();
    // PIN id for use inside of PIO
    let led_pin_id = led.id().num;

    // HELLO WORLD in morse code:
    // .... . .-.. .-.. --- / .-- --- .-. .-.. -..
    #[allow(clippy::unusual_byte_groupings)]
    let message = [
        0b10101010_00100010_11101010_00101110,
        0b10100011_10111011_10000000_10111011,
        0b10001110_11101110_00101110_10001011,
        0b10101000_11101010_00000000_00000000,
    ];

    // Define a PIO program which reads data from the TX FIFO bit by bit, configures the LED
    // according to the data, and then writes the data back to the RX FIFO.
    let program = pio_proc::pio_asm!(
        ".wrap_target",
        "    out x, 1",
        "    mov pins, x",
        "    in x, 1 [13]",
        ".wrap"
    );

    // Initialize and start PIO
    let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
    let installed = pio.install(&program.program).unwrap();
    let (mut sm, rx, tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
        .out_pins(led_pin_id, 1)
        .clock_divisor_fixed_point(0, 0) // as slow as possible (0 is interpreted as 65536)
        .autopull(true)
        .autopush(true)
        .build(sm0);
    // The GPIO pin needs to be configured as an output.
    sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]);
    sm.start();

    let dma = pac.DMA.split(&mut pac.RESETS);

    // Transfer a single message via DMA.
    let tx_buf = singleton!(: [u32; 4] = message).unwrap();
    let rx_buf = singleton!(: [u32; 4] = [0; 4]).unwrap();
    let tx_transfer = single_buffer::Config::new(dma.ch0, tx_buf, tx).start();
    let rx_transfer = single_buffer::Config::new(dma.ch1, rx, rx_buf).start();
    let (ch0, tx_buf, tx) = tx_transfer.wait();
    let (ch1, rx, rx_buf) = rx_transfer.wait();
    for i in 0..rx_buf.len() {
        if rx_buf[i] != tx_buf[i] {
            // The data did not match, abort.
            #[allow(clippy::empty_loop)]
            loop {}
        }
    }

    // Chain some buffers together for continuous transfers
    let tx_buf2 = singleton!(: [u32; 4] = message).unwrap();
    let rx_buf2 = singleton!(: [u32; 4] = [0; 4]).unwrap();
    let tx_transfer = double_buffer::Config::new((ch0, ch1), tx_buf, tx).start();
    let mut tx_transfer = tx_transfer.read_next(tx_buf2);
    let rx_transfer = double_buffer::Config::new((dma.ch2, dma.ch3), rx, rx_buf).start();
    let mut rx_transfer = rx_transfer.write_next(rx_buf2);
    loop {
        // When a transfer is done we immediately enqueue the buffers again.
        if tx_transfer.is_done() {
            let (tx_buf, next_tx_transfer) = tx_transfer.wait();
            tx_transfer = next_tx_transfer.read_next(tx_buf);
        }
        if rx_transfer.is_done() {
            let (rx_buf, next_rx_transfer) = rx_transfer.wait();
            for i in 0..rx_buf.len() {
                if rx_buf[i] != message[i] {
                    // The data did not match, abort.
                    #[allow(clippy::empty_loop)]
                    loop {}
                }
            }
            rx_transfer = next_rx_transfer.write_next(rx_buf);
        }
    }
}
