I successfully configured custom GPIO pins on my Jetson Orin Nano Super using device tree overlays and jetson-io.py. Here’s what worked for me, which may help others in similar situations:
What I Needed
-
Pin 7: Button input (active-low with pull-up)
-
Pins 11, 12, 13: GPIO outputs for RGB LED control
Resources That Helped
-
JetsonHacks GPIO Patch Repository: https://github.com/jetsonhacks/jetson-orin-gpio-patch
-
Excellent reference for pin mappings in headers/tegra234-p3767-0000-common-hdr40.dtsi (didn’t even look at it, CoPilot did)
-
Example DTS structure from pin7_as_gpio.dts
-
Associated YouTube video provides great background on the approach
-
Note: I didn’t need the kernel patch itself, just used it as reference documentation
-
AI-Assisted DTS Generation: Used AI (GitHub Copilot) to generate the custom DTS file based on my pin requirements and the reference files from the repo above.
Critical Issue with Custom/Minimal Images
The jetson-io.py tool needed patching to work with custom-built images!
The stock jetson-io.py script has hardcoded DTB filename patterns that don’t match what is available in the 36.4.4 base. Specifically:
-
It expects files like kernel_tegra234-p3768-0000+p3767-0005-nv-super.dtb when what was available was kernel_tegra234-p3768-0000+p3767-0005-nv.dtb
-
The script also didn’t handle the -super suffix in board names properly (though it falls back gracefully)
Jetson-IO Patch Required:
diff --git a/Jetson/board.py b/Jetson/board.py
index 5308b2d..74d5f12 100644
--- a/Jetson/board.py
+++ b/Jetson/board.py
@@ -147,6 +147,18 @@ def _board_partition_is_root_mountpoint(partlabel):
def _board_get_dtb(compat, model, path):
dtbs = dtc.find_compatible_dtb_files(compat, model, path)
+ # If exact model match fails, try finding DTB by compatible string only
+ # This handles cases like "Engineering Reference" vs regular boards
+ if dtbs is None:
+ # Try matching just by compatible string
+ import glob
+ pattern = os.path.join(path, "kernel*.dtb")
+ for dtb_file in glob.glob(pattern):
+ dtb_compat = dtc.get_compatible(dtb_file)
+ if dtb_compat and any(c in compat for c in dtb_compat.split()):
+ dtbs = [dtb_file]
+ break
+
if dtbs is None:
raise RuntimeError("No DTB found for %s!" % model)
if len(dtbs) > 1:
@@ -216,12 +228,18 @@ class Board(object):
#Finding the active partition in case of redundant rootfs flash.
activepart = syscall.call_out('nvbootctrl -t rootfs get-current-slot')
- if activepart[0] == '0':
+ if activepart and activepart[0]:
+ activepart = activepart[0].strip()
+ else:
+ activepart = None
+
+ if activepart == '0':
mountpart = "APP"
- elif activepart[0] == '1':
+ elif activepart == '1':
mountpart = "APP_b"
else:
- raise RuntimeError("Failed to get active rootfs partition!")
+ # Non A/B system – fall back to using /boot directly
+ mountpart = None
# When mounting the rootfs via NFS, the root partition is not a
# block device. Furthermore, when booting with NFS the partition
@@ -230,7 +248,7 @@ class Board(object):
# not mounted with the active partition, then it is necessary to
# find and mount the active partition and copy the generated
# files back to this partition.
- if not _board_partition_is_root_mountpoint(mountpart):
+ if mountpart is not None and not _board_partition_is_root_mountpoint(mountpart):
self.appdir = _board_partition_mount(mountpart)
dtbdir = os.path.join(self.appdir, 'boot/dtb')
fio.is_rw(self.appdir)
Steps That Worked
1. Create Custom DTS File
/dts-v1/;
/plugin/;
/ {
jetson-header-name = "Jetson 40pin Header";
overlay-name = "My Custom Header 1.0";
compatible = "nvidia,p3768-0000+p3767-0000\0nvidia,p3768-0000+p3767-0001\0nvidia,p3768-0000+p3767-0003\0nvidia,p3768-0000+p3767-0004\0nvidia,p3768-0000+p3767-0005\0nvidia,p3768-0000+p3767-0000-super\0nvidia,p3768-0000+p3767-0001-super\0nvidia,p3768-0000+p3767-0003-super\0nvidia,p3768-0000+p3767-0004-super\0nvidia,p3768-0000+p3767-0005-super\0nvidia,p3509-0000+p3767-0000\0nvidia,p3509-0000+p3767-0001\0nvidia,p3509-0000+p3767-0003\0nvidia,p3509-0000+p3767-0004\0nvidia,p3509-0000+p3767-0005";
fragment@0 {
target = <&pinmux>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&jetson_io_pinmux>;
jetson_io_pinmux: exp-header-pinmux {
hdr40-pin7 {
nvidia,pins = "soc_gpio59_pac6";
nvidia,tristate = <0x1>; /* Enable for input */
nvidia,enable-input = <0x1>; /* Enable input receiver */
nvidia,pull = <0x2>; /* Pull-up resistor */
};
hdr40-pin11 {
nvidia,pins = "uart1_rts_pr4";
nvidia,tristate = <0x0>; /* Enable output driver */
nvidia,enable-input = <0x1>; /* Bidirectional capability */
nvidia,pull = <0x0>; /* No pull resistor */
};
hdr40-pin12 {
nvidia,pins = "soc_gpio41_ph7";
nvidia,tristate = <0x0>;
nvidia,enable-input = <0x1>;
nvidia,pull = <0x0>;
};
hdr40-pin13 {
nvidia,pins = "spi3_sck_py0";
nvidia,tristate = <0x0>;
nvidia,enable-input = <0x1>;
nvidia,pull = <0x0>;
};
};
};
};
};
2. Compile and Install
# Compile DTS to DTBO
dtc -O dtb -o my_custom_header_1.0.dtbo my_custom_header_1.0.dts
# Copy to boot directory
sudo cp my_custom_header_1.0.dtbo /boot/
# Apply patch to jetson-io.py (if jetson-io fails to run)
[Apply the patch above to /opt/nvidia/jetson-io/jetson-io.py]
# Run jetson-io
sudo /opt/nvidia/jetson-io/jetson-io.py
# Select your custom overlay from the menu
# Reboot
sudo reboot
3. Verify
# Check that overlay is loaded
cat /boot/extlinux/extlinux.conf
# Should show: OVERLAYS /boot/my_custom_header_1.0.dtbo
# Test GPIO using libgpiod tools
gpioinfo gpiochip0 | grep -E "line\s+(50|112|122|144)"
Testing GPIO
GPIO Line Mappings:
-
Pin 7 (button) → line 144 (PAC.06)
-
Pin 11 → line 112 (PR.04)
-
Pin 12 → line 50 (PH.07)
-
Pin 13 → line 122 (PY.00)
Test Commands:
# Read button state
gpioget gpiochip0 144
# Set output high
gpioset -m exit gpiochip0 50=1
# Monitor button events
gpiomon --falling-edge gpiochip0 144
Key Learnings
-
Excel sheet not required: While NVIDIA provides pinmux spreadsheets, the header files in the jetsonhacks repo were sufficient for finding pin names
-
Jetson-IO works great once patched: Helpful to patch the system
-
Button debouncing: Mechanical buttons will bounce - handle debouncing in software (50-100ms filtering) - Not sure if I can add something in the dts file which would do this for me - that would be sweet
Working Result
Successfully controlling RGB LEDs via GPIO and reading button presses using standard libgpiod tools (gpioset, gpioget, gpiomon).
Hope this helps anyone else working with custom images or trying to configure GPIO on the Orin platform / DevKit / minimal images.