//go:build k210

// This file implements target-specific things for the K210 chip as used in the
// MAix Bit with Mic.

package runtime

import (
	"device/kendryte"
	"device/riscv"
	"machine"
	"runtime/volatile"
	"unsafe"
)

type timeUnit int64

//export main
func main() {

	// Both harts should disable all interrupts on startup.
	initPLIC()

	// Only use one hart for the moment.
	if riscv.MHARTID.Get() != 0 {
		abort()
	}

	// Reset all interrupt source priorities to zero.
	for i := 0; i < kendryte.IRQ_max; i++ {
		kendryte.PLIC.PRIORITY[i].Set(0)
	}

	// Zero MCAUSE, which is set to the reset reason on reset. It must be zeroed
	// to make interrupt.In() work.
	// This would also be a good time to save the reset reason, but that hasn't
	// been implemented yet.
	riscv.MCAUSE.Set(0)

	// Set the interrupt address.
	// Note that this address must be aligned specially, otherwise the MODE bits
	// of MTVEC won't be zero.
	riscv.MTVEC.Set(uintptr(unsafe.Pointer(&handleInterruptASM)))

	// Reset the MIE register and enable external interrupts.
	// It must be reset here because it not zeroed at startup.
	riscv.MIE.Set(1 << 11) // bit 11 is for machine external interrupts

	// Enable global interrupts now that they've been set up.
	riscv.MSTATUS.SetBits(1 << 3) // MIE

	preinit()
	initPeripherals()
	run()
	exit(0)
}

func initPLIC() {
	hartId := riscv.MHARTID.Get()

	// Zero the PLIC enable bits at startup.
	for i := 0; i < ((kendryte.IRQ_max + 32) / 32); i++ {
		kendryte.PLIC.TARGET_ENABLES[hartId].ENABLE[i].Set(0)
	}

	// Zero the PLIC threshold bits to allow all interrupts.
	kendryte.PLIC.TARGETS[hartId].THRESHOLD.Set(0)
}

//go:extern handleInterruptASM
var handleInterruptASM [0]uintptr

//export handleInterrupt
func handleInterrupt() {
	cause := riscv.MCAUSE.Get()
	code := uint64(cause &^ (1 << 63))
	if cause&(1<<63) != 0 {
		// Topmost bit is set, which means that it is an interrupt.
		switch code {
		case 7: // Machine timer interrupt
			// Signal timeout.
			timerWakeup.Set(1)
			// Disable the timer, to avoid triggering the interrupt right after
			// this interrupt returns.
			riscv.MIE.ClearBits(1 << 7) // MTIE bit
		case 11: // Machine external interrupt
			hartId := riscv.MHARTID.Get()

			// Claim this interrupt.
			id := kendryte.PLIC.TARGETS[hartId].CLAIM.Get()
			// Call the interrupt handler, if any is registered for this ID.
			kendryte.HandleInterrupt(int(id))
			// Complete this interrupt.
			kendryte.PLIC.TARGETS[hartId].CLAIM.Set(id)
		}
	} else {
		// Topmost bit is clear, so it is an exception of some sort.
		// We could implement support for unsupported instructions here (such as
		// misaligned loads). However, for now we'll just print a fatal error.
		handleException(code)
	}

	// Zero MCAUSE so that it can later be used to see whether we're in an
	// interrupt or not.
	riscv.MCAUSE.Set(0)
}

// initPeripherals configures periperhals the way the runtime expects them.
func initPeripherals() {
	// Enable APB0 clock.
	kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN)

	// Enable FPIOA peripheral.
	kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_FPIOA_CLK_EN)

	machine.InitSerial()
}

func putchar(c byte) {
	machine.Serial.WriteByte(c)
}

func getchar() byte {
	for machine.Serial.Buffered() == 0 {
		Gosched()
	}
	v, _ := machine.Serial.ReadByte()
	return v
}

func buffered() int {
	return machine.Serial.Buffered()
}

var timerWakeup volatile.Register8

func ticks() timeUnit {
	highBits := uint32(kendryte.CLINT.MTIME.Get() >> 32)
	for {
		lowBits := uint32(kendryte.CLINT.MTIME.Get() & 0xffffffff)
		newHighBits := uint32(kendryte.CLINT.MTIME.Get() >> 32)
		if newHighBits == highBits {
			return timeUnit(lowBits) | (timeUnit(highBits) << 32)
		}
		highBits = newHighBits
	}
}

func sleepTicks(d timeUnit) {
	target := uint64(ticks() + d)
	kendryte.CLINT.MTIMECMP[0].Set(target)
	riscv.MIE.SetBits(1 << 7) // MTIE
	for {
		if timerWakeup.Get() != 0 {
			timerWakeup.Set(0)
			// Disable timer.
			break
		}
		riscv.Asm("wfi")
	}
}

// handleException is called from the interrupt handler for any exception.
// Exceptions can be things like illegal instructions, invalid memory
// read/write, and similar issues.
func handleException(code uint64) {
	// For a list of exception codes, see:
	// https://content.riscv.org/wp-content/uploads/2019/08/riscv-privileged-20190608-1.pdf#page=49
	print("fatal error: exception with mcause=")
	print(code)
	print(" pc=")
	print(riscv.MEPC.Get())
	println()
	abort()
}
