GPIO and SPI – NVIDIA Jetson TX1

In a post on the NVIDIA Jetson TX1 forum, Wilkins White (Atrer) from Nova Dynamics (ww@novadynamics.com) wrote up a quite wonderful explanation of how to enable SPI on the Jetson TX1. The SPI interface is used in the discussion to interface with a MCP2515 CAN Bus Module (CAN Bus is a vehicle bus standard. Bus as in computer bus in a vehicle, not the big yellow thing).

In the discussion, Wilkins explains not only how to enable SPI, but also how to decipher and process the GPIO mappings from the pinmux and the kernel source code to setup the board DTSI file. All good fun. We’re reprinting it here because we think it is good information to share throughout the community. The process is similar on all of the Jetson family. Here’s the post:

Forum Post

Looks like NVIDIA has integrated CAN into the TX2, but the rest of us have to do it the old fashioned way. Here’s a quick guide on how to get an MCP2515 working with the TX1 development board.

Special Note: The TX1’s SPI logic level is 1.8V, the MCP2515 runs on 3.3V and expects a logic level of 0.9*VDD (~2.97V) to latch. Thus you’ll need a level shifter between the TX1 and the MCP2515 to bridge the gap if you’re using the Display Expansion connector (SPI0 and SPI2). It is possible that J21 SPI1 can be set to 3.3V by setting the jumper on J24, but I haven’t tested that.

Here are the attachment files:
tegra210-daxc03.dtsi
board-t210ref

Kernel Configuration

In order for the MCP2515 to work the kernel needs to have both SPI and the MCP251x drivers enabled. First step is becoming familiar with the process for building the kernel. Ridgerun has an excellent guide on the subject here: https://developer.ridgerun.com/wiki/index.php?title=Compiling_Tegra_X1_source_code

Run through that guide until you get to Build Kernel step 5:

make tegra21_defconfig
make menuconfig

The configurations that need to be set are the following:

  • CONFIG_SPI_SPIDEV=y
  • CONFIG_CAN_DEV=y
  • CONFIG_CAN_MCP251X=y

For menuconfig that means this:
< blockquote >Device Drivers →
  <*> SPI support →
    <*> User mode SPI device driver support

<*> Networking support →
  <*> CAN bus subsystem support →
  CAN Device Drivers →
    <*> Microchip MCP251x SPI CAN controllers

Once the kernel is configured we need to update the dtsi and edit the TX1 board file so it knows where to find our MCP2515.

Device Tree

Next step, which may not be necessary for using the MCP2515, but is useful for debugging, is to enable spidev in the device tree. This allows you to manipulate the SPI port using tools such as the SPIDEV kernel module or the py-spidev python package (https://github.com/doceme/py-spidev).

The TX1 loads a device tree blob (dtb) on boot which contains configuration information such as clock speeds, register settings, and pin defaults. To find out what file your board uses you can check dmesg.

$ dmesg | grep .dts
[ 0.000000] DTS File Name: arch/arm64/boot/dts/tegra210-jetson-tx1-p2597-2180-a01-devkit.dts

Now navigate your kernel_source folder. And take a look at that dts file.

$ cd $DEVDIR/64_TX1/Linux_for_Tegra_64_tx1/sources/kernel_source/
$ vim arch/arm64/boot/dts/tegra210-jetson-tx1-p2597-2180-a01-devkit.dts

After the copyright information you’ll see an include line. Insert your own include with the SPI configuration below that one. If you’re using my dtsi it would look like this:

/*
* arch/arm64/boot/dts/tegra210-jetson-tx1-p2597-2180-a01-devkit.dts
*
* Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/

#include “tegra210-jetson-cv-base-p2597-2180-a00.dts”
#include “tegra210-daxc02.dtsi”

Don’t forget to actually put that file (or a link to the file) in the folder so it can be included.

If you want to modify the device tree in place (on the TX1) you can find instructions at the following link: http://elinux.org/Jetson/TX1_SPI

GPIO and SFIO

Selecting which GPIO to use (and figuring out which ones are available) can be tricky. In this section I’ll talk about GPIO and SFIO, pin mappings, and walk through enabling access to the SPI port on the J21 header.

The majority of pins on the TX1, and most embedded devices for that matter, can be configured to either source or sink current. They are aptly called “General Purpose Input Output” or GPIO. Sometimes these pins are connected to specialized internal circuitry for purposes such as analog to digital conversion, generating waveforms, or handling protocols such as UART, SPI, or I2C. When a pin has circuitry like that it is called a “Specific Function Input Output” or SFIO. The device tree is responsible for telling the TX1 how every single one of those pins is to be configured. Either as an output (source current), an input (sink current), or SFIO.

To start figuring out the TX1 GPIO system you’ll want to jump over to the download center and grab “Jetson TX1 Module Pinmux” (http://developer.nvidia.com/embedded/dlc/jetson-tx1-module-pinmux). This spreadsheet was created as a customer reference showing how the TX1 pins are configured. It even has scripts to allow you to generate a new gpio defaults dtsi, but I prefer to do everything in my own dtsi file. Mux is shorthand for “multiplexor” which is the device that handles selecting which internal circuitry a pin is connected to.

Column A shows the pin names and columns G-K show the GPIO and SFIO mappings. If we scroll down to the SPI section we can see that the SPI1 bus, which is connected to the J21 header, can be multiplxed to either GPIO PC1-4, or SPI. We can also see on column AN that GPIO is selected by default. Thus to use the port as SPI we need to configure those pins to be SFIO instead of GPIO.

“GPIO3_PC.00” probably doesn’t mean much to you. Luckily there is a mapping buried in the kernel source.

$ cd $DEVDIR/64_TX1/Linux_for_Tegra_64_tx1/sources/kernel_source/
$ less arch/arm/mach-tegra/gpio-names.h

With the gpio-names file we can cross reference the GPIO names with the actual pin numbers.

#define TEGRA_GPIO_PC0 16
#define TEGRA_GPIO_PC1 17
#define TEGRA_GPIO_PC2 18
#define TEGRA_GPIO_PC3 19
#define TEGRA_GPIO_PC4 20

Once we have that information, switching the pins to sfio is as easy as calling “gpio-to-sfio” in gpio defaults like in my example dtsi.

gpio@6000d000 {
/* Needed for J21 Header SPI1 */
gpio_default: default {
gpio-input = <170 174 185>; // Set PV2, PV6, and PX1 to input
gpio-to-sfio = <16 17 18 19 20>; // J21 Header SPI1
};
};

