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

#
# target configuration for
# Xilinx ZynqMP (UltraScale+ / A53)
#
if { [info exists CHIPNAME] } {
    set _CHIPNAME $CHIPNAME
} else {
    set _CHIPNAME uscale
}

#
# DAP tap (Quard core A53)
#
if { [info exists DAP_TAPID] } {
    set _DAP_TAPID $DAP_TAPID
} else {
    set _DAP_TAPID 0x5ba00477
}

jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap

#
# PS tap (UltraScale+)
#
if { [info exists PS_TAPID] } {
    set _PS_TAPID $PS_TAPID
    jtag newtap $_CHIPNAME ps -irlen 12 -ircapture 0x1 -irmask 0x03 -expected-id $_PS_TAPID
} else {
    # FPGA Programmable logic. Values take from Table 39-1 in UG1085:
    jtag newtap $_CHIPNAME ps -irlen 12 -ircapture 0x1 -irmask 0x03 -ignore-version \
        -expected-id 0x04711093 \
        -expected-id 0x04710093 \
        -expected-id 0x04721093 \
        -expected-id 0x04720093 \
        -expected-id 0x04739093 \
        -expected-id 0x04730093 \
        -expected-id 0x04738093 \
        -expected-id 0x04740093 \
        -expected-id 0x04750093 \
        -expected-id 0x04759093 \
        -expected-id 0x04758093
}

set jtag_configured 0

jtag configure $_CHIPNAME.ps -event setup {
    global _CHIPNAME
    global jtag_configured

    if { $jtag_configured == 0 } {
        # add the DAP tap to the chain
        # See https://forums.xilinx.com/t5/UltraScale-Architecture/JTAG-Chain-Configuration-for-Zynq-UltraScale-MPSoC/td-p/758924
        irscan $_CHIPNAME.ps 0x824
        drscan $_CHIPNAME.ps 32 0x00000003
        runtest 100

        # setup event will be re-entered through jtag arp_init
        # break the recursion
        set jtag_configured 1
        # re-initialized the jtag chain
        jtag arp_init
    }
}

set _TARGETNAME $_CHIPNAME.a53
set _CTINAME $_CHIPNAME.cti
set _smp_command ""

set DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000}
set CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000}
set _cores 4

for { set _core 0 } { $_core < $_cores } { incr _core } {

    cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 1 \
        -baseaddr [lindex $CTIBASE $_core]

    set _command "target create $_TARGETNAME.$_core aarch64 -dap $_CHIPNAME.dap \
        -dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core"

    if { $_core != 0 } {
        # non-boot core examination may fail
        set _command "$_command -defer-examine"
        set _smp_command "$_smp_command $_TARGETNAME.$_core"
    } else {
        set _command "$_command -rtos hwthread"
        set _smp_command "target smp $_TARGETNAME.$_core"
    }

    eval $_command
}

target create uscale.axi mem_ap -dap uscale.dap -ap-num 0

eval $_smp_command
targets $_TARGETNAME.0

proc core_up { args } {
    global _TARGETNAME
    foreach core $args {
        $_TARGETNAME.$core arp_examine
    }
}

proc BIT {n} {
	return [expr {1 << $n}]
}

set IPI_BASE			0xff300000
set IPI_PMU_0_TRIG		[expr {$IPI_BASE + 0x30000}]
set IPI_PMU_0_IER		[expr {$IPI_BASE + 0x30018}]
set IPI_PMU_0					[BIT 16]

set CRF_APB_BASE		0xfd1a0000
set CRF_APB_RST_FPD_APU		[expr {$CRF_APB_BASE + 0x104}]
set CRF_APB_RST_FPD_APU_ACPU0_PWRON_RESET	[BIT 10]
set CRF_APB_RST_FPD_APU_L2_RESET		[BIT  8]
set CRF_APB_RST_FPD_APU_ACPU0_RESET		[BIT  0]

set APU_BASE			0xfd5c0000
set APU_RVBARADDR_BASE		[expr {$APU_BASE + 0x40}]

set PMU_BASE			0xffd80000
set PMU_GLOBAL			$PMU_BASE
set PMU_GLOBAL_MB_SLEEP				[BIT 16]
set PMU_GLOBAL_FW_IS_PRESENT			[BIT  4]
set PMU_GLOBAL_DONT_SLEEP			[BIT  0]

set PMU_RAM_BASE		0xffdc0000

set OCM_RAM_BASE		0xfffc0000

rename BIT {}

