Enabling a hidden UART port through kernel device tree modifications.

Context

When developing custom carriers for embedded modules/computers, or perhaps, want to mess around with the device low-level configuration, new pin assignments and functions often need to be created. However, this is not a straightforward process. It requires a significant amount of knowledge about Yocto, the specific BSP layer you’re working with (such as meta-tegra, etc.), and the Linux kernel itself.

In this post, I’ll walk you through a simple process I used to add support for a new UART on a custom Linux image built with Yocto, on the NVIDIA Orin NX, by modifying the kernel device tree.

Setup

The Orin NX module has three UART interfaces:

uart_datasheet

Module UART# SOC UART# Used Pins <uart*>@<address> Usage
UART0 SOC UART2 PX04, PX05, PX06, PX07 uartb@3110000 serial1 Unused
UART1 SOC UART1 PR02, PR03, PR04, PR05 uarta@3100000 serial0 /dev/ttyTHS0
UART2 SOC UART3 PCC05, PCC06 uartc@c280000 serial2 /dev/ttyTCU0

The goal here is to enable uartb, as it’s disabled by default, so we can have three functional UARTs in our module!

Device tree

A Linux Device Tree is a data structure used to describe the hardware components of a system, such as CPUs, memory, buses, and peripherals, so that the operating system can configure and use the hardware properly. It is typically represented in .dts (source) and .dtb (binary) files.

The first step is to identify the device tree that our board uses. This can be done by checking the machine configuration in the Yocto BSP layer. In this case, I’m using an Orin NX 16 GB, so I referred to p3768-0000-p3767-0000.conf file in the meta-tegra repository.

Diving into the Kernel

Next, we need to dive deeper into the kernel to search for the existing UART nodes, allowing us to add the new one. This layer uses a custom kernel, OE4T/linux-tegra. Let’s tart by examining what the device tree includes. For instance, the tegra234-p3767-0000-p3768-0000-a0.dts file includes cvb/tegra234-p3768-0000-a0.dtsi, which contains the following relevant sections:

serial@3100000 {/* UARTA, for 40 pin header */
    status = "okay";
};

mttcan@c310000 {
    status = "okay";
};

serial@3140000 {
    /* UARTE, Goes to M2.E and also some of the pins to bootstrap */
    status = "okay";
};

serial@31d0000 {/* UARTI - SBSA */
    status = "okay";
};

So, huh, easy right? Adding the following should just work:

serial@3110000 {/* UARTB */
    status = "okay";
};

Wait… Make sure that the address serial@3110000 is not being used by other nodes or verify by checking if the same device pins are not used by another node (you can find this information in the pinmux excel sheet or data sheet).

After some search, only uartb uses it.

uartb: serial@3110000 {
	compatible = "nvidia,tegra194-hsuart";
	iommus = <&smmu_niso0 TEGRA_SID_NISO0_GPCDMA_0>;
	dma-coherent;
	reg = <0x0 0x03110000 0x0 0x10000>;
	reg-shift = <2>;
	interrupts = <0 TEGRA234_IRQ_UARTB 0x04>;
	nvidia,memory-clients = <14>;
	dmas = <&gpcdma 9>, <&gpcdma 9>;
	dma-names = "rx", "tx";
	clocks = <&bpmp_clks TEGRA234_CLK_UARTB>,
		<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
	clock-names = "serial", "parent";
	resets = <&bpmp_resets TEGRA234_RESET_UARTB>;
	reset-names = "serial";
	status = "disabled";
};

Applying Kernel patch to Yocto

So, to apply this in our custom Linux image, I usually do the hacky-patch-way.

git add .
git commit -m "add uart b"
git format-patch -1

then, you can create something like linux-tegra_5.10%.bbappend and patch as the usual

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI += "file://0001-add-uart-b.patch"

Did it work?

root@p3768-0000-p3767-0000:~# dmesg | grep tty
[    0.000000] Kernel command line: mminit_loglevel=4 console=ttyTCU0,115200 firmware_class.path=/etc/firmware fbcon=map:0 net.ifnames=0 nospectre_bhb ipv6.disable=1 audit=0 ipv6.disable=1 audit=0 
[    0.264673] 31d0000.serial: ttyAMA0 at MMIO 0x31d0000 (irq = 66, base_baud = 0) is a SBSA
[    1.873125] printk: console [ttyTCU0] enabled
[    3.746453] 3100000.serial: ttyTHS0 at MMIO 0x3100000 (irq = 17, base_baud = 0) is a TEGRA_UART
[    3.761178] 3110000.serial: ttyTHS1 at MMIO 0x3110000 (irq = 63, base_baud = 0) is a TEGRA_UART
[    3.776142] 3130000.serial: ttyTHS3 at MMIO 0x3130000 (irq = 64, base_baud = 0) is a TEGRA_UART
[    3.790859] 3140000.serial: ttyTHS4 at MMIO 0x3140000 (irq = 65, base_baud = 0) is a TEGRA_UART
[    6.545598] systemd[1]: Created slice Slice /system/getty.
[    6.548166] systemd[1]: Created slice Slice /system/serial-getty.

As you can see, we have a new UART port at /dev/ttyTHS1:

[    3.761178] 3110000.serial: ttyTHS1 at MMIO 0x3110000 (irq = 63, base_baud = 0) is a TEGRA_UART

Cool!

References

updated_at 03-09-2024