Jetson AGX Orin: Issues with minimum SPI speed and byte send order after update to Jetpack 6

Hello,

I am using a Jetson AGX Orin Devkit and currently updated from Jetpack 5.1.3 to Jetpack 6, in order to get Ubuntu 22.04 on the system.

For our application, we were utilizing the Jeton’s SPI (SPI2 on the camera connector, but not relevant for this topic) to communicate with an external device using the Linux spidev in a C program.

Unfortunately, after the Upgrade to Jetpack 6, the SPI speed cannot be lower than 3.125 MHz (before it could go down to 150 kHz). This is a problem for us since the external device is requiring a frequency of < 1MHz.
Additionally, on the logic analyzer, I can see that the SPI now sends out the least significant BYTE first (but still most significant BIT first, as configured) , which it also did not before (e.g.: sending out 0xDEADBEEF with a configured 32 bits per word results in 0xEF 0xBE 0xAD 0xDE).

Did anyone encounter similar issues after upgrading the Jetpack, or has an idea on these two problems?

Any help is welcome, thanks!

Kind regards,
Daniel

Hi dk_sal,

Could you share the steps how you configure the SPI speed to 150kHz in Jetpack 5.1.3?

Please also share the full dmesg for further check.

Hello KevinFFF,

I just tried to reproduce the prior results by flashing JP 5.1.3 again, and now I could only reach SPI speeds as low as 300 kHz (which would still be fine for our usecase).

On both JP6.0 and JP5.1.3 I used the following code to test the SPI:

#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

int main()
{
    int fd = open("/dev/spidev1.0", O_RDWR);

    // ---------------------- Configure SPI -----------------------------

    uint32_t spi_mode = SPI_MODE_0;
    int sysret = ioctl(fd, SPI_IOC_WR_MODE32, &spi_mode);
    printf("SPI_IOC_WR_MODE32       : %d\n", sysret);

    bool lsb_first = false;
    sysret = ioctl(fd, SPI_IOC_WR_LSB_FIRST, &lsb_first);
    printf("SPI_IOC_WR_LSB_FIRST    : %d\n", sysret);

    uint8_t bits_per_word = 32;
    sysret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word);
    printf("SPI_IOC_WR_BITS_PER_WORD: %d\n", sysret);

    uint32_t max_speed = 150000;
    sysret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed);
    printf("SPI_IOC_WR_MAX_SPEED_HZ : %d\n", sysret);

    // ---------------------- Write Message -----------------------------

    struct spi_ioc_transfer msg;
    memset(&msg, 0, sizeof(msg));

    uint32_t data[] = {0xDEADBEEF};

    msg.tx_buf = (__u64)data;
    msg.rx_buf = (__u64)NULL;
    msg.len = sizeof(data);

    sysret = ioctl(fd, SPI_IOC_MESSAGE(1), &msg);
    printf("SPI_IOC_MESSAGE(1)      : %d\n", sysret);

    // ------------------------------------------------------------------------

    close(fd);
    return EXIT_SUCCESS;
}

Interestingly, on JP5.1.3 I need to load the “spidev” kernel module manually, using modprobe, while on JP6 it is loaded automatically. The kernel module “spi_tegra114” is loaded automatically on both.

Attached you will find the dmesg output for both JP versions, as well as the relevant excerpt from the SPI node in the device tree and some screenshots from the logic analyzer.

I hope this helps, thanks!

Jetpack 5.1.3:
jp5_device_tree.txt (1.1 KB)
jp5_dmesg.txt (69.6 KB)

Jetpack 6.0:
jp6_device_tree.txt (1.1 KB)
jp6_dmesg.txt (56.3 KB)

Hello again,

I dug deeper now and maybe found the source of the speed issue.

Thanks to this and this thread, and additionally this page, it became clear to me that the minimum speed the SPI can achieve is determined by its parent clock. Due to the limited internal clock-divider and the high parent clock (PLLP: ~400MHz), only >= 3.2 MHz can be achieved on the SPI.

Well, why did it then work on Jetpack 5, but not on Jetpack 6?

In the Tegra SPI driver from Kernel 5.10, which was used in JP5, there was this functionality that the driver sets the best suitable parent clock (from a set of supported parent clocks) to achieve the desired speed. The link to the source function is here and it is generally invoked at the beginning of this function.

If we now look to the same place in the Tegra SPI driver of the Jammy kernel (Jetpack 6) here, it becomes clear that the functionality of setting the best parent clock is not there anymore. It just tries to set the desired speed without checking for a suitable clock source.

I now tried using the debugfs to get information about the selected parent clock and also to set a different one. This is located at: /sys/kernel/debug/bpmp/debug/clk/spi2

cat possible_parents lists all possibilities for parent clocks
echo osc > parent sets a different parent
cat min_rate shows the new minimum speed with the new parent clock (which is now 300 kHz).

Testing the above SPI program again and I get a clock of 300 kHz.

In conclusion: the Tegra SPI driver is missing some functionality to set a suitable parent clock in JP6, which it still had in JP5.

Still, this is a workaround and needs an official solution, and also the Byte-Order is still wrong.

It seems spidev has been installed in kernel image instead of built as a loadable kernel module in JP6.

Yes, the parent clock for SPI would affect the clock frequency of SPI.
It seems you’ve worked it out something.

Which SPI interface are you using?

What the result of this command?

Hello KevinFFF,
thanks for your response.

