//go:build mimxrt1062

package runtime

import (
	"device/arm"
	"device/nxp"
	"runtime/interrupt"
	"runtime/volatile"
	"unsafe"
)

const (
	lastCycle      = SYSTICK_FREQ/1000 - 1
	cyclesPerMicro = CORE_FREQ / 1000000
)

const (
	pitFreq           = OSC_FREQ // PIT/GPT are muxed to 24 MHz OSC
	pitCyclesPerMicro = pitFreq / 1000000
	pitSleepTimer     = 0 // x4 32-bit PIT timers [0..3]
)

var (
	tickCount  volatile.Register64
	cycleCount volatile.Register32
	pitActive  volatile.Register32
	pitTimeout interrupt.Interrupt
)

var (
	// debug exception and monitor control
	DEM_CR     = (*volatile.Register32)(unsafe.Pointer(uintptr(0xe000edfc)))
	DWT_CR     = (*volatile.Register32)(unsafe.Pointer(uintptr(0xe0001000)))
	DWT_CYCCNT = (*volatile.Register32)(unsafe.Pointer(uintptr(0xe0001004)))
)

func ticksToNanoseconds(ticks timeUnit) int64 {
	return int64(ticks) * 1000
}

func nanosecondsToTicks(ns int64) timeUnit {
	return timeUnit(ns / 1000)
}

func initSysTick() {

	const (
		traceEnable      = 0x01000000 // enable debugging & monitoring blocks
		cycleCountEnable = 0x00000001 // cycle count register
	)

	// disable SysTick if already running
	if arm.SYST.SYST_CSR.HasBits(arm.SYST_CSR_ENABLE_Msk) {
		arm.SYST.SYST_CSR.ClearBits(arm.SYST_CSR_ENABLE_Msk)
	}

	// zeroize the counter
	tickCount.Set(0)
	arm.SYST.SYST_RVR.Set(lastCycle)
	arm.SYST.SYST_CVR.Set(0)
	arm.SYST.SYST_CSR.Set(arm.SYST_CSR_TICKINT | arm.SYST_CSR_ENABLE)

	// set SysTick and PendSV priority to 32
	nxp.SystemControl.SHPR3.Set((0x20 << nxp.SCB_SHPR3_PRI_15_Pos) |
		(0x20 << nxp.SCB_SHPR3_PRI_14_Pos))

	// turn on cycle counter
	DEM_CR.SetBits(traceEnable)
	DWT_CR.SetBits(cycleCountEnable)
	cycleCount.Set(DWT_CYCCNT.Get())

	// enable PIT, disable counters
	nxp.PIT.MCR.Set(0)
	for i := range nxp.PIT.TIMER {
		nxp.PIT.TIMER[i].TCTRL.Set(0)
	}

	// register sleep timer interrupt
	pitTimeout = interrupt.New(nxp.IRQ_PIT, timerWake)
	pitTimeout.SetPriority(0x21)
	pitTimeout.Enable()
}

func initRTC() {
	if !nxp.SNVS.LPCR.HasBits(nxp.SNVS_LPCR_SRTC_ENV) {
		// if SRTC isn't running, start it with default Jan 1, 2019
		nxp.SNVS.LPSRTCLR.Set(uint32((0x5c2aad80 << 15) & 0xFFFFFFFF))
		nxp.SNVS.LPSRTCMR.Set(uint32(0x5c2aad80 >> 17))
		nxp.SNVS.LPCR.SetBits(nxp.SNVS_LPCR_SRTC_ENV)
	}
}

//go:export SysTick_Handler
func tick() {
	tickCount.Set(tickCount.Get() + 1)
	cycleCount.Set(DWT_CYCCNT.Get())
}

func ticks() timeUnit {
	mask := arm.DisableInterrupts()
	tick := tickCount.Get()
	cycs := cycleCount.Get()
	curr := DWT_CYCCNT.Get()
	arm.EnableInterrupts(mask)
	var diff uint32
	if curr < cycs { // cycle counter overflow/rollover occurred
		diff = (0xFFFFFFFF - cycs) + curr
	} else {
		diff = curr - cycs
	}
	frac := uint64(diff*0xFFFFFFFF/cyclesPerMicro) >> 32
	if frac > 1000 {
		frac = 1000
	}
	return timeUnit(1000*tick + frac)
}

func sleepTicks(duration timeUnit) {
	curr := ticks()
	last := curr + duration // 64-bit overflow unlikely
	for curr < last {
		cycles := timeUnit((last - curr) / pitCyclesPerMicro)
		if cycles > 0xFFFFFFFF {
			cycles = 0xFFFFFFFF
		}
		if !timerSleep(uint32(cycles)) {
			return // return early due to interrupt
		}
		curr = ticks()
	}
}

func timerSleep(cycles uint32) bool {
	pitActive.Set(1)
	nxp.PIT.TIMER[pitSleepTimer].LDVAL.Set(cycles)
	nxp.PIT.TIMER[pitSleepTimer].TCTRL.Set(nxp.PIT_TIMER_TCTRL_TIE)     // enable interrupts
	nxp.PIT.TIMER[pitSleepTimer].TCTRL.SetBits(nxp.PIT_TIMER_TCTRL_TEN) // start timer
	for {
		//arm.Asm("wfi") // TODO: causes hardfault! why?
		if pitActive.Get() == 0 {
			return true
		}
		if hasScheduler {
			break // some other interrupt occurred and needs servicing
		}
	}
	timerWake(interrupt.Interrupt{}) // clear and disable timer
	return false
}

func timerWake(interrupt.Interrupt) {
	pitActive.Set(0)
	// TFLGn[TIF] are set to 1 when a timeout occurs on the associated timer, and
	// are cleared to 0 by writing a 1 to the corresponding TFLGn[TIF].
	nxp.PIT.TIMER[pitSleepTimer].TFLG.Set(nxp.PIT_TIMER_TFLG_TIF) // clear interrupt flag
	nxp.PIT.TIMER[pitSleepTimer].TCTRL.Set(0)                     // disable timer/interrupt enable flags
}
