Jetson/TX1 SPI

This is a guide to enabling and testing SPI port functionality on the Jetson TX1 Developer Kit.

The following procedures were verified using JetPack 2.3 / 2.3.1 and are run from the Jetson terminal.

Configuring GPIO Pinmux for SPI
By default the GPIO pins used for SPI are mapped to an alternate special function, as indicated below under row C (0x1f):

$ sudo cat /sys/kernel/debug/tegra_gpio Name:Bank:Port CNF OE OUT IN INT_STA INT_ENB INT_LVL A: 0:0 24 00 00 24 00 00 000000 B: 0:1 0f 00 00 00 00 00 000000 C: 0:2 1f 00 00 00 00 00 000000 D: 0:3 10 10 00 00 00 00 000000 E: 1:0 70 00 00 00 00 00 000000 F: 1:1 00 00 00 00 00 00 000000 G: 1:2 00 00 00 00 00 00 000000 H: 1:3 ff 1b 0b 40 00 24 002024 I: 2:0 0f 0d 01 02 00 00 000000 J: 2:1 00 00 00 00 00 00 000000 K: 2:2 f0 20 00 d0 00 80 008080 L: 2:3 02 00 00 02 00 02 000000 M: 3:0 00 00 00 00 00 00 000000 N: 3:1 00 00 00 00 00 00 000000 O: 3:2 00 00 00 00 00 00 000000 P: 3:3 00 00 00 00 00 00 000000 Q: 4:0 00 00 00 00 00 00 000000 R: 4:1 00 00 00 00 00 00 000000 S: 4:2 f0 f0 00 00 00 00 000000 T: 4:3 03 03 00 00 00 00 000000 U: 5:0 0c 00 00 08 00 00 000000 V: 5:1 6e 66 00 00 00 00 000000 W: 5:2 00 00 00 00 00 00 000000 X: 5:3 ff 00 00 fe 00 70 606000 Y: 6:0 03 00 00 03 00 01 010100 Z: 6:1 1f 08 00 17 00 07 030300 AA: 6:2 00 00 00 00 00 00 000000 BB: 6:3 0d 04 00 00 00 00 000000 CC: 7:0 32 30 20 20 00 02 020200 DD: 7:1 00 00 00 00 00 00 000000 EE: 7:2 00 00 00 00 00 00 000000 FF: 7:3 00 00 00 00 00 00 000000

To properly map the GPIO pins to SPI, the following segment must be added to the Device Tree:

+       gpio: gpio@6000d000 { +               gpio_default: default { +                      gpio-to-sfio = ; +              };

To do that, we will decompile the Jetson TX1's existing Device Tree, add the patch, and recompile the Device Tree.

Installing DTC Tool
Recompiling the kernel is not required for editing/updating the Device Tree and can be done from onboard the Jetson. To do that, first install the Device Tree Compiler (DTC) toolchain package from the Ubuntu aarch64 repository:

$ sudo apt-get update $ sudo apt-get install device-tree-compiler

Locating the Active DTB
Device Tree Binary (DTB) files are located in /boot. The particular DTB loaded during boot is configured in /boot/extlinux/extlinux.conf under the 'FDT' entry:

$ cat /boot/extlinux/extlinux.conf TIMEOUT 30 DEFAULT primary MENU TITLE p2371-2180 eMMC boot options LABEL primary MENU LABEL primary kernel LINUX /boot/Image INITRD /boot/initrd FDT /boot/tegra210-jetson-tx1-p2597-2180-a01-devkit.dtb APPEND fbcon=map:0 console=tty0 console=ttyS0,115200n8 androidboot.modem=none androidboot.serialno=P2180A00P00940c003fd androidboot.security=non-secure tegraid=21.1.2.0.0 ddr_die=2048M@2048M ddr_die=2048M@4096M section=256M memtype=0 vpr_resize usb_port_owner_info=0 lane_owner_info=0 emc_max_dvfs=0 touch_id=0@63 video=tegrafb no_console_suspend=1 debug_uartport=lsport,0 earlyprintk=uart8250-32bit,0x70006000 maxcpus=4 usbcore.old_scheme_first=1 lp0_vec=${lp0_vec} nvdumper_reserved=${nvdumper_reserved} core_edp_mv=1125 core_edp_ma=4000 gpt android.kerneltype=normal androidboot.touch_vendor_id=0 androidboot.touch_panel_id=63 androidboot.touch_feature=0 androidboot.bootreason=pmc:software_reset,pmic:0x0 net.ifnames=0 root=/dev/mmcblk0p1 rw rootwait

This line indicates the file to reference in the following step when the DTB is decompiled to source.

Extracting DTB to Source
Using the DTC tool installed above, extract the DTB back into human-readable Device Tree Source (DTS) file:

$ dtc -I dtb -O dts -o ~/spi_test/extracted.dts /boot/tegra210-jetson-tx1-p2597-2180-a01-devkit.dtb

In this example, the intermediate files are stored in a user-defined folder that was created (in this case /home/ubuntu/spi_test) Look through the extracted DTS text to verify the translation is complete.

Patching the New DTS
Open the extracted DTS in text editor and locate the gpio@6000d000 entry (line ~2762), and append the following snippet to the entry:

gpio_default: default { gpio-to-sfio = ; };

The gpio@6000d000 entry should now reflect the following:

gpio@6000d000 { compatible = "nvidia,tegra210-gpio", "nvidia,tegra124-gpio", "nvidia,tegra30-gpio"; reg = <0x0 0x6000d000 0x0 0x1000>; interrupts = <0x0 0x20 0x4 0x0 0x21 0x4 0x0 0x22 0x4 0x0 0x23 0x4 0x0 0x37 0x4 0x0 0x57 0x4 0x0 0x59 0x4 0x0 0x7d 0x4>; #gpio-cells = ; gpio-controller; #interrupt-cells = ; interrupt-controller; gpio-ranges = ; status = "okay"; gpio-init-1 = ; linux,phandle = ; phandle = ; wlan-default { gpio-input = ; gpio-output-high = ; linux,phandle = ; phandle = ; }; 		camera_control { gpio-output-low = ; gpio-output-high = ; }; 		camera-control { gpio-output-low = ; }; 		''' gpio_default: default { gpio-to-sfio = ; }; ''' };

We saved this edited file as ~/spi_test/modified-gpio.dts

Recompiling the New DTB
Use the DTC tool again the generate the new DTB from the updated DTS:

$ dtc -I dts -O dtb -o ~/spi_test/modified-gpio.dtb ~/spi_test/modified-gpio.dts

Booting with the New DTB
Although we have compiled the new DTB, the system is still configured to boot from the old DTB.

First copy the new DTB into /boot, in this example using a naming convention consistent with the existing DTB's with the addition of a -gpio suffix to indicate our changes:

$ sudo cp /home/ubuntu/spi_test/modified-gpio.dtb /boot/tegra210-jetson-tx1-p2597-2180-a01-devkit-gpio.dtb

Then update /boot/extlinux/extlinux.conf in text editor to reflect using the new DTB. The updated extlinux.conf should now look like so:

$ cat /boot/extlinux/extlinux.conf TIMEOUT 30 DEFAULT primary MENU TITLE p2371-2180 eMMC boot options LABEL primary MENU LABEL primary kernel LINUX /boot/Image INITRD /boot/initrd FDT  /boot/tegra210-jetson-tx1-p2597-2180-a01-devkit-gpio.dtb  APPEND fbcon=map:0 console=tty0 console=ttyS0,115200n8 androidboot.modem=none androidboot.serialno=P2180A00P00940c003fd androidboot.security=non-secure tegraid=21.1.2.0.0 ddr_die=2048M@2048M ddr_die=2048M@4096M section=256M memtype=0 vpr_resize usb_port_owner_info=0 lane_owner_info=0 emc_max_dvfs=0 touch_id=0@63 video=tegrafb no_console_suspend=1 debug_uartport=lsport,0 earlyprintk=uart8250-32bit,0x70006000 maxcpus=4 usbcore.old_scheme_first=1 lp0_vec=${lp0_vec} nvdumper_reserved=${nvdumper_reserved} core_edp_mv=1125 core_edp_ma=4000 gpt android.kerneltype=normal androidboot.touch_vendor_id=0 androidboot.touch_panel_id=63 androidboot.touch_feature=0 androidboot.bootreason=pmc:software_reset,pmic:0x0 net.ifnames=0 root=/dev/mmcblk0p1 rw rootwait

Then reboot the system to have the changes take effect.

$ sudo reboot

Verifying GPIO PinMux
After rebooting, check tegra_gpio again, and now row C should be changed to 0x00, verifying those GPIO pins are now mapped to SPI mode:

$ sudo cat /sys/kernel/debug/tegra_gpio Name:Bank:Port CNF OE OUT IN INT_STA INT_ENB INT_LVL A: 0:0 24 00 00 24 00 00 000000 B: 0:1 0f 00 00 00 00 00 000000 C: 0:2 00 00 00 00 00 00 000000 D: 0:3 10 10 00 00 00 00 000000 E: 1:0 70 00 00 00 00 00 000000 F: 1:1 00 00 00 00 00 00 000000 G: 1:2 00 00 00 00 00 00 000000 H: 1:3 ff 1b 0b 40 00 24 002024 I: 2:0 0f 0d 01 02 00 00 000000 J: 2:1 00 00 00 00 00 00 000000 K: 2:2 f0 20 00 d0 00 80 008080 L: 2:3 02 00 00 02 00 02 000000 M: 3:0 00 00 00 00 00 00 000000 N: 3:1 00 00 00 00 00 00 000000 O: 3:2 00 00 00 00 00 00 000000 P: 3:3 00 00 00 00 00 00 000000 Q: 4:0 00 00 00 00 00 00 000000 R: 4:1 00 00 00 00 00 00 000000 S: 4:2 f0 f0 00 00 00 00 000000 T: 4:3 03 03 00 00 00 00 000000 U: 5:0 0c 00 00 08 00 00 000000 V: 5:1 6e 66 00 00 00 00 000000 W: 5:2 00 00 00 00 00 00 000000 X: 5:3 ff 00 00 fe 00 70 606000 Y: 6:0 03 00 00 03 00 01 010100 Z: 6:1 1f 08 00 17 00 07 030300 AA: 6:2 00 00 00 00 00 00 000000 BB: 6:3 0d 04 00 00 00 00 000000 CC: 7:0 32 30 20 20 00 02 020200 DD: 7:1 00 00 00 00 00 00 000000 EE: 7:2 00 00 00 00 00 00 000000 FF: 7:3 00 00 00 00 00 00 000000

The SPI ports can now be used from the kernel. To test them from userspace, we will install spidev and a loopback test.

Installing SPIdev Kernel Module
The Linux [|SPIdev] driver provides a user interface for accessing SPI to userspace processes and applications. Although SPIdev is not compiled into the L4T kernel by default, it can be compiled and loaded as a kernel module. This is able to be done from onboard the Jetson without recompiling the entire kernel or can be cross-compiled from x86 host if desired.

Downloading Kernel Sources
The source code for SPIdev module and the loopback tests are included in the L4T kernel source code. The following will download and extract the tree used to build the kernel module:

$ wget --no-check-certificate 'https://developer.nvidia.com/embedded/dlc/l4t-sources-24-2-1' -O sources_r24.2.1.tbz2 $ tar -xvf sources_r24.2.1.tbz2 $ cd sources $ tar -xvf kernel_src.tbz2 $ cd kernel

Updating the Kernel Config
First, get a copy of the Jetson's existing kconfig to start from:

$ zcat /proc/config.gz > .config

Next, enable building SPIdev as module, and set the kernel LOCALVERSION:

$ sed -i 's/# CONFIG_SPI_SPIDEV is not set/CONFIG_SPI_SPIDEV=m/' .config $ sed -i 's/CONFIG_LOCALVERSION=""/CONFIG_LOCALVERSION="-tegra"/' .config

Building the SPIdev Module
Run these commands to configure the source tree and build the SPI modules, without having to compile the kernel:

$ make prepare $ make modules_prepare $ make M=drivers/spi/

Installing the SPIdev Module
Copy the built .ko object into /lib/modules and reboot:

$ sudo cp drivers/spi/spidev.ko /lib/modules/$(uname -r)/kernel/drivers $ sudo depmod $ sudo reboot

Enabling SPIdev in the Device Tree
Although the spidev kernel module may be loaded and device node created, the associated SPI channel(s) have not yet been mapped to use spidev driver in the Device Tree.

To bind a Tegra SPI channel to the spidev0.0 node, append the following block to the particular SPI port in the DTS (i.e. spi@7000d400)

spi0_0 { #address-cells = ; #size-cells = ; compatible = "spidev"; reg = ; spi-max-frequency = ; nvidia,enable-hw-based-cs; nvidia,cs-setup-clk-count = ; nvidia,cs-hold-clk-count = ; nvidia,rx-clk-tap-delay = ; nvidia,tx-clk-tap-delay = ; };

As Configured for the Devkit
The SPI1 channel that is accessible from pins 19 & 21 of the J21 Expansion header on the Jetson TX1 Developer Kit corresponds to device spi@7000d400 in the Device Tree. This is what the updated section of the DTS looks like:

spi@7000d400 { compatible = "nvidia,tegra210-spi"; reg = <0x0 0x7000d400 0x0 0x200>; interrupts = ; nvidia,dma-request-selector = ; iommus = ; #address-cells = ; #size-cells = ; dmas = ; dma-names = "rx", "tx"; nvidia,clk-parents = "pll_p", "clk_m"; status = "okay"; prod-settings { prod { prod = ; }; 			prod_c_flash { status = "disabled"; prod = ; }; 			prod_c_loop { status = "disabled"; prod = ; }; 		}; 		 '''spi0_0 { #address-cells = ; #size-cells = ; compatible = "spidev"; reg = ; spi-max-frequency = ; nvidia,enable-hw-based-cs; nvidia,cs-setup-clk-count = ; nvidia,cs-hold-clk-count = ; nvidia,rx-clk-tap-delay = ; nvidia,tx-clk-tap-delay = ; }; ''' };

We edited the patched DTS which already had the gpio-to-sfio directives included from before, and added the spi0_0 block to spi@7000d400 above. We saved the new DTS file as ~/spi_test/modified-gpio-spi0.dts. Next, recompile the DTB again, now with spidev enabled.

Compiling the DTB for SPIdev
As before, build the DTB again, now with spidev enabled on J21 Expansion header for the devkit. As per the previous section, we're calling this one modified-gpio-spi0.dtb

$ dtc -I dts -O dtb -o ~/spi_test/modified-gpio-spi0.dtb ~/spi_test/modified-gpio-spi0.dts $ sudo cp ~/spi_test/modified-gpio-spi0.dtb /boot/tegra210-jetson-tx1-p2597-2180-a01-devkit-gpio-spi0.dtb

Booting the DTB with SPIdev
As before, extlinux.conf needs changed over to load the new DTB at boot. The updated extlinux.conf should now look like:

$ cat /boot/extlinux/extlinux.conf TIMEOUT 30 DEFAULT primary MENU TITLE p2371-2180 eMMC boot options LABEL primary MENU LABEL primary kernel LINUX /boot/Image INITRD /boot/initrd FDT  /boot/tegra210-jetson-tx1-p2597-2180-a01-devkit-gpio-spi0.dtb  APPEND fbcon=map:0 console=tty0 console=ttyS0,115200n8 androidboot.modem=none androidboot.serialno=P2180A00P00940c003fd androidboot.security=non-secure tegraid=21.1.2.0.0 ddr_die=2048M@2048M ddr_die=2048M@4096M section=256M memtype=0 vpr_resize usb_port_owner_info=0 lane_owner_info=0 emc_max_dvfs=0 touch_id=0@63 video=tegrafb no_console_suspend=1 debug_uartport=lsport,0 earlyprintk=uart8250-32bit,0x70006000 maxcpus=4 usbcore.old_scheme_first=1 lp0_vec=${lp0_vec} nvdumper_reserved=${nvdumper_reserved} core_edp_mv=1125 core_edp_ma=4000 gpt android.kerneltype=normal androidboot.touch_vendor_id=0 androidboot.touch_panel_id=63 androidboot.touch_feature=0 androidboot.bootreason=pmc:software_reset,pmic:0x0 net.ifnames=0 root=/dev/mmcblk0p1 rw rootwait

This time, shut down the system so we can install the loopback jumper. Upon start-up, the DTB changes should have taken effect.

$ sudo shutdown -a now

Verifying SPIdev has Loaded
After rebooting, the kernel should automatically have loaded spidev.ko.

You can check this by listing the currently loaded modules with lsmod:

$ lsmod Module                 Size  Used by  bnep                   14896  2 bcmdhd              7465032  0 cfg80211             452899  1 bcmdhd spidev                8679  0  bluedroid_pm          11420  0

There should also be a device node created under /dev called /dev/spidev0.0

$ ls /dev/spi* /dev/spidev0.0 

Loopback Testing
A simple loopback test is included in the kernel source. After the SPI pins MOSI/MISO are shorted via jumper or a short wire, the tool verifies that MOSI/MISO values are transmitted/received the same. This is useful for verifying data integrity of the Jetson setup before connecting 3rd-party SPI peripherals.

Installing Loopback Wire
The Jetson TX1 Developer Kit includes the SPI1 channel on it's J21 Expansion header. The SPI1 MOSI/MISO signals are pins 19 & 21 on J21.

With the system shutdown, install a jumper or short wire (<10cm) between pins 19 & 21 to complete the circuit for the loopback test.

Compiling the Loopback Test
The loopback test source code is included in the kernel tree used above to the compile SPIdev module. If you go back to where you downloaded and extract the L4T source to, the loopback test is located under sources/kernel/Documentation/spi.

A simple gcc command compiles it:

$ cd sources/kernel/Documentation/spi $ gcc -o spidev_test spidev_test.c

Running the Loopback Test
The following command will initiate loopback testing using spidev0.0 $ sudo ./spidev_test -D /dev/spidev0.0 spi mode: 0 bits per word: 8 max speed: 500000 Hz (500 KHz) FF FF FF FF FF FF 40 00 00 00 00 95 FF FF FF FF FF FF FF FF FF FF FF FF  FF FF FF FF FF FF  DE AD BE EF BA AD  F0 0D

The above output indicates that the test completed successfully, and the SPI channel data integrity is confirmed OK.