//! Allwinner D1 ROM runtime.
//!
//! # Usage
//!
//! Here's an sample usage of this crate:
//!
//! ```no_run
//! use d1_rom_rt::{entry, Parameters, Handover};
//!
//! #[entry]
//! fn main(params: Parameters) -> Handover {
//!     /* code */
//! }
//! ```
#![feature(naked_functions, asm_const)]
#![no_std]

#[cfg(any(feature = "nezha", feature = "lichee"))]
mod mctl;
#[cfg(any(feature = "nezha", feature = "lichee"))]
/// Dram initializing function.
pub use mctl::init as dram_init;

use base_address::Static;
pub use d1_rom_rt_macros::entry;

#[cfg(feature = "log")]
use aw_soc::uart::{self, Parity, Serial, StopBits, WordLength};
use aw_soc::{ccu::Clocks, time::U32Ext, Pins, CCU, COM, PLIC, SPI, UART};
use core::arch::asm;

/// ROM runtime peripheral ownership and configurations.
pub struct Parameters {
    pub memory_meta: &'static mut Meta,
    #[cfg(not(feature = "log"))]
    pub gpio: Pins<Static<0x02000000>>,
    #[cfg(not(feature = "log"))]
    pub uart0: UART<Static<0x02500000>>,
    pub spi0: SPI<Static<0x04025000>>,
    pub com: COM<Static<0x03102000>>,
    pub ccu: CCU<Static<0x02001000>>,
    pub plic: PLIC<Static<0x10000000>>,
    pub clocks: Clocks,
}

/// Recycled ownership from `Parameters`.
pub struct Handover {}

impl From<Parameters> for Handover {
    #[inline]
    fn from(src: Parameters) -> Self {
        let _ = src;
        Handover {}
    }
}

#[doc(hidden)]
pub struct Meta {
    pub from_flash: bool,
    pub see: u32,
    pub kernel: u32,
    pub dtb: u32,
}

#[link_section = ".head.meta"]
static mut MEMORY_META: Meta = Meta {
    from_flash: false,
    see: 0,
    kernel: 0,
    dtb: 0,
};

/// eGON.BT0 identifying structure.
#[repr(C)]
pub struct EgonHead {
    magic: [u8; 8],
    pub checksum: u32,
    pub length: u32,
    _head_size: u32,
    fel_script_address: u32,
    fel_uenv_length: u32,
    dt_name_offset: u32,
    dram_size: u32,
    boot_media: u32,
    string_pool: [u32; 13],
}

/// Jump over head data to executable code.
///
/// # Safety
///
/// Naked function.
///
/// NOTE: `mxstatus` is a custom T-Head register. Do not confuse with `mstatus`.
/// It allows for configuring special eXtensions. See further below for details.
#[naked]
#[link_section = ".text.entry"]
unsafe extern "C" fn start() -> ! {
    const STACK_SIZE: usize = 1024;
    #[link_section = ".bss.uninit"]
    static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
    asm!(
        // Enable T-Head ISA extension
        "li t1, 1 << 22",
        "csrs 0x7C0, t1",
        // Invalidate instruction and data cache, branch history table
        // and branch target buffer table
        "li t1, 0x30013",
        "csrs 0x7C2, t1",
        // 关中断
        "csrw mie, zero",
        "la  sp, {stack}
        li   t0, {stack_size}
        add  sp, sp, t0",
        // 清空bss
        "la  t1, sbss
        la   t2, ebss
    1:  bgeu t1, t2, 1f
        sd   zero, 0(t1)
        addi t1, t1, 8
        j    1b
    1:  ",
        // 启动！
        "call {main}
    1:  wfi
        j 1b",
        stack      =   sym STACK,
        stack_size = const STACK_SIZE,
        main       =   sym wrap_main,
        options(noreturn)
    )
}

#[rustfmt::skip]
extern "Rust" {
    // This symbol is generated by `#[rom_rt::entry]` macro
    fn main(param: Parameters) -> Handover;
}

