//! arm 系统时钟驱动
//!
//! 该驱动包含系统时钟源驱动和系统时钟事件驱动

use moa_bitops::make_64bit_mask;
use moa_bsp_define::{
    clocksource::{ClockImpl, SetNextEventFunc, SetStateStoppedFunc},
    irqchip::IrqType,
};

use crate::{
    irq::{
        IrqDevData, IrqHandlerRet,
        irqctl::{irq_enable, irq_set_type},
        request_percpu_irq,
    },
    processor::isb,
    time::{
        clockevents::clockevent_handler,
        clocksource::{ClockSource, clocksource_init},
    },
};

#[inline(always)]
fn p_read() -> u64 {
    let tick: u64;
    isb();
    unsafe {
        core::arch::asm!("mrs {0}, cntpct_el0", out(reg) tick);
    }
    tick
}

#[inline(always)]
fn v_read() -> u64 {
    let tick: u64;
    isb();
    unsafe {
        core::arch::asm!("mrs {0}, cntvct_el0", out(reg) tick);
    }
    tick
}

/// arm 系统时钟类型
#[derive(PartialEq, PartialOrd)]
pub enum ArmClockType {
    /// 物理时钟
    Phys,
    /// 虚拟时钟
    Virt,
}

/// arm 系统时钟
pub struct ArmClock {
    clktype: ArmClockType,
    irqtype: IrqType,
    hwirq: usize,
}

impl ArmClock {
    /// 创建 arm 系统时钟
    pub const fn create(clktype: ArmClockType, irqtype: IrqType, hwirq: usize) -> Self {
        Self { clktype, irqtype, hwirq }
    }

    fn timer_setup(&self) {
        let timer_shutdown = self.get_set_state_stopped();
        timer_shutdown();

        let mut cntkctl: u32;
        unsafe {
            core::arch::asm!(
                "mrs {0:x}, cntkctl_el1",
                out(reg) cntkctl
            );
        }
        // 配置 user 访问权限
        cntkctl &= !(ARCH_TIMER_USR_PT_ACCESS_EN
            | ARCH_TIMER_USR_VT_ACCESS_EN
            | ARCH_TIMER_USR_VCT_ACCESS_EN
            | ARCH_TIMER_VIRT_EVT_EN
            | ARCH_TIMER_USR_PCT_ACCESS_EN);
        cntkctl |= ARCH_TIMER_USR_VCT_ACCESS_EN;
        unsafe {
            core::arch::asm!(
                "msr cntkctl_el1, {0:x}",
                in(reg) cntkctl
            );
        }
        isb();
    }
}

const ARCH_TIMER_USR_PCT_ACCESS_EN: u32 = 1 << 0;
const ARCH_TIMER_USR_VCT_ACCESS_EN: u32 = 1 << 1;
const ARCH_TIMER_VIRT_EVT_EN: u32 = 1 << 2;
const ARCH_TIMER_USR_VT_ACCESS_EN: u32 = 1 << 8;
const ARCH_TIMER_USR_PT_ACCESS_EN: u32 = 1 << 9;

impl ClockImpl for ArmClock {
    fn init(&self) {
        let rate = self.rate();
        let mask = make_64bit_mask(0, 56);
        let name;
        let read: fn() -> u64;
        if self.clktype == ArmClockType::Phys {
            name = "arm_arch_timer(phys)";
            read = p_read;
        } else {
            name = "arm_arch_timer(virt)";
            read = v_read;
        }

        let clocksource = ClockSource::create(read, mask, name);
        clocksource_init(clocksource, rate);

        let f = if self.clktype == ArmClockType::Phys {
            arch_timer_handler_phys
        } else {
            arch_timer_handler_virt
        };
        request_percpu_irq(self.hwirq as u32, self.irqtype, f, None);

        self.timer_setup();
    }

    fn init_cpu(&self) {
        self.timer_setup();
        irq_set_type(self.hwirq as u32, self.irqtype).unwrap();
        irq_enable(self.hwirq as u32);
    }

    fn get_set_next_event(&self) -> SetNextEventFunc {
        if self.clktype == ArmClockType::Phys {
            arch_timer_set_next_event_phys
        } else {
            arch_timer_set_next_event_virt
        }
    }

    fn get_set_state_stopped(&self) -> SetStateStoppedFunc {
        if self.clktype == ArmClockType::Phys {
            arch_timer_shutdown_phys
        } else {
            arch_timer_shutdown_virt
        }
    }

    #[inline(always)]
    fn rate(&self) -> usize {
        let rate: usize;
        unsafe {
            core::arch::asm!(
                "mrs {0}, cntfrq_el0",
                out(reg) rate
            );
        }
        rate
    }

    #[inline(always)]
    fn min_delta(&self) -> u64 {
        0xf
    }

    #[inline(always)]
    fn max_delta(&self) -> u64 {
        0x7fffffff
    }
}

