Not Understanding GPIO Configuration Registers

I’m trying to write a GPIO driver for the VisionFive 2. The JH7110 TRM is very confusing to me and contains very little detail on how the GPIO system works. Even after reading the kernel driver source and the examples in the TRM, I still don’t understand how to properly configure a GPIO pin.

The most confusing thing to me is OEN and the OEN Table. What does this table mean, and what is the difference between the DOUT and DOEN register?

The GPIO output example (TRM and the kernel driver write 0 to OEN to configure the direction to “output”, but shouldn’t it be 1 (like Output Enable = 1)?

//direction = output
modl(regs->doen+n, GPIO_DOEN_MASK<<shift, 0<<shift);

Does somebody understand the GPIO configuration and can help me out?

1 Like

I don’t know what code you’re looking at, but find the definition of ‘modl’. I’ll bet you’ll find that the arguments are, in order, register, bits to set, bits to clear.

Why would someone shift a zero? That’s probably a copy-paste-o.

Googling around, which is hard for a term that looks like a typo itself, I’m referencing and that doc doesn’t provide a definition and the use of it is so inconsistent that it’s hard to say. (“modify long”?) There’s usually a function or macro in systems level code that allow this kind of operation all in a single store, for example.

The prose is also inconsistent, so let’s try to find a better example from Starfive themselves. Let’s try


I think the latter example is more self-contained and easier to read for educational value. We can at least find a clue that clrsetbits_le32 is indeed (register, bits to set, bits to clear)

I think you’re asking more what is “OEN” in this context. This chip has an amazing mux which allows physical pins to be different things. For the GPIO case, a physical pin can be an output OR an input. Hopefully clearly, if you configured it to be both at the same time, readings would be trash because you’re driving the pin - which also means you’re connecting two outputs together, which isn’t great.

So imagine OEN like a switch between the outputs and the actual pin. Using DOUT, you can drive a pin high or low, but only if the output is enabled does that output actually leave the chip. Most environments will leave chips like this configured with the outputs disabled, driving the line neither hign nor low, to reduce current flow and/or the chance of damage of having multiple outputs driving each other in case that trace is routed to something that’ll be driving that trace by default.

So there’s the Always On MUX and System MUX at 2.8.7 ad 2.8.8 in the doc that you have to configure for the port.

starfive_gpio_direction_input() then sets the bits for GPIO_DOEN_MASK for that gpio line AND clears the bits as given by the pattern in table 2-41.

starfive_gpio_direction_output() does pretty much the same thing, but uses a zero in that shift - as shown in your “direction = output” case above and then does another register write to actually set the output itself high or low. So the first store turns on that switch we talked about, connecting the outputs to be driven and then the second actually flips the off/on switch for that line.

Unfortunately, between all the code snippets using names that don’t match the actual TRM, the difficulty of finding definitions for the macros, and the failure to really describe the actual patterns in the registers so you can see that the registers hold arrays of bitfields per port, I think it’s hard for a developer to really see the patterns here to run forward with the next implementation.

It’s been a very long time since I’ve looked at all this, so I could be off in the details, but hopefully the additional references to code and doc will help clear this all up.


Thank you for your detailed answer! The u-boot-source is a very good reference, it shows the same behavior as the Linux kernel driver but is a little easier to read:

Ok, so if OEN would be just 1 bit, I get it. From the u-boot and Linux source, we know that OEN = 0 for output configuration and OEN = 1 for input (right?). I don’t know how this is represented by table 2-41 or anywhere else in the TRM.
But unfortunately, OEN is not just 1 bit. What are the other values doing?

Lets for example configure a GPIO pin to output the PWM0 signal. With my current understanding, I would set OEN = 0 (for output mode) and DOUT = 24 (for u0_pwm_8ch_ptc_pwm[0] from table 2-41). With DOUT I configure the source I want to output which is PWM0. But in the Linux device tree, OEN is also set to PWM0 and not just 0. Why?

Another place to look might be in
I have not checked but I would assume that the GPIO configuration code would be extremely similar between the JH7100 and the JH7110.

The code is not actually used:
“// gpio_init();”

But the files I would look at are:

I would expect that the overall process would be the same, but the addresses, pins, and functions are probably different. So do not just blindly copy and paste code from the JH7100 and expect it to work on the JH7110. Validate everything.


As a supplement to the StarFive documentation have a look at GPIO


Another place to look, might be the recently updated OpenBSD code, which should initially allow partial support for JH7110 based boards probably in OpenBSD 7.4 (2023-10) or, if that release window is missed for whatever reason, possibly in OpenBSD 7.5 (2024-04).

For reference the VisionFive V1 board initially had some code added in 7.2 (2022-10: stfclock driver - StarFive JH7100 clock controller; stfpinctrldriver - StarFive JH7100 pin configuration; stftemp driver - StarFive JH7100 temperature sensor) and then 7.3 (2023-04) support was added for the StarFive VisionFive V1 board.

Since lots of groundwork has been done, there are large blocks of low level code that are common to both the JH7100 and JH7110, I suspect that the StarFive VisionFive 2 might be partially supported in OpenBSD 7.4 (2023-10), but I could be wrong.