use alloc::sync::Arc;
use device_api::device::{Device, DeviceInitContext};
use device_tree::driver::{device_tree_driver, Node, ProbeContext};
use libk::{
    error::Error,
    fs::sysfs::{self, nodes::SysfsRtcNode},
};
use libk_mm::device::DeviceMemoryIo;
use libk_util::sync::IrqSafeSpinlock;
use tock_registers::{
    interfaces::Readable,
    register_structs,
    registers::{ReadOnly, ReadWrite, WriteOnly},
};

register_structs! {
    #[allow(non_snake_case)]
    Regs {
        (0x00 => RTCDR: ReadOnly<u32>),
        (0x04 => RTCMR: ReadWrite<u32>),
        (0x08 => RTCLR: ReadWrite<u32>),
        (0x0C => RTCCR: ReadWrite<u32>),
        (0x10 => RTCIMSC: ReadWrite<u32>),
        (0x14 => RTCRIS: ReadOnly<u32>),
        (0x18 => RTCMIS: ReadOnly<u32>),
        (0x1C => RTCICR: WriteOnly<u32>),
        (0x20 => @END),
    }
}

struct Pl031 {
    regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
}

impl SysfsRtcNode for Pl031 {
    fn read(&self) -> Result<u64, Error> {
        let regs = self.regs.lock();
        Ok(regs.RTCDR.get() as _)
    }
}

impl Device for Pl031 {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        sysfs::nodes::add_rtc_node(self.clone());
        Ok(())
    }

    fn display_name(&self) -> &str {
        "ARM PL031 RTC"
    }
}

device_tree_driver! {
    compatible: ["arm,pl031"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let base = node.map_base(context, 0)?;

            let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;

            let rtc = Arc::new(Pl031 { regs: IrqSafeSpinlock::new(regs) });

            Some(rtc)
        }
    }
}