add_help_text halt_pmu "Halt the PMU in preparation for loading new firmware.\
	This should be matched with a call to resume_pmu."
proc halt_pmu {} {
	set axi $::_CHIPNAME.axi
	set val [$axi read_memory $::IPI_PMU_0_IER 32 1]
	$axi write_memory $::IPI_PMU_0_IER 32 [expr {$val | $::IPI_PMU_0}]

	set val [$axi read_memory $::IPI_PMU_0_TRIG 32 1]
	$axi write_memory $::IPI_PMU_0_TRIG 32 [expr {$val | $::IPI_PMU_0}]

	set start [ms]
	while {!([$axi read_memory $::PMU_GLOBAL 32 1] & $::PMU_GLOBAL_MB_SLEEP)} {
		if {[ms] - $start > 1000} {
			error "Timed out waiting for PMU to halt"
		}
	}
}

add_help_text resume_pmu "Resume the PMU after loading new firmware. This\
	should be matched with a call to halt_pmu."
proc resume_pmu {} {
	set axi $::_CHIPNAME.axi
	set val [$axi read_memory $::PMU_GLOBAL 32 1]
	$axi write_memory $::PMU_GLOBAL 32 [expr {$val | $::PMU_GLOBAL_DONT_SLEEP}]

	set start [ms]
	while {!([$axi read_memory $::PMU_GLOBAL 32 1] & $::PMU_GLOBAL_FW_IS_PRESENT)} {
		if {[ms] - $start > 5000} {
			error "Timed out waiting for PMU firmware"
		}
	}
}

add_usage_text release_apu {apu}
add_help_text release_apu "Release an APU from reset. It will start executing\
	at RVBARADDR. You probably want resume_apu or start_apu instead."
proc release_apu {apu} {
	set axi $::_CHIPNAME.axi
	set val [$axi read_memory $::CRF_APB_RST_FPD_APU 32 1]
	set mask [expr {
		(($::CRF_APB_RST_FPD_APU_ACPU0_PWRON_RESET | \
		  $::CRF_APB_RST_FPD_APU_ACPU0_RESET) << $apu) | \
		$::CRF_APB_RST_FPD_APU_L2_RESET
	}]
	$axi write_memory $::CRF_APB_RST_FPD_APU 32 [expr {$val & ~$mask}]

	core_up $apu
	$::_TARGETNAME.$apu aarch64 dbginit
}

proc _rvbaraddr {apu} {
	return [expr {$::APU_RVBARADDR_BASE + 8 * $apu}]
}

add_usage_text resume_apu {apu addr}
add_help_text resume_apu "Resume an APU at a given address."
proc resume_apu {apu addr} {
	set addrl [expr {$addr & 0xffffffff}]
	set addrh [expr {$addr >> 32}]
	$::_CHIPNAME.axi write_memory [_rvbaraddr $apu] 32 [list $addrl $addrh]

	release_apu $apu
}

add_usage_text start_apu {apu}
add_help_text start_apu "Start an APU and put it into an infinite loop at\
	RVBARADDR. This can be convenient if you just want to halt the APU\
	(since it won't execute anything unusual)."
proc start_apu {apu} {
	set axi $::_CHIPNAME.axi
	foreach {addrl addrh} [$axi read_memory [_rvbaraddr $apu] 32 2] {
		set addr [expr {($addrh << 32) | $addrl}]
	}
	# write the infinite loop instruction
	$axi write_memory $addr 32 0x14000000

	release_apu $apu
}

add_usage_text boot_pmu {image}
add_help_text boot_pmu "Boot the PMU with a given firmware image, loading it\
	to the beginning of PMU RAM. The PMU ROM will jump to this location\
	after we resume it."
proc boot_pmu {image} {
	halt_pmu
	echo "Info : Loading PMU firmware $image to $::PMU_RAM_BASE"
	load_image $image $::PMU_RAM_BASE
	resume_pmu
}

add_usage_text boot_apu "image \[apu=0 \[addr=$OCM_RAM_BASE\]\]"
add_help_text boot_apu "Boot an APU with a given firmware image. The default\
	address is the beginning of OCM RAM. Upon success, the default target\
	will be changed to the (running) apu."
proc boot_apu [list image {apu 0} [list addr $OCM_RAM_BASE]] {
	start_apu $apu
	targets $::_TARGETNAME.$apu
	halt

	echo "Info : Loading APU$apu firmware $image to $addr"
	load_image $image $addr
	resume $addr
}