#[derive(Clone, Copy)]
enum ArchTimerReg {
    Ctrl,
    Tval,
}

#[inline(always)]
fn arch_timer_reg_read(access: TimerAccess, reg: ArchTimerReg) -> u32 {
    unsafe {
        let val;
        match access {
            TimerAccess::PhysAccess => match reg {
                ArchTimerReg::Ctrl => {
                    core::arch::asm!(
                        "mrs {0:x}, cntp_ctl_el0",
                        out(reg) val
                    );
                    val
                },
                ArchTimerReg::Tval => {
                    core::arch::asm!(
                        "mrs {0:x}, cntp_tval_el0",
                        out(reg) val
                    );
                    val
                },
            },
            TimerAccess::VirtAccess => match reg {
                ArchTimerReg::Ctrl => {
                    core::arch::asm!(
                        "mrs {0:x}, cntv_ctl_el0",
                        out(reg) val
                    );
                    val
                },
                ArchTimerReg::Tval => {
                    core::arch::asm!(
                        "mrs {0:x}, cntv_tval_el0",
                        out(reg) val
                    );
                    val
                },
            },
        }
    }
}

#[inline(always)]
fn arch_timer_reg_write(access: TimerAccess, reg: ArchTimerReg, val: u32) {
    unsafe {
        match access {
            TimerAccess::PhysAccess => match reg {
                ArchTimerReg::Ctrl => {
                    core::arch::asm!(
                        "msr cntp_ctl_el0, {0:x}",
                        in(reg) val
                    );
                },
                ArchTimerReg::Tval => {
                    core::arch::asm!(
                        "msr cntp_tval_el0, {0:x}",
                        in(reg) val
                    );
                },
            },
            TimerAccess::VirtAccess => match reg {
                ArchTimerReg::Ctrl => {
                    core::arch::asm!(
                        "msr cntv_ctl_el0, {0:x}",
                        in(reg) val
                    );
                },
                ArchTimerReg::Tval => {
                    core::arch::asm!(
                        "msr cntv_tval_el0, {0:x}",
                        in(reg) val
                    );
                },
            },
        }
        isb();
    }
}

#[derive(Clone, Copy)]
enum TimerAccess {
    PhysAccess,
    VirtAccess,
}

const ARCH_TIMER_CTRL_ENABLE: u32 = 1 << 0;
const ARCH_TIMER_CTRL_IT_MASK: u32 = 1 << 1;
const ARCH_TIMER_CTRL_IT_STAT: u32 = 1 << 2;

#[inline(always)]
fn timer_handler(access: TimerAccess) -> IrqHandlerRet {
    let mut ctrl = arch_timer_reg_read(access, ArchTimerReg::Ctrl);
    if ctrl & ARCH_TIMER_CTRL_IT_STAT != 0 {
        ctrl |= ARCH_TIMER_CTRL_IT_MASK;
        arch_timer_reg_write(access, ArchTimerReg::Ctrl, ctrl);
        clockevent_handler();
    }
    IrqHandlerRet::IrqHandled
}

#[allow(clippy::ref_option)]
fn arch_timer_handler_virt(_: u32, _: &Option<IrqDevData>) -> IrqHandlerRet {
    timer_handler(TimerAccess::VirtAccess)
}

#[allow(clippy::ref_option)]
fn arch_timer_handler_phys(_: u32, _: &Option<IrqDevData>) -> IrqHandlerRet {
    timer_handler(TimerAccess::PhysAccess)
}

#[inline(always)]
fn set_next_event(access: TimerAccess, evt: u64) {
    let mut ctrl = arch_timer_reg_read(access, ArchTimerReg::Ctrl);
    ctrl |= ARCH_TIMER_CTRL_ENABLE;
    ctrl &= !ARCH_TIMER_CTRL_IT_MASK;
    arch_timer_reg_write(access, ArchTimerReg::Tval, evt as u32);
    arch_timer_reg_write(access, ArchTimerReg::Ctrl, ctrl);
}

fn arch_timer_set_next_event_phys(evt: u64) -> bool {
    set_next_event(TimerAccess::PhysAccess, evt);
    true
}

fn arch_timer_set_next_event_virt(evt: u64) -> bool {
    set_next_event(TimerAccess::VirtAccess, evt);
    true
}

#[inline(always)]
fn timer_shutdown(access: TimerAccess) {
    let mut ctrl = arch_timer_reg_read(access, ArchTimerReg::Ctrl);
    ctrl &= !ARCH_TIMER_CTRL_ENABLE;
    arch_timer_reg_write(access, ArchTimerReg::Ctrl, ctrl);
}

fn arch_timer_shutdown_phys() {
    timer_shutdown(TimerAccess::PhysAccess);
}

fn arch_timer_shutdown_virt() {
    timer_shutdown(TimerAccess::VirtAccess);
}