This sets three pins, 170, 174, and 185 to inputs, and converts 5 pins, 16-20, to SFIO.

Choosing an interrupt GPIO

Now that we have these two documents we can choose pins for our MCP2515 interrupts. Lets say we want to stick with the J21 header. If we take a look at the header pinout (http://www.jetsonhacks.com/nvidia-jetson-tx1-j21-header-pinout/) we can see that several pins are helpfully already labeled as GPIO. Most of these have some other label such as GPIO9_MOTION_INT, but these are just suggestions. A GPIO is a GPIO, you can use it for whatever you would like.

Say we wanted to use J21 pin 31 (GPIO9_MOTION_INT) as our interrupt pin. Pull up the Pinmux spreadsheet and search for GPIO9. You should find something like “GPIO9/MOTION_INT” in column A. Scrolling over to column G shows that it is mapped to “GPIO3_PX.02”

Now check /arch/arm/mach-tegra/gpio-names.h for the pin number.

$ cd $DEVDIR/64_TX1/Linux_for_Tegra_64_tx1/sources/kernel_source/
$ cat arch/arm/mach-tegra/gpio-names | grep PX2

Using that number (in this case 186) you can set that gpio to an input in the dtsi and use it for your interrupt in the board-file, below. Note that gpio-names.h is compiled into the kernel. So for the board file you can use the define in place of the pin number. Ex: TEGRA_GPIO_PX2

Board File

The version of the MCP251x driver included in the kernel does not use the device tree for its configuration. Thus the TX1’s board file needs to be modified to add the driver’s private data structs.

This board file compiled into the TX1 kernel is located here:

kernel_source/arch/arm64/mach-tegra/board-t210ref.c

First define the MCP2515 private data structures and init function. You may need to make the following changes to my definitions:

  • Change the irq to whichever GPIO pin you are using
  • Switch the oscillator_frequency to match the crystal you have connected to the mcp2515
  • Change the bus_num to the SPI port you are using, see my dtsi file for mapping

#ifdef CONFIG_CAN_MCP251X
#include <linux/can/platform/mcp251x.h>
#define CAN_GPIO_IRQ_MCP251x_SPI TEGRA_GPIO_PV6

static struct mcp251x_platform_data mcp251x_info = {
.oscillator_frequency = 16 * 1000 * 1000, /* Oscillator connected to the MCP2515 crystal */
.board_specific_setup = NULL, /* We don’t have a board specific setup */
.power_enable = NULL, /* We don’t want any power enable function */
.transceiver_enable = NULL, /* We don’t want any transceiver enable function */
};

struct spi_board_info mcp251x_spi_board[1] = {
{
.modalias = “mcp2515”, /* (or mcp2510) used chip controller */
.platform_data = &mcp251x_info, /* reference to the mcp251x_platform_data mcp251x_info */
.max_speed_hz = 2 * 1000 * 1000, /* max speed of the used chip */
.chip_select = 0, /* the spi cs usage*/
.bus_num = 1, // SPI0
.mode = SPI_MODE_0,
},
};

static int __init mcp251x_init(void)
{
mcp251x_spi_board[0].irq = gpio_to_irq(CAN_GPIO_IRQ_MCP251x_SPI); // #define CAN_GPIO_IRQ_MCP251x_SPI TEGRA_GPIO_PK2
spi_register_board_info(mcp251x_spi_board, ARRAY_SIZE(mcp251x_spi_board));
pr_info(“mcp251x_init\n”);
return 0;
}

#endif

Next add the init function to t210ref_late_init;

static void __init tegra_t210ref_late_init(void)
{
struct board_info board_info;
tegra_get_board_info(&board_info);
pr_info(“board_info: id:sku:fab:major:minor = 0x%04x:0x%04x:0x%02x:0x%02x:0x%02x\n”,
board_info.board_id, board_info.sku,
board_info.fab, board_info.major_revision,
board_info.minor_revision);

t210ref_usb_init();
tegra_io_dpd_init();
#ifdef CONFIG_PM_SLEEP
/* FIXME: Assumed all t210ref platforms have sdhci DT support */
t210ref_suspend_init();
#endif
tegra21_emc_init();
isomgr_init();

#ifdef CONFIG_CAN_MCP251X
mcp251x_init();
#endif

/* put PEX pads into DPD mode to save additional power */
t210ref_camera_init();
}

Once you have those changes made go ahead and finish the Ridgerun guide (step 6+) and finish compiling and flashing the kernel.

Bringing up the Interface

The MCP2515 should appear as a can0 interface under ifconfig -a

To bring it up use the following commands (change bitrate to whatever your bus is)

$ sudo ip link set can0 type can bitrate 500000
$ sudo ifconfig can0 up

Conclusion

I wish I had this when trying to figure out the mappings for the TX1 when I received a prototype unit a few years ago!

Be the first to comment

Leave a Reply

Your email address will not be published.


*