# Xilinx XADC support for 7 Series FPGAs
#
# The 7 Series FPGAs contain an on-chip 12 bit ADC that can probe die
# temperature, internal power supply rail voltages as well as external
# voltages. The XADC is available both from fabric as well as through the
# JTAG TAP.
#
# This code implements access throught the JTAG TAP.
#
# https://www.xilinx.com/support/documentation/user_guides/ug480_7Series_XADC.pdf

# build a 32 bit DRP command for the XADC DR
proc xadc_cmd {cmd addr data} {
	array set cmds {
		NOP 0x00
		READ 0x01
		WRITE 0x02
	}
	return [expr ($cmds($cmd) << 26) | ($addr << 16) | ($data << 0)]
}

# XADC register addresses
# Some addresses (status registers 0-3) have special function when written to.
proc XADC {key} {
	array set addrs {
		TEMP 0x00
		LOCK 0x00
		VCCINT 0x01
		VCCAUX 0x02
		VAUXEN 0x02
		VPVN 0x03
		RESET 0x03
		VREFP 0x04
		VREFN 0x05
		VCCBRAM 0x06
		SUPAOFFS 0x08
		ADCAOFFS 0x09
		ADCAGAIN 0x0a
		VCCPINT 0x0d
		VCCPAUX 0x0e
		VCCODDR 0x0f
		VAUX0 0x10
		VAUX1 0x11
		VAUX2 0x12
		VAUX3 0x13
		VAUX4 0x14
		VAUX5 0x15
		VAUX6 0x16
		VAUX7 0x17
		VAUX8 0x18
		VAUX9 0x19
		VAUX10 0x1a
		VAUX11 0x1b
		VAUX12 0x1c
		VAUX13 0x1d
		VAUX14 0x1e
		VAUX15 0x1f
		SUPBOFFS 0x30
		ADCBOFFS 0x31
		ADCBGAIN 0x32
		FLAG 0x3f
		CFG0 0x40
		CFG1 0x41
		CFG2 0x42
		SEQ0 0x48
		SEQ1 0x49
		SEQ2 0x4a
		SEQ3 0x4b
		SEQ4 0x4c
		SEQ5 0x4d
		SEQ6 0x4e
		SEQ7 0x4f
		ALARM0 0x50
		ALARM1 0x51
		ALARM2 0x52
		ALARM3 0x53
		ALARM4 0x54
		ALARM5 0x55
		ALARM6 0x56
		ALARM7 0x57
		ALARM8 0x58
		ALARM9 0x59
		ALARM10 0x5a
		ALARM11 0x5b
		ALARM12 0x5c
		ALARM13 0x5d
		ALARM14 0x5e
		ALARM15 0x5f
	}
	return $addrs($key)
}

# Select the XADC DR
proc xadc_select {tap} {
	set XADC_IR 0x37
	irscan $tap $XADC_IR
	runtest 10
}

# XADC transfer
proc xadc_xfer {tap cmd addr data} {
	set ret [drscan $tap 32 [xadc_cmd $cmd $addr $data]]
	runtest 10
	return [expr 0x$ret]
}

# XADC register write
proc xadc_write {tap addr data} {
	xadc_xfer $tap WRITE $addr $data
}

# XADC register read, non-pipelined
proc xadc_read {tap addr} {
	xadc_xfer $tap READ $addr 0
	return [xadc_xfer $tap NOP 0 0]
}

# convert 16 bit register code from ADC measurement on
# external voltages (VAUX) to Volt
proc xadc_volt {code} {
	return [expr $code * 1./(1 << 16)]
}

# convert 16 bit temperature measurement to Celsius
proc xadc_temp {code} {
	return [expr $code * 503.975/(1 << 16) - 273.15]
}

# convert 16 bit suppply voltage measurement to Volt
proc xadc_sup {code} {
	return [expr $code * 3./(1 << 16)]
}

# perform a single channel measurement using default settings
proc xadc_single {tap ch} {
	set cfg0 [xadc_read $tap [XADC CFG0]]
	set cfg1 [xadc_read $tap [XADC CFG1]]
	# set channel
	xadc_write $tap [XADC CFG0] $cfg0
	# single channel, disable the sequencer
	xadc_write $tap [XADC CFG1] 0x3000
	# leave some time for the conversion
	runtest 100
	set ret [xadc_read $tap [XADC $ch]]
	# restore CFG0/1
	xadc_write $tap [XADC CFG0] $cfg0
	xadc_write $tap [XADC CFG1] $cfg1
	return $ret
}

# measure all internal voltages
proc xadc_report {tap} {
	xadc_select $tap
	echo "TEMP [format %.2f [xadc_temp [xadc_single $tap TEMP]]] C"
	foreach ch [list VCCINT VCCAUX VCCBRAM VPVN VREFP VREFN \
		VCCPINT VCCPAUX VCCODDR] {
		echo "$ch [format %.3f [xadc_sup [xadc_single $tap $ch]]] V"
	}
}