As the location suggests, we are using the SPI2 interface on the AGX Orin’s camera connector.

/sys/kernel/debug/bpmp/debug/clk/spi2# cat possible_parents
pllp_out0 pll_c pll_aon clk_32k osc

The min_rates with these parents are as follows:

pllp_out0: 3187500
pll_c    : 1562499
pll_aon  : 3125000
clk_32k  : 256
osc      : 300000

So there are enough possibilities to reach the desired clock rate, the driver just does not do this selection in JP6 (as described above).

Furthermore, I probably also found the source of the inverted byte order when sending:

The TRM for the AGX Orin, in the SPI section, it states that there is a config field called En_LE_Byte (page 8709 and 8725) that is used to control the byte endianness of transfers. The driver of kernel 5.10 (JP5) sets this bit, but the driver of the jammy kernel (JP6) does not set it.

It seems that there were big changes to the driver, coming from JP5 to JP6, that will break a lot of systems (especially the hard coded endianness).

I tried setting the En_LE_Byte manually via devmem2 and /dev/mem, but it seems I cannot access the registers of SPI2 (base address: 0x0C260000) since it always returns 0xFFFFFFFF (when reading it) and I get an error in dmesg:

~/Documents$ sudo devmem2 0x0C260000
/dev/mem opened.
Memory mapped at address 0xffff9e157000.
Value at address 0xC260000 (0xffff9e157000): 0xFFFFFFFF

[ 3647.958270] CPU:0, Error: cbb-fabric@0x13a00000, irq=192
[ 3647.958282] **************************************
[ 3647.958284] CPU:0, Error:cbb-fabric, Errmon:2
[ 3647.958291]    Error Code            : TIMEOUT_ERR
[ 3647.958299]
[ 3647.958300]    Error Code            : TIMEOUT_ERR
[ 3647.958301]    MASTER_ID             : CCPLEX
[ 3647.958303]    Address               : 0xc260000
[ 3647.958304]    Cache                 : 0x0 -- Device Non-Bufferable
[ 3647.958306]    Protection            : 0x2 -- Unprivileged, Non-Secure, Data Access
[ 3647.958308]    Access_Type           : Read
[ 3647.958309]    Access_ID             : 0x15
[ 3647.958311]    Fabric                : cbb-fabric
[ 3647.958312]    Slave_Id              : 0x0
[ 3647.958313]    Burst_length          : 0x0
[ 3647.958314]    Burst_type            : 0x1
[ 3647.958315]    Beat_size             : 0x3
[ 3647.958315]    VQC                   : 0x0
[ 3647.958316]    GRPSEC                : 0x7e
[ 3647.958317]    FALCONSEC             : 0x0
[ 3647.958320]    AON_SLV_TIMEOUT_STATUS : 0xffffffff
[ 3647.958323]  **************************************
[ 3647.958346] WARNING: CPU: 0 PID: 0 at drivers/soc/tegra/cbb/tegra234-cbb.c:608 tegra234_cbb_isr+0x14c/0x180
[ 3647.958619] ---[ end trace e6f36f163923c8de ]---

SPI2 is coming from SPE and it is different from SPI1/SPI3.

You may configure it in BPMP-DTB.

It seems the change in driver between these 2 kernel releases.
Have you tried to add it back in driver of JP6 like the following?

- tspi->def_command1_reg  = SPI_M_S;
+ tspi->def_command1_reg  = SPI_M_S | SPI_LSBYTE_FE;

Yes, I see, SPI2 is coming from the AON/SPE.
Although I’m not sure what you want to express here.
The behaviour described in this thread is exactly the same with SPI1 as well.

  1. What is the BPMP-DTB? I could not find a DTS related to that.
  2. I know I can select a different parent clock in the SPI device-tree node as well
  3. In my opinion this should be the drivers task, as in JP5, to select the most suitable parent clock.

As I see it, this is exactly the source of the problems I encounter.
It seems to me that JP6 includes an SPI driver that is even older than the one from JP5.

I have not tried that yet. By any chance, do you have a quick guide on how to recompile the driver and deploy (or reflash) it to the device?

You can check the flash log for the bpfdtbfile.

They are open-source and you can customize the driver for your requirement.

Please refer to Kernel Customization — NVIDIA Jetson Linux Developer Guide 1 documentation to build/update kernel image.

I did change the code now, recompiled the driver and loaded the new module (after unloading the old one), and it works. As expected, it reversed the byte sending order and in that regards behaves like in JP5.

I am aware of that, but I was expecting that when NVIDIA starts developing a new Jetpack version, that they also migrate patches to the updated kernel, unless it is pushed to the mainline kernel anyway.

In case of JP6, I assume NVIDIA started of with the mainline kernel but missed to migrate lots of patches they did to the JP5 driver of the SPI. Comparing the commit history of the spi-tegra114 driver from JetPack 5’s kernel to the one from JetPack 6’s kernel shows that a lot of patches, for example from July 11th 2021, are not merged/included in the new kernel driver. Maybe, @KevinFFF, you have more insight into the development and can comment on that.

I see functionality missing and, judging by the commit history, even memory leaks not fixed in the JP6 driver. This leads us to probably go back to JP5 for now, until there is an update on that matter.

Currently, you can port the spi-tegra114 driver from JP5 to JP6.

Thank you for pointing out the issue of SPI driver, please let me check with internal for the missing patches.