Edit: TX2 version located here: Guide to Enabling MCP251x (MCP2515) on the TX2 (SPI CAN)
Edit: Updates for r28.1!nabling-mcp251x-mcp2515-on-the-tx2-spi-can/77911
The mcp251x.c driver is now configured entirely from the device tree, no more clunky board file!
I made some example dtsi gists with everything you need (in the DTB) to probe an MCP2515 on J21 and J23 Expansion Header. Thanks to annerjb for figuring out J21.
You would need to customize it for your own SPI and interrupt pin.
Old Guide for r24.2:
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.
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: Compiling Tegra X1/X2 source code - RidgeRun Developer Connection
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:
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 (GitHub - 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.
ubuntu@tegra-ubuntu:~$ 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"
<b>#include "tegra210-daxc02.dtsi"</b>
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: Jetson/TX1 SPI - eLinux.org
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 (NVIDIA Jetson TX1 J21 Header Pinout - JetsonHacks) 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 = {
<b>.oscillator_frequency = 16 * 1000 * 1000,</b> /* 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*/
<b>.bus_num = 1,</b> // 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();
[b]#ifdef CONFIG_CAN_MCP251X
mcp251x_init();
#endif[/b]
/* put PEX pads into DPD mode to save additional power */
t210ref_camera_init();
}
Once you have those changes made go ahead and finish the Riderun 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
board-t210ref.c (13.7 KB)
tegra210-daxc03.dtsi.txt (2.97 KB)