fn wrap_main() {
    let clocks = Clocks {
        psi: 600_000_000.hz(),
        apb1: 24_000_000.hz(),
    };
    let gpio: Pins<Static<0x02000000>> = unsafe { core::mem::transmute(()) };
    let ccu: CCU<Static<0x02001000>> = unsafe { CCU::steal_static() };
    let uart0: UART<Static<0x02500000>> = unsafe { UART::steal_static() };
    let spi0: SPI<Static<0x04025000>> = unsafe { SPI::steal_static() };
    let plic: PLIC<Static<0x10000000>> = unsafe { PLIC::steal_static() };
    #[cfg(feature = "log")]
    let config = uart::Config {
        baudrate: 115200.bps(),
        wordlength: WordLength::Eight,
        parity: Parity::None,
        stopbits: StopBits::One,
    };
    #[cfg(feature = "log")]
    let serial = Serial::new(
        uart0,
        (gpio.pb8.into_function::<6>(), gpio.pb9.into_function::<6>()),
        config,
        &clocks,
        &ccu,
    );
    let memory_meta = unsafe { &mut MEMORY_META };
    let com: COM<Static<0x03102000>> = unsafe { COM::steal_static() };

    let params = Parameters {
        memory_meta,
        #[cfg(not(feature = "log"))]
        gpio,
        #[cfg(not(feature = "log"))]
        uart0,
        spi0,
        com,
        ccu,
        plic,
        clocks,
    };
    #[cfg(feature = "log")]
    log::set_logger(serial);

    let _ = unsafe { main(params) };

    // the actual Handover is dropped to ensure ownership is returned
}

#[cfg(feature = "log")]
#[doc(hidden)]
pub mod log {
    use aw_soc::gpio::{Function, Pin};
    use base_address::Static;
    use embedded_hal::serial::Write;
    use spin::{Mutex, Once};

    pub static LOGGER: Once<LockedLogger> = Once::new();

    pub type SERIAL = aw_soc::uart::Serial<
        Static<0x02500000>,
        0,
        (
            Pin<Static<0x02000000>, 'B', 8, Function<6>>,
            Pin<Static<0x02000000>, 'B', 9, Function<6>>,
        ),
    >;

    pub struct S(SERIAL);

    pub struct LockedLogger {
        #[allow(unused)]
        pub inner: Mutex<S>,
    }

    impl ufmt::uWrite for S {
        type Error = embedded_hal::serial::ErrorKind;
        #[inline]
        fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
            self.0.write(s.as_bytes())?;
            self.0.flush()?;
            Ok(())
        }
    }

    #[inline]
    pub fn set_logger(serial: SERIAL) {
        LOGGER.call_once(|| LockedLogger {
            inner: Mutex::new(S(serial)),
        });
    }
}

#[cfg(feature = "log")]
#[doc(hidden)]
pub extern crate ufmt;

#[cfg(feature = "log")]
#[macro_export(local_inner_macros)]
macro_rules! print {
    ($($arg:tt)*) => ({
        pub use $crate::ufmt as ufmt;
        let mut logger = $crate::log::LOGGER.wait().inner.lock();
        let ans = ufmt::uwrite!(logger, $($arg)*);
        drop(logger);
        ans
    });
}

#[cfg(feature = "log")]
#[macro_export(local_inner_macros)]
macro_rules! println {
    () => ($crate::print!("\r\n"));
    ($fmt: literal $(, $($arg: tt)+)?) => ({
        pub use $crate::ufmt as ufmt;
        let mut logger = $crate::log::LOGGER.wait().inner.lock();
        let ans = ufmt::uwrite!(logger, $fmt $(, $($arg)+)?);
        drop(logger);
        let _ = $crate::print!("\r\n");
        ans
    });
}

#[no_mangle]
#[link_section = ".head.egon"]
static EGON_HEAD: EgonHead = EgonHead {
    magic: *b"eGON.BT0",
    checksum: 0x5F0A6C39, // real checksum filled by blob generator
    length: 0x8000,
    _head_size: 0,
    fel_script_address: 0,
    fel_uenv_length: 0,
    dt_name_offset: 0,
    dram_size: 0,
    boot_media: 0,
    string_pool: [0; 13],
};

core::arch::global_asm! {
    ".section .text.head",
    "head_jump:",
    "j  {}",
    sym start,
}
