Adding a CAN-bus with MCP2515 over SPI

There has been some requests for CAN-bus support, and I have now succeeded in connecting Jetson to a MCP2515 Can-bus controller over SPI. I use the SPI port available over the expansion connector (normaly used for touch screen interface). The MCP2515 requires a buffer to translate to 3.3V, and I use the GPIO-PU3 for the hardware interrupt. For tests the code uses only 4Mhz, which is the lowest possible clock frequency. It can be increased quite a bit, but this is not what limits the speed.

The main patch to the kernel is a modification to arch/arm/mach-tegra/board-ardbeg.c.

diff --git a/kernel/arch/arm/mach-tegra/board-ardbeg.c b/kernel/arch/arm/mach-tegra/board-ardbeg.c
index 9b997dd..419ce01 100644
--- a/kernel/arch/arm/mach-tegra/board-ardbeg.c
+++ b/kernel/arch/arm/mach-tegra/board-ardbeg.c
@@ -93,6 +93,7 @@
 #include "pm.h"
 #include "tegra-board-id.h"
 #include "tegra-of-dev-auxdata.h"
+#include <linux/can/platform/mcp251x.h>
 
 static struct board_info board_info, display_board_info;
 
@@ -1114,6 +1115,44 @@ static struct spi_board_info rm31080a_norrin_spi_board[1] = {
 	},
 };
 
+static struct mcp251x_platform_data mcp251x_info = {
+.oscillator_frequency = 16000000,
+.board_specific_setup = NULL,
+.power_enable = NULL,
+.transceiver_enable = NULL,
+};
+
+static struct spi_board_info mcp251x_spi_board[1] = {
+	{
+		.modalias = "mcp2515",
+		.bus_num = 0,
+		.chip_select = 0,
+		.max_speed_hz = 4 * 1000 * 1000,
+		.mode = SPI_MODE_0,
+		.controller_data = &dev_cdata,
+		.platform_data = &mcp251x_info,
+	},
+};
+
+#define TEGRA_GPIO_PU5		165
+#define MCP2515_INT TEGRA_GPIO_PU5
+
+static int __init mcp251x_controller_init(void)
+{
+	int ret;
+	mcp251x_spi_board[0].irq = gpio_to_irq(MCP2515_INT);
+	pr_info("mcp251x_controller_initialisation\n");
+	ret = gpio_request(MCP2515_INT, "MCP251x interrupt");
+	if (ret < 0) {
+		pr_err("gpio_request failed for MCP251x irq\n");
+		return ret;
+	}
+	pr_info("setting mcp251x interrupt pin direction\n");
+	gpio_direction_input(MCP2515_INT);
+	spi_register_board_info(mcp251x_spi_board, ARRAY_SIZE(mcp251x_spi_board));
+	return 0;
+};
+
 static int __init ardbeg_touch_init(void)
 {
 	tegra_get_board_info(&board_info);
@@ -1417,7 +1456,8 @@ static void __init tegra_ardbeg_late_init(void)
 
 	edp_init();
 	isomgr_init();
-	ardbeg_touch_init();
+  mcp251x_controller_init();
+  ardbeg_touch_init();
 	if (board_info.board_id == BOARD_E2548 ||
 			board_info.board_id == BOARD_P2530)
 		loki_panel_init();

We get an error message saying chip select 0 is already used. This is because the touch controller fails to start up, becuase MCP251x has taken it already. This can be ignored, or you can disable the
touch controller by editing /boot/extlinux/extlinux.conf and setting “touch_id=3@3”

ubuntu@tegra-ubuntu:~$ dmesg | grep "spi"
[    0.592477] avdd-spi: 3300 mV at 150 mA
[    4.471441] spi-tegra114 spi-tegra114.0: chipselect 0 already in use
[    4.479089] spi-tegra114 spi-tegra114.0: can't create new device for rm_ts_spidev
[    4.521330] mcp251x spi0.0: probed
[    7.631728] avdd-spi: incomplete constraints, leaving on

For testing, try the following:

sudo ip link set can0 type can bitrate 125000 triple-sampling on
sudo ifconfig can0 up
sudo apt-get install can-utils
cansend can0 5A1#11.22.33.44.55.66.77.88

Note that there does not seem to be any need to change the DTS file, contrary to many earlier advices. The reason seems to be that the DTS changes are only needed when you are going to access the SPI port alone. The MCP251x.c does not need this.

At last, there is a remaining problem:
The chip select is active for around 100us, while the data transfer takes only 30uS (or less using higher clock frequency). This is not fast enough for 1Mb can bus trafic.
Is there any way to speed up the transfers?

It would also be nice if the board-ardbeg.c patch could take its parameters from the dts file, so you did not have to recompile the kernel if the irq line or bitrate changes. That would make it possible to include the patch permanently in the L4T code. Is there anybody capable to do this?

The chip select is active for around 100us, while the data transfer takes only 30uS (or less using higher clock frequency). This is not fast enough for 1Mb can bus trafic.
Is there any way to speed up the transfers?

  • There are 2 reasons which cause the issues:
    1). The latency of spi transfer, since the size is CAN package is around 14 bytes, so tegra spi master always use PIO mode to receive data. So the latency is very long, that’s why you see CS keep low very long time.
    2). The latency of GPIO interrupt which used in mcp251x driver. Normal in kernel system, the interrupt latency is around 20us, which will increase the latency of CAN package transfer.

