# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Marek Kraus <gamelaster@gami.ee>

#
# Bouffalo Labs BL616 and BL618 target
#
# Default JTAG pins: (if not changed by eFuse configuration)
# TMS - GPIO0
# TCK - GPIO1
# TDO - GPIO2
# TDI - GPIO3
#

source [find mem_helper.tcl]

transport select jtag

if { [info exists CHIPNAME] } {
	set _CHIPNAME $CHIPNAME
} else {
	set _CHIPNAME bl616
}

jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10000b6f

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

riscv set_mem_access progbuf
riscv set_enable_virt2phys off

$_TARGETNAME configure -work-area-phys 0x40000000 -work-area-size 0x10000 -work-area-backup 1

adapter speed 4000

# Useful functions
set dmcontrol 		0x10
set dmcontrol_dmactive	[expr {1 << 0}]
set dmcontrol_haltreq	[expr {1 << 31}]

# 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.
$_TARGETNAME configure -event reset-assert {
	halt

	# To stay in BootROM until JTAG re-attaches, we are using BootROM functionality
	# to force ISP mode, so BootROM looks out for external ISP communication.

	# In HBN_RSV2, set HBN_RELEASE_CORE to HBN_RELEASE_CORE_FLAG (4)
	# and HBN_USER_BOOT_SEL to 1 (ISP)
	mww 0x2000f108 0x44000000

	# Switch clock to internal RC32M
	# In HBN_GLB, set ROOT_CLK_SEL = 0
	mmw 0x2000f030 0x0 0x00000002

	# In GLB_SYS_CFG0, set REG_BCLK_DIV and REG_HCLK_DIV = 0
	mmw 0x20000090 0x0 0x00FFFF00

	# Trigger BCLK ACT pulse
	# In GLB_SYS_CFG1, set BCLK_DIV_ACT_PULSE = 1
	mmw 0x20000094 0x1 0x00000001
	# In GLB_SYS_CFG1, wait for GLB_STS_BCLK_PROT_DONE to become 1
	while { [expr {[mrw 0x20000094] & 4}] == 0 } { sleep 1 }

	# In GLB_SWRST_CFG2, clear CTRL_PWRON_RESET
	mmw 0x20000548 0x0 0x00000001

	# 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 program buffer access directly with DMI commands, to avoid errors printed by
	# OpenOCD about unsuccessful register write.

	# In GLB_SWRST_CFG2, set CTRL_PWRON_RESET to 1
	set_reg {fp 0x20000548 s1 0x01}
	riscv dmi_write 0x20 0x00942023
	riscv dmi_write 0x17 0x40000

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

	# 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} ]
}

$_TARGETNAME configure -event reset-deassert-post {
	# Set Program Counter to start of BootROM and execute one instruction
	step 0x90000000

	# When using default JTAG pinout, BOOT pin is the same as JTAG TDO pin.
	# Since after reset we set PC to start of the BootROM,
	# BootROM will execute also check of BOOT pin, which will disable TDO pin,
	# to check the BOOT pin state. This leads to temporary loss of JTAG access
	# and causes (recoverable) errors in OpenOCD. We can bypass the BOOT pin check
	# function, by forcing booting from Media/SPI Flash.

	# In HBN_RSV2, set HBN_RELEASE_CORE to HBN_RELEASE_CORE_FLAG (4)
	# and HBN_USER_BOOT_SEL to 2 (Media/SPI Flash)
	mww 0x2000f108 0x48000000

	# Resume the processor if reset was triggered without halt request
	if {$halt == 0} {
		resume
	}
}

# According to JTAG spec (IEEE 1149.1), when chip enters "Test-Logic-Reset" state,
# the IR instruction should be set to "IDCODE" or "BYPASS" (when chip does not have IDCODE).
# This is done so automatic chain scan can detect all the chips within JTAG chain without knowing IDCODE.
# JTAG Debug Transport Module (DTM) used in this chip, developed by T-Head (formerly C-Sky)
# does not implement this, so OpenOCD can't detect the chip anymore after the IR instruction is changed.
# This workaround gets chip into known state, and manually set IR instruction to IDCODE,
# which is 0x01, standardized by RISC-V Debug Specification.
proc init_reset { mode } {
	if {[using_jtag]} {
		# Get JTAG SM to known state
		runtest 10
		# Set IR to IDCODE
		irscan $::_CHIPNAME.cpu 0x01
		jtag arp_init-reset
	}
}

proc jtag_init {} {
	# Get JTAG SM to known state
	runtest 10
	# Set IR to IDCODE
	irscan $::_CHIPNAME.cpu 0x01

	if {[catch {jtag arp_init} err]!=0} {
		# try resetting additionally
		init_reset startup
	}
}
