//---------------------------------------------------------------------------
//
//	SCSI Target Emulator PiSCSI
//	for Raspberry Pi
//
//  Copyright (C) 2022 akuker
//
//	[ High resolution timer for the Allwinner series of SoC's]
//
//---------------------------------------------------------------------------

#include "hal/systimer_allwinner.h"
#include <sys/mman.h>

#include "hal/gpiobus.h"

#include "shared/log.h"

const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem";

//---------------------------------------------------------------------------
//
//	Initialize the system timer
//
//---------------------------------------------------------------------------
void SysTimer_AllWinner::Init()
{
    LOGTRACE("%s", __PRETTY_FUNCTION__)

    int fd;

    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        LOGERROR("I can't open /dev/mem. Are you running as root?")
        exit(-1);
    }

    hsitimer_regs =
        (sun8i_hsitimer_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, hs_timer_base_address);

    if (hsitimer_regs == MAP_FAILED) {
        LOGERROR("Unable to map high speed timer registers. Are you running as root?")
    }

    sysbus_regs = (struct sun8i_sysbus_registers *)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
                                                        system_bus_base_address);

    if (sysbus_regs == MAP_FAILED) {
        LOGERROR("Unable to map system bus registers. Are you running as root?")
    }

    enable_hs_timer();
}

void SysTimer_AllWinner::enable_hs_timer()
{
    // By default, the HSTimer clock gating is masked. When it is necessary to use
    // the HSTimer, its clock gating should be opened in BUS Clock Gating Register 0
    // and then de-assert the software reset in BUS Software Reset Register 0 on the
    // CCU module. If it is not needed to use the HSTimer, both the gating bit and
    // the software reset bit should be set 0.

    LOGTRACE("%s [Before Enable] CLK GATE: %08X  SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0,
             sysbus_regs->bus_soft_rst_reg0)

    sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 | (1 << BUS_CLK_GATING_REG0_HSTMR);
    sysbus_regs->bus_soft_rst_reg0   = sysbus_regs->bus_soft_rst_reg0 | (1 << BUS_SOFT_RST_REG0_HSTMR);
    LOGTRACE("%s [After Enable] CLK GATE: %08X  SOFT RST: %08X", __PRETTY_FUNCTION__, sysbus_regs->bus_clk_gating_reg0,
             sysbus_regs->bus_soft_rst_reg0)

    // Set interval value to the maximum value. (its a 52 bit register)
    hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF)
    hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX;

    // Select prescale value of 1, continuouse mode
    hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1;

    // Set reload bit
    hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD;

    // Enable HSTimer
    hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN;
}

// TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to
// eat up a little extra power if we leave it running.
void SysTimer_AllWinner::disable_hs_timer()
{
    LOGTRACE("%s", __PRETTY_FUNCTION__)

    LOGINFO("[Before Disable] CLK GATE: %08X  SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0,
            sysbus_regs->bus_soft_rst_reg0)

    sysbus_regs->bus_clk_gating_reg0 = sysbus_regs->bus_clk_gating_reg0 & ~(1 << BUS_CLK_GATING_REG0_HSTMR);
    sysbus_regs->bus_soft_rst_reg0   = sysbus_regs->bus_soft_rst_reg0 & ~(1 << BUS_SOFT_RST_REG0_HSTMR);

    LOGINFO("[After Disable] CLK GATE: %08X  SOFT RST: %08X", sysbus_regs->bus_clk_gating_reg0,
            sysbus_regs->bus_soft_rst_reg0)
}

uint32_t SysTimer_AllWinner::GetTimerLow()
{
    // PiSCSI expects the timer to count UP, but the Allwinner HS timer counts
    // down. So, we subtract the current timer value from UINT32_MAX
    return UINT32_MAX - (hsitimer_regs->hs_tmr_curnt_lo_reg / 200);
}

uint32_t SysTimer_AllWinner::GetTimerHigh()
{
    return (uint32_t)0;
}

//---------------------------------------------------------------------------
//
//	Sleep in nanoseconds
//
//---------------------------------------------------------------------------
void SysTimer_AllWinner::SleepNsec(uint32_t nsec)
{
    // If time is less than one HS timer clock tick, don't do anything
    if (nsec < 20) {
        return;
    }

    // The HS timer receives a 200MHz clock input, which equates to
    // one clock tick every 5 ns.
    auto clockticks = (uint32_t)std::ceil(nsec / 5);

    uint32_t enter_time = hsitimer_regs->hs_tmr_curnt_lo_reg;

    // TODO: NEED TO HANDLE COUNTER OVERFLOW
    LOGTRACE("%s entertime: %08X ns: %d clockticks: %d", __PRETTY_FUNCTION__, enter_time, nsec, clockticks)
    while ((enter_time - hsitimer_regs->hs_tmr_curnt_lo_reg) < clockticks)
        ;

    return;
}

//---------------------------------------------------------------------------
//
//	Sleep in microseconds
//
//---------------------------------------------------------------------------
void SysTimer_AllWinner::SleepUsec(uint32_t usec)
{
    LOGTRACE("%s", __PRETTY_FUNCTION__)

    // If time is 0, don't do anything
    if (usec == 0) {
        return;
    }

    uint32_t enter_time = GetTimerLow();
    while ((GetTimerLow() - enter_time) < usec)
        ;
}
