# script for Cypress PSoC 4 devices

#
# PSoC 4 devices support SWD transports only.
#
source [find target/swj-dp.tcl]

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

# Work-area is a space in RAM used for flash programming
# By default use 4kB
if { [info exists WORKAREASIZE] } {
   set _WORKAREASIZE $WORKAREASIZE
} else {
   set _WORKAREASIZE 0x1000
}

if { [info exists CPUTAPID] } {
   set _CPUTAPID $CPUTAPID
} else {
   set _CPUTAPID 0x0bb11477
}

swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME cortex_m -dap $_CHIPNAME.dap

$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0

set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME psoc4 0 0 0 0 $_TARGETNAME

adapter_khz 1500

# Reset, bloody PSoC 4 reset
#
# 1) XRES (nSRST) resets also SWD DP so SWD line reset and DP reinit is needed.
# High level adapter stops working after SRST and needs OpenOCD restart.
# If your hw does not use SRST for other circuits, use sysresetreq instead
#
# 2) PSoC 4 executes initialization code from system ROM after reset.
# This code subsequently jumps to user flash reset vector address.
# Unfortunately the system ROM code is protected from reading and debugging.
# Protection breaks vector catch VC_CORERESET used for "reset halt" by cortex_m.
#
# Cypress uses TEST_MODE flag to loop CPU in system ROM before executing code
# from user flash. Programming specifications states that TEST_MODE flag must be
# set in time frame 400 usec delayed about 1 msec from reset.
#
# OpenOCD have no standard way how to set TEST_MODE in specified time frame.
# As a workaround the TEST_MODE flag is set before reset instead.
# It worked for the oldest family PSoC4100/4200 even though it is not guaranteed
# by specification.
#
# Newer families like PSoC 4000, 4100M, 4200M, 4100L, 4200L and PSoC 4 BLE
# clear TEST_MODE flag during device reset so workaround is not possible.
# Use a KitProg adapter for theese devices or "reset halt" will not stop
# before executing user code.
#
# 3) SWD cannot be connected during system initialization after reset.
# This might be a reason for unconnecting ST-Link v2 when deasserting reset.
# As a workaround arp_reset deassert is not called for hla

if {![using_hla]} {
   # if srst is not fitted use SYSRESETREQ to
   # perform a soft reset
   cortex_m reset_config sysresetreq
}

proc psoc4_get_family_id {} {
	set err [catch "mem2array romtable_pid 32 0xF0000FE0 3"]
	if { $err } {
		return 0
	}
	if { [expr $romtable_pid(0) & 0xffffff00 ]
	  || [expr $romtable_pid(1) & 0xffffff00 ]
	  || [expr $romtable_pid(2) & 0xffffff00 ] } {
		echo "Unexpected data in ROMTABLE"
		return 0
	}
	set designer_id [expr (( $romtable_pid(1) & 0xf0 ) >> 4) | (( $romtable_pid(2) & 0xf ) << 4 ) ]
	if { $designer_id != 0xb4 } {
		echo [format "ROMTABLE Designer ID 0x%02x is not Cypress" $designer_id]
		return 0
	}
	set family_id [expr ( $romtable_pid(0) & 0xff ) | (( $romtable_pid(1) & 0xf ) << 8 ) ]
	return $family_id
}

proc ocd_process_reset_inner { MODE } {
	global PSOC4_USE_ACQUIRE PSOC4_TEST_MODE_WORKAROUND
	global _TARGETNAME

	if { 0 != [string compare $_TARGETNAME [target names]] } {
		return -code error "PSoC 4 reset can handle only one $_TARGETNAME target";
	}
	set t $_TARGETNAME

	# If this target must be halted...
	set halt -1
	if { 0 == [string compare $MODE halt] } {
		set halt 1
	}
	if { 0 == [string compare $MODE init] } {
		set halt 1;
	}
	if { 0 == [string compare $MODE run ] } {
		set halt 0;
	}
	if { $halt < 0 } {
		return -code error "Invalid mode: $MODE, must be one of: halt, init, or run";
	}

	if { ! [info exists PSOC4_USE_ACQUIRE] } {
		if { 0 == [string compare [adapter_name] kitprog ] } {
			set PSOC4_USE_ACQUIRE 1
		} else {
			set PSOC4_USE_ACQUIRE 0
		}
	}
	if { $PSOC4_USE_ACQUIRE } {
		set PSOC4_TEST_MODE_WORKAROUND 0
	} elseif { ! [info exists PSOC4_TEST_MODE_WORKAROUND] } {
		if { [psoc4_get_family_id] == 0x93 } {
			set PSOC4_TEST_MODE_WORKAROUND 1
		} else {
			set PSOC4_TEST_MODE_WORKAROUND 0
		}
	}

	#$t invoke-event reset-start
	$t invoke-event reset-assert-pre

	if { $halt && $PSOC4_USE_ACQUIRE } {
		catch { [adapter_name] acquire_psoc }
		$t arp_examine
	} else {
		if { $PSOC4_TEST_MODE_WORKAROUND } {
			set TEST_MODE 0x40030014
			if { $halt == 1 } {
				catch { mww $TEST_MODE 0x80000000 }
			} else {
				catch { mww $TEST_MODE 0 }
			}
		}

		$t arp_reset assert 0
	}

	$t invoke-event reset-assert-post
	$t invoke-event reset-deassert-pre
	if {![using_hla]} {	# workaround ST-Link v2 fails and forcing reconnect
		$t arp_reset deassert 0
	}
	$t invoke-event reset-deassert-post

	# Pass 1 - Now wait for any halt (requested as part of reset
	# assert/deassert) to happen.  Ideally it takes effect without
	# first executing any instructions.
	if { $halt } {
		# Now PSoC CPU should loop in system ROM
		$t arp_waitstate running 200
		$t arp_halt

		# Catch, but ignore any errors.
		catch { $t arp_waitstate halted 1000 }

		# Did we succeed?
		set s [$t curstate]

		if { 0 != [string compare $s "halted" ] } {
			return -code error [format "TARGET: %s - Not halted" $t]
		}

		# Check if PSoC CPU is stopped in system ROM
		set pc [ocd_reg pc]
		regsub {pc[^:]*: } $pc "" pc
		if { $pc < 0x10000000 || $pc > 0x1000ffff } {
			set hint ""
			set family_id [psoc4_get_family_id]
			if { $family_id == 0x93 } {
				set hint ", use 'reset_config none'"
			} elseif { $family_id > 0x93 } {
				set hint ", use a KitProg adapter"
			}
			return -code error [format "TARGET: %s - Not halted in system ROM%s" $t $hint]
		}

		# Set registers to reset vector values
		mem2array value 32 0 2
		reg pc [expr $value(1) & 0xfffffffe ]
		reg msp $value(0)

		if { $PSOC4_TEST_MODE_WORKAROUND } {
			catch { mww $TEST_MODE 0 }
		}
	}

	#Pass 2 - if needed "init"
	if { 0 == [string compare init $MODE] } {
		set err [catch "$t arp_waitstate halted 5000"]

		# Did it halt?
		if { $err == 0 } {
			$t invoke-event reset-init
		}
	}

	$t invoke-event reset-end
}