Please email me if you have more questions.

EMAIL: ricksong.email@icloud.com

Some of the delay has been removed by changing the dev_cdata structure in board-ardbeg.c

static struct tegra_spi_device_controller_data dev_cdata = {
	.rx_clk_tap_delay = 0,
	.tx_clk_tap_delay = 16,
	.is_hw_based_cs = true,   // new
	.cs_setup_clk_count = 2,  // new
	.cs_hold_clk_count = 5,   // new
};

Now the delay from chip select to clock is much less (around 1-2uS).
When I now test with bursts of 48 messages at 125k, all except some of the first messages are received. A quick check on the hardware shows that the first interrupt uses much longer time than the rest. There are som queues in the background, but the Can-bus is the only user of the SPI bus.
Why does it take so long for the first transfer?

I don’t know what drives the particular IRQ channel shown on the graph, but change of performance modes comes to mind as a possibility. Perhaps something went from sleep to wake. Have you set CPU to performance mode at all times?
http://elinux.org/Jetson/Performance

Other than that, a driver somewhere might be locking out other access till it is done, e.g., inside of a spinlock. In that case (and assuming performance is set to max at all times) the delay to start might be random and not consistent.

Your comment #3 is correct to decrease the latency. Not sure why first message consume longer time, but I can give suggest to you to decrease the latency much more:

  1. Update spi-tegra114.c to avoid interrupt, but use polling mode.

  2. Implement a HW direct function in spi-tegra114.c, and delete unnecessary register read/write, then call this function directly in mcp251x.c

  3. Attach MCP251X interrupt to a seperate CPU core, burst freq. to MAX.

After implement below 3, if you still see the issues, you may need move MCP251X spi transfer to AVP, which can solve the issues completely.

I discovered that there is a simple way to reduce the latency dramatically.
The message pump in spi.c is running at normal priority by default, but can be set
to run at real-time priority by setting the master->rt flag to true.
This should idealy have been possible through the device tree, but for now, you must
modify spi-tegra114.com, inserting at line ca 1387:

1387: master->rt = true;

Now the latency from interrupt low to first spi transaction is only 1.2mS, and the irq is
low for ca 3.7mS.

Testing with bursts of 48 can messages at 125kbit/s gives no load data. This is a great improvement.
As Rick sugests above, removing the queue and accessing spi directly would be much better,
but the code is quite difficult to understand, and will have to wait.