a row of my life

# VisionFive JTAG adventures, Part 1: JH7100 GPIO

2022-06-27

## Background

The research that went into this article began as a simple technical question: How do I connect to the JTAG debug port on the VisionFive?

When luojia asked this very question on the support forum, the only responses were, surprisingly, discouragement. However, for any low-level software development, such a debug port is pretty much the only way to sanely, well, debug anything at all. Even though printf debugging was technically possible, it is still worth it to see if we can reach it over JTAG, given that the board is supposed to have JTAG connectors.

## VisionFive

The development board in question, VisionFive, is a RISC-V single-board computer from StarFive. At its core is a StarFive JH7100 SoC, with two SiFive U74 cores and one E24 core.

(As of writing, the version of VisionFive currently available is also known as ‘VisionFive V1’, though even official documentation often omits the version number. The name ‘VisionFive’ in this article consistently refers to VisionFive V1, in case confusion arises in the future.)

The same processor core is also found on the HiFive Unmatched, on which the main SoC has four U74 cores and one S7 core. Unmatched had more RAM and better peripherals, and was made in the form factor of a regular PC motherboard. VisionFive is, instead, clearly intended for a slightly lower end market, or for those who prefers a palm-sized Raspberry Pi lookalike.

## JTAG on the VisionFive, or not

For our purposes, the one main difference between the Unmatched and the VisionFive is how the JTAG port is connected. On the Unmatched, an FT2322H adapter on-board means that the Micro-USB port gives access to both the UART port and the JTAG port, readily usable with riscv-openocd.

On the VisionFive, however, JTAG access seems… elusive. Nowhere in the documentation is the JTAG port on-board mentioned. On drawings of the board, the words JTAG are written next to the PMIC, which is, at least to me, nonsensical, as there are no notable ports to be found there.

Next to the color-coded (nice!) 40-pin header though, one can find seven plated holes, one of them having a square outline. Browsing through the schematic reveals that this is indeed where the JTAG port is connected.

Problem solved, right? Solder up some header pins or build a pogo-pin rig, and just connect it up to your workstation with an FT2322H, and we have JTAG.

Or so we thought. Unfortunately, this JTAG ports does not seem to respond at all to any input. It seems as if this port isn’t connected at all.

## Finding the JTAG port

Chasing through labels on the schematics reveals that the JTAG_* through-holes connect to a level shifter, which presents the SoC with 1.8 V signals instead of 3.3 V ones. They then connect to pads on the SoC, mysteriously named GPIOxx/U74_JTAG_*.

So are these GPIO or JTAG? Thankfully the pad connections have labels with positions, so we can look them up on the datasheet. For example, A25 is described as…

A25     GPIO[0]     IO      function IO share with GPIO

‘Function IO Share’ is the title of section 11 in the datasheet. One register, named IO_PADSHARE_SEL, has one of 7 valid values, 0 through 6, is a global configuration controlling the functions two-hundred-odd pads PAD_FUNC_SHARE[141:0] and PAD_GPIO[63:0]. Each of the configurations is called a ‘signal group’, and the groups themselves are called ‘Function 0’ through ‘Function 7’. A giant table then follows, showing each pad’s function under each signal group.

The pads PAD_GPIO[4:0] would be the connections we found earlier, and in Function 0, they. Since Function 0 is supposed to be the default value on reset, this means that we should see a JTAG port there, right?

At this point, the only thing I could think of is connecting to JTAG while holding down the reset button. However, since I had neither a JTAG adapter nor a VisionFive board, all I did was tell luojia about it, and moved on to finish my finals.

## Digging deeper into the GPIO multiplexer

The single document that made the system ‘click’ for me is the devicetree documentation for starfive,jh7100-pinctrl. This node can be found in jh7100.dtsi, which is included in jh7100-starfive-visionfive-v1.dts:

gpio: pinctrl@11910000 {
compatible = "starfive,jh7100-pinctrl";
reg = <0x0 0x11910000 0x0 0x10000>,
<0x0 0x11858000 0x0 0x1000>;
reg-names = "gpio", "padctl";
/* <snip> */
};

The address ranges mentioned here correspond to these rows in the datasheet:

SYSCTRL-IOPAD_CTRL  0x00_1185_8000  0x00_1185_BFFF  16KB
GPIO                0x00_1191_0000  0x00_1191_FFFF  64KB

These registers control the functions and states of PAD_GPIO[63:0] and PAD_FUNC_SHARE[141:0]. The following diagram is included in the devicetree bindings documentation:

                          Signal group 0, 1, ... or 6
