# SPDX-License-Identifier: GPL-2.0-or-later

# Script for Bouffalo chips with similar architecture used in BL602
# based on SiFive E21 core

source [find mem_helper.tcl]

transport select jtag

if { [info exists CPUTAPID ] } {
	set _CPUTAPID $CPUTAPID
} else {
	error "you must specify a tap id"
}

if { [info exists BL602_CHIPNAME] } {
	set _CHIPNAME $BL602_CHIPNAME
} else {
	error "you must specify a chip name"
}

if { [info exists WORKAREAADDR] } {
	set _WORKAREAADDR $WORKAREAADDR
} else {
	error "you must specify a work area address"
}

if { [info exists WORKAREASIZE] } {
	set _WORKAREASIZE $WORKAREASIZE
} else {
	error "you must specify a work area size"
}

jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id $_CPUTAPID

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME

riscv set_mem_access sysbus
riscv set_enable_virt2phys off

$_TARGETNAME configure -work-area-phys $_WORKAREAADDR -work-area-size $_WORKAREASIZE -work-area-backup 1

# Internal RC ticks on 32 MHz, so this speed should be safe to use.
adapter speed 8000

# Useful functions

set dmcontrol 		0x10
set dmcontrol_dmactive	[expr {1 << 0}]
set dmcontrol_ndmreset	[expr {1 << 1}]
set dmcontrol_resumereq	[expr {1 << 30}]
set dmcontrol_haltreq	[expr {1 << 31}]

proc bl602_restore_clock_defaults { } {
	# Switch clock to internal RC32M
	# In HBN_GLB, set ROOT_CLK_SEL = 0
	mmw 0x4000f030 0x0 0x00000003
	# Wait for clock switch
	sleep 10

	# GLB_REG_BCLK_DIS_FALSE
	mww 0x40000ffc 0x0

	# HCLK is RC32M, so BCLK/HCLK doesn't need divider
	# In GLB_CLK_CFG0, set BCLK_DIV = 0 and HCLK_DIV = 0
	mmw 0x40000000 0x0 0x00FFFF00
	# Wait for clock to stabilize
	sleep 10
}

# By spec, ndmreset should reset whole chip. This implementation resets only few parts of the chip.
# CTRL_PWRON_RESET register in GLB core triggers full "power-on like" reset, so we use it instead
# for full software reset.
proc bl602_sw_reset { } {
	# In GLB_SWRST_CFG2, clear CTRL_SYS_RESET, CTRL_CPU_RESET and CTRL_PWRON_RESET
	mmw 0x40000018 0x0 0x00000007

	# This Software reset method resets everything, so CPU as well.
	# It does that in not much good way, resulting in Debug Module being reset as well.
	# This also means, that right after CPU and Debug Module are turned on, we need to
	# enable Debug Module and halt CPU if needed. Additionally, we trigger this SW reset
	# through system bus access directly with DMI commands, to avoid errors printed by
	# OpenOCD about unsuccessful register write.

	# In GLB_SWRST_CFG2, set CTRL_SYS_RESET, CTRL_CPU_RESET and CTRL_PWRON_RESET to 1
	riscv dmi_write 0x39 0x40000018
	riscv dmi_write 0x3c 0x7

	# We need to wait for chip to finish reset and execute BootROM
	sleep 1

	# JTAG Debug Transport Module is reset as well, so we need to get into RUN/IDLE state
	runtest 10

	# We need to enable Debug Module and halt the CPU, so we can reset Program Counter
	# and to do additional clean-ups. If reset was called without halt, resume is handled
	# by reset-deassert-post event handler.

	# In Debug Module Control (dmcontrol), set dmactive to 1 and then haltreq to 1
	riscv dmi_write $::dmcontrol $::dmcontrol_dmactive
	riscv dmi_write $::dmcontrol [ expr {$::dmcontrol_dmactive | $::dmcontrol_haltreq} ]

	# Set Program Counter to start of BootROM
	set_reg {pc 0x21000000}
}

# On BL602 and BL702, the only way to force chip stay in BootROM (until JTAG attaches)
# is by putting infinity loop into HBN RAM (which is not reset by sw reset), and then
# configure HBN registers, which will cause BootROM to jump into our code early in BootROM.
proc bl602_sw_reset_hbn_wait {} {
	# Restore clocks to defaults
	bl602_restore_clock_defaults

	# In HBN RAM, write infinity loop instruction
	# beq zero, zero, 0
	mww 0x40010000 0x00000063
	# In HNB, set HBN_RSV0 (Status Flag) to "EHBN" (as uint32_t)
	mww 0x4000f100 0x4e424845
	# In HBN, set HBN_RSV1 (WakeUp Address) to HBN RAM address
	mww 0x4000f104 0x40010000

	# Perform software reset
	bl602_sw_reset

	# Clear HBN RAM, HBN_RSV0 and HBN_RSV1
	mww 0x40010000 0x00000000
	mww 0x4000f100 0x00000000
	mww 0x4000f104 0x00000000

	# This early jump method locks up BootROM through Trust Zone Controller.
	# That means any read of BootROM returns 0xDEADBEEF.
	# Only way to reset it, is through JTAG Reset, thus toggling ndmreset in dmcontrol.
	riscv dmi_write $::dmcontrol [ expr {$::dmcontrol_dmactive | $::dmcontrol_ndmreset} ]
	riscv dmi_write $::dmcontrol [ expr {$::dmcontrol_dmactive} ]
}

$_TARGETNAME configure -event reset-deassert-post {
	# Resume the processor if reset was triggered without halt request
	if {$halt == 0} {
		riscv dmi_write $::dmcontrol [ expr {$::dmcontrol_dmactive | $::dmcontrol_resumereq} ]
	}
}
