//! System controller/regmap drivers
use alloc::{string::String, sync::Arc};
use device_api::device::Device;
use libk::error::Error;
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIoMut};
use libk_util::sync::IrqSafeSpinlock;

use crate::driver::{device_tree_driver, Node, ProbeContext};

/// System controller/register map interface
pub trait DeviceTreeSyscon: Device {
    /// Reads a register
    fn read_register(&self, address: usize) -> Result<u32, Error>;
    /// Writes a register
    fn write_register(&self, address: usize, val: u32) -> Result<(), Error>;
    /// Modifies a register, clearing the `clear` bits and setting the `set` bits.
    fn modify_register(&self, address: usize, clear: u32, set: u32) -> Result<(), Error>;
}

/// Simple syscon node driver
pub struct SimpleSyscon {
    regs: IrqSafeSpinlock<DeviceMemoryIoMut<'static, [u32]>>,
    regs_len: usize,
    name: String,
}

unsafe impl Send for SimpleSyscon {}
unsafe impl Sync for SimpleSyscon {}

impl SimpleSyscon {
    fn validate_address(&self, address: usize) -> Result<(), Error> {
        if address + 4 > self.regs_len || address % 4 != 0 {
            log::error!("{}: invalid register access {:#x}", self.name, address);
            Err(Error::InvalidArgument)
        } else {
            Ok(())
        }
    }
}

impl Device for SimpleSyscon {
    fn display_name(&self) -> &str {
        "syscon"
    }
}

impl DeviceTreeSyscon for SimpleSyscon {
    fn read_register(&self, address: usize) -> Result<u32, Error> {
        self.validate_address(address)?;

        let lock = self.regs.lock();
        let ptr = &raw const lock[address / 4];

        Ok(unsafe { ptr.read_volatile() })
    }

    fn write_register(&self, address: usize, val: u32) -> Result<(), Error> {
        self.validate_address(address)?;

        let mut lock = self.regs.lock();
        let ptr = &raw mut lock[address / 4];

        Ok(unsafe { ptr.write_volatile(val) })
    }

    fn modify_register(&self, address: usize, clear: u32, set: u32) -> Result<(), Error> {
        self.validate_address(address)?;

        let mut lock = self.regs.lock();
        let ptr = &raw mut lock[address / 4];

        unsafe {
            let mut val = ptr.read_volatile();
            val &= !clear;
            val |= set;
            ptr.write_volatile(val);
        }

        Ok(())
    }
}

device_tree_driver! {
    compatible: ["syscon"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let range = node.map_range(context, 0)?;
            let base = PhysicalAddress::from_u64(range.start);
            let len = (range.end - range.start) as usize;
            let name = if let Some(name) = node.name() {
                name.into()
            } else {
                alloc::format!("syscon@{base:x}")
            };

            let regs = unsafe { DeviceMemoryIoMut::map_slice(base, len / 4, Default::default()) }
                .inspect_err(|e| log::error!("{name}: map error: {e:?}"))
                .ok()?;

            log::info!("{name}: probed");

            let syscon = Arc::new(SimpleSyscon {
                regs: IrqSafeSpinlock::new(regs),
                regs_len: len,
                name,
            });

            node.make_system_controller(syscon.clone());

            Some(syscon)
        }
    }
}