___|___
|       |
LCD output -----------------|       |
CMOS Camera interface ------|       |--- PAD_GPIO[0]
Ethernet PHY interface -----|  MUX  |--- PAD_GPIO[1]
...                       |       |      ...
-------- GPIO0 ------------|       |
|  -------|-- GPIO1 --------|       |--- PAD_FUNC_SHARE[0]
| |       |   |             |       |--- PAD_FUNC_SHARE[1]
| |       |   |  ...        |       |       ...
| |       |   |             |       |--- PAD_FUNC_SHARE[141]
| |  -----|---|-- GPIO63 ---|       |
| | |     |   |   |          -------
UART0     UART1 --

These pads on the package, or ‘function IO share with GPIO’ as listed in the datasheet, are connected to the internal signals through a two-stage multiplexer:

First, as mentioned, the IO_PADSHARE_SEL register selects one of 7 ‘signal groups’. This is, curiously, a global setting, meaning that it affects all of the ‘function share’ pads at once. A huge table in the datasheet describes the function of each such pad’s function in each signal group. For example, the row in the table for PAG_GPIO[0] reads: (Reproduced here vertically for convenience)

Interface               GPIO
Function 0 (Default)    U74_JTAG_TDO
Function 1              GPIO0
Function 2              X2C_TX_DATA3
Function 3              LCD_DATA4
Function 4              X2C_TX_DATA3
Function 5              PLL_RFSLIP[0]
Function 6              MIPITX_MPOSV[0]

Note that GPIOn has no direct correspondence to PAD_GPIO[n]. For example, PAD_FUNC_SHARE[0] can also be connected to GPIO0:

Interface               ChipLink
Function 0 (Default)    X2C_TX_CLK
Function 1              LCD_CLK
Function 2              CM_CLK
Function 3              X2C_TX_CLK
Function 4              GPIO0
Function 5              GPIO0
Function 6              GPIO0

Instead, GPIOn are internal signals further multiplexed into internal inputs and outputs by the ‘GPIO FMUX’. Each of the GPIOn signals also has configurable pull up/down and Schmitt triggers, though not all options are available for all I/O pads.

After that, there are three connections to be made: output, output enable, and input. These are all configured from the destination side, so:

• Each GPIO output and output enable may be connected to either one of the internal outputs or a constant 0 or 1.
• Each internal input may be connected to one of the GPIO inputs, or a constant 0 or 1.

In addition, the input value from each GPIO may be read from MMIO registers GPIODIN_0 and GPIODIN_1, and it’s also possible to configure them to fire interrupts.

This allows quite a flexible usage of the GPIO internal signals. They can be selected to work with the 1.8 V or 3.3 V I/O pads, and can either be fully controlled by software with interrupt support, or connected to one of the internal I2C/I2S/SDIO/… controllers.

Curiously, in Function 0, the default mode for IO_PADSHARE_SEL, none of the GPIOn signals are connected to the I/O pads. Instead, some of the internal signals are directly connected to I/O pads. Moreover, in a few of the cases, the internal signals PAD_GPIO[n] connects to in Function 0 are conveniently also by default connected to the same-numbered internal GPIOn. For example, most relevant to our original use cases, in Function 0 these connections are made:

PAD_GPIO[0]         U74_JTAG_TDO
PAD_GPIO[4]         U74_JTAG_TRSTN

At the same time, the default GPIO FMUX configuration for these JTAG signals are:

CPU_JTAG_TDO        GPIO0
CPU_JTAG_TCK        GPIO1
CPU_JTAG_TDI        GPIO2
CPU_JTAG_TMS        GPIO3
CPU_JTAG_TRSTN      GPIO4

(It seems that CPU_JTAG_* are synonymous with U74_JTAG_*.)

## Finding the JTAG port, take two

It seems that Function 0 is intended for booting and initialization, with many of the internal functions available on I/O pads right away without configuration. However given the lack of, well, actual general purpose input/output, there is no chance Linux runs in Function 0.

We already see our problem: The seven through-holes going to PAD_GPIO[4:0] are JTAG in Function 0 but might not be when another ‘Function’ is selected. This means that at some time after booting, IO_PADSHARE_SEL is set from 0 to some other value, and these JTAG-appearing through-holes would no longer be JTAG.

Which value is it then? Curiously, the example listed in the devicetree documentation has this property:

starfive,signal-group = <6>;

Which would suggest that Linux selects Function 6 at initialization, though the actual jh7100.dtsi did not have this property. The documentation indicates that in case the property is not specified, the IO_PADSHARE_SEL register is left unchanged:

starfive,signal-group:
description: |
Select one of the 7 signal groups. If this property is not set it
defaults to the configuration already chosen by the earlier boot stages.