How to add the SPI CS pin of Thor through the I2C-expanded GPIO?

We have added a chip that supports I2C expansion of GPIOs. By modifying the driver, we have registered it in the kernel. Now, the gpiod tool can be used to control the corresponding GPIO pins normally.

nvidia@thor:~/can$ gpioinfo /dev/gpiochip3
gpiochip3 - 16 lines:
        line   0:    "PPLA.00"       unused   input  active-high
        line   1:    "PPLA.01"       unused   input  active-high
        line   2:    "PPLA.02"       unused   input  active-high
        line   3:    "PPLA.03"       unused   input  active-high
        line   4:    "PPLA.04"       unused   input  active-high
        line   5:    "PPLA.05"       unused   input  active-high
        line   6:    "PPLA.06"       unused   input  active-high
        line   7:    "PPLA.07"       unused   input  active-high
        line   8:    "PPLB.00"       unused   input  active-high
        line   9:    "PPLB.01"       unused   input  active-high
        line  10:    "PPLB.02"       unused   input  active-high
        line  11:    "PPLB.03"       unused   input  active-high
        line  12:    "PPLB.04"       unused   input  active-high
        line  13:    "PPLB.05"       unused   input  active-high
        line  14:    "PPLB.06"       unused   input  active-high
        line  15:    "PPLB.07"       unused   input  active-high

Now, while using the default two CS pins, we want to use some of the GPIOs in the I2C extended GPIOs as CS pins.

However, I was unable to capture the CS signal at the corresponding GPIO pin.

		i2c@810c680000 {
			status = "okay";
			pcf8575: pcf8575@20 {
				status = "okay";
				reg = <0x20>;
				compatible = "nxp,pcf8575";
				interrupt-parent = <&gpio_aon>;
				interrupts = <TEGRA264_AON_GPIO(DD, 4) 0>;
				gpio-controller;
				interrupt-controller;
				#gpio-cells = <12>;
				#interrupt-cells = <4>;
			};
		};

		spi@810c590000 {	/* SPI1 */
			status = "okay";
			num-cs = <6>;
			cs-gpios = <&pcf8575 TEGRA264_PLINK_GPIO(PLB, 0) GPIO_ACTIVE_LOW>,
					  <&pcf8575 TEGRA264_PLINK_GPIO(PLB, 1) GPIO_ACTIVE_LOW>,
					  <&pcf8575 TEGRA264_PLINK_GPIO(PLB, 2) GPIO_ACTIVE_LOW>,
					  <&pcf8575 TEGRA264_PLINK_GPIO(PLB, 3) GPIO_ACTIVE_LOW>;
			spi@0 {	/* cs0, ch9434 serial codec */
				compatible = "wch,ch943x";
				status = "disabled";
				#address-cells = <1>;
				#size-cell = <1>;
				reg = <0x0>;
				spi-max-frequency = <50000000>;
				gpioint = <&gpio_aon TEGRA264_AON_GPIO(DD, 3) 0>;	/* GPIO08: PDD.03; GPIO09: PDD.04 */
				controller-data {
					nvidia,enable-hw-based-cs;
					nvidia,rx-clk-tap-delay = <0x10>;
					nvidia,tx-clk-tap-delay = <0x0>;
				};
			};

			spi@1 {
				compatible = "tegra-spidev";
				reg = <0x1>;
                spi-max-frequency = <10000000>;
				controller-data {
					nvidia,enable-hw-based-cs;
					nvidia,rx-clk-tap-delay = <0x10>;
					nvidia,tx-clk-tap-delay = <0x0>;
				};
			};

			spi@2 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x3>;
                spi-max-frequency = <10000000>
			};

			spi@3 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x3>;
                spi-max-frequency = <10000000>;
			};

			spi@4 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x4>;
                spi-max-frequency = <10000000>;
			};

			spi@5 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x5>;
                spi-max-frequency = <10000000>;
			};

I referred to the following posts, but still have some unclear points regarding the operation. Could you please give me some good suggestions?

How to use GPIO Expander IO as SPI Interrupts? - Jetson & Embedded Systems / Jetson AGX Xavier - NVIDIA Developer Forums

Enable more CS pins for SPI - Cannot find /Linux-for-Tegras directory - Jetson & Embedded Systems / Jetson AGX Xavier - NVIDIA Developer Forums

SPI TPM module support fail on jetpack 5.0.2/5.1 on jetson xavier agx - Jetson & Embedded Systems / Jetson AGX Xavier - NVIDIA Developer Forums

SPI CS doesn’t work - Jetson & Embedded Systems / Jetson Xavier NX - NVIDIA Developer Forums

Hi chen.xi,

Are you using the devkit or custom board for Thor?
What’s the L4T version in use?

Do you mean that you want to use the GPIO of your I2C expander for the SPI-CS?
Could you share the full dmesg and device tree for further check?

Custom board.

nvidia@thor:~$ cat /etc/nv_tegra_release
# R38 (release), REVISION: 2.0, GCID: 41844464, BOARD: generic, EABI: aarch64, DATE: Fri Aug 22 00:55:42 UTC 2025
# KERNEL_VARIANT: oot
TARGET_USERSPACE_LIB_DIR=nvidia
TARGET_USERSPACE_LIB_DIR_PATH=usr/lib/aarch64-linux-gnu/nvidia
INSTALL_TYPE=openrm

yes, At the same time, the system’s own cs0 (PK, 02) and cs1 (PK. 03) need to be retained.

dmesg.txt (120.7 KB)

Device tree:

#define TEGRA264_PLINK_GPIO_PORT_PLA  0
#define TEGRA264_PLINK_GPIO_PORT_PLB  1
#define TEGRA264_PLINK_GPIO(port, offset) \
	((TEGRA264_PLINK_GPIO_PORT_##port * 8) + offset)

	clocks {
		tcan4x5x_clk: tcan4x5x_clk {
			compatible = "fixed-clock";
			#clock-cells = <0>;
			clock-frequency = <20000000>;
			clock-output-names = "tcan4x5x-clk";
			status = "okay";
		};
	};

	bus@0 {
		i2c@810c680000 {
			status = "okay";
			pcf8575: pcf8575@20 {
				status = "okay";
				reg = <0x20>;
				compatible = "nxp,pcf8575";
				interrupt-parent = <&gpio_aon>;
				interrupts = <TEGRA264_AON_GPIO(DD, 4) 0>;
				gpio-controller;
				interrupt-controller;
				#gpio-cells = <12>;
				#interrupt-cells = <4>;
			};
		};

		spi@810c590000 {	/* SPI1 */
			status = "okay";
			num-cs = <6>;
			cs-gpios =   	<&pcf8575 TEGRA264_PLINK_GPIO(PLB, 0) GPIO_ACTIVE_LOW>,
						<&pcf8575 TEGRA264_PLINK_GPIO(PLB, 1) GPIO_ACTIVE_LOW>,
						<&pcf8575 TEGRA264_PLINK_GPIO(PLB, 2) GPIO_ACTIVE_LOW>,
						<&pcf8575 TEGRA264_PLINK_GPIO(PLB, 3) GPIO_ACTIVE_LOW>;
			spi@0 {	/* cs0, ch9434 serial codec */
				compatible = "wch,ch943x";
				status = "okay";
				#address-cells = <1>;
				#size-cell = <1>;
				reg = <0x0>;
				spi-max-frequency = <50000000>;
				gpioint = <&gpio_aon TEGRA264_AON_GPIO(DD, 3) 0>;	
				controller-data {
					nvidia,enable-hw-based-cs;
					nvidia,rx-clk-tap-delay = <0x10>;
					nvidia,tx-clk-tap-delay = <0x0>;
				};
			};

			spi@1 {
				compatible = "tegra-spidev";
				reg = <0x1>;
                spi-max-frequency = <10000000>;
			};

			spi@2 {
				status = "okay";
				compatible = "ti,tcan4x5x";
                reg = <0x2>;
                #address-cells = <1>;
                #size-cells = <1>;
                spi-max-frequency = <10000000>;
                bosch,mram-cfg = <0x0 0 0 16 0 0 1 1>;
				clocks = <&tcan4x5x_clk>, <&tcan4x5x_clk>;
				clock-names = "hclk", "cclk";
                interrupt-parent = <&pcf8575>;
                interrupts = <TEGRA264_PLINK_GPIO(PLA, 0) IRQ_TYPE_LEVEL_LOW>;
                //device-state-gpios = <&gpio3 21 GPIO_ACTIVE_HIGH>;
                //device-wake-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
                reset-gpios = <&pcf8575 TEGRA264_PLINK_GPIO(PLB, 4) GPIO_ACTIVE_LOW>;
			};

			spi@3 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x3>;
                spi-max-frequency = <10000000>;
			};

			spi@4 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x4>;
                spi-max-frequency = <10000000>;
			};

			spi@5 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x5>;
                spi-max-frequency = <10000000>;
			};
		};
        };
[    0.780309] OF: /bus@0/spi@810c590000: #gpio-cells = 12 found 11
[    0.784263] OF: /bus@0/spi@810c590000/spi@2: #gpio-cells = 12 found 2

Please check if you configure the correct value in the following snippet, or just try using 2 instead.

The modifications have been made. Below are the latest dts and dmesg logs.

#define TEGRA264_PLINK_GPIO_PORT_PLA  0
#define TEGRA264_PLINK_GPIO_PORT_PLB  1
#define TEGRA264_PLINK_GPIO(port, offset) \
	((TEGRA264_PLINK_GPIO_PORT_##port * 8) + offset)
/ {
	clocks {
		tcan4x5x_clk: tcan4x5x_clk {
			compatible = "fixed-clock";
			#clock-cells = <0>;
			clock-frequency = <20000000>;
			clock-output-names = "tcan4x5x-clk";
			status = "okay";
		};
	};

	bus@0 {
		i2c@810c680000 {
			status = "okay";
			pcf8575: pcf8575@20 {
				status = "okay";
				reg = <0x20>;
				compatible = "nxp,pcf8575";
				interrupt-parent = <&gpio_aon>;
				interrupts = <TEGRA264_AON_GPIO(DD, 4) 0>;
				gpio-controller;
				interrupt-controller;
				#gpio-cells = <2>;
				#interrupt-cells = <2>;
			};
		};

		spi@810c590000 {	/* SPI1 */
			status = "okay";
			num-cs = <6>;
			cs-gpios =  <&pcf8575 0x08 GPIO_ACTIVE_LOW>,
						<&pcf8575 0x09 GPIO_ACTIVE_LOW>,
						<&pcf8575 0x0a GPIO_ACTIVE_LOW>,
						<&pcf8575 0x0b GPIO_ACTIVE_LOW>;
			spi@0 {	/* cs0, ch9434 serial codec */
				compatible = "wch,ch943x";
				status = "okay";
				#address-cells = <1>;
				#size-cell = <1>;
				reg = <0x0>;
				spi-max-frequency = <50000000>;
				gpioint = <&gpio_aon TEGRA264_AON_GPIO(DD, 3) 0>;	/* GPIO08: PDD.03; GPIO09: PDD.04 */
				/delete-node/ controller-data;
				controller-data {
					nvidia,enable-hw-based-cs;
					nvidia,rx-clk-tap-delay = <0x10>;
					nvidia,tx-clk-tap-delay = <0x0>;
				};
			};

			spi@1 {
				compatible = "tegra-spidev";
				reg = <0x1>;
                spi-max-frequency = <10000000>;
				controller-data {
					nvidia,enable-hw-based-cs;
					nvidia,rx-clk-tap-delay = <0x10>;
					nvidia,tx-clk-tap-delay = <0x0>;
				};
			};

			spi@2 {
				status = "okay";
				compatible = "ti,tcan4x5x";
                reg = <0x2>;
                #address-cells = <1>;
                #size-cells = <1>;
                spi-max-frequency = <10000000>;
                bosch,mram-cfg = <0x0 0 0 16 0 0 1 1>;
				clocks = <&tcan4x5x_clk>, <&tcan4x5x_clk>;
				clock-names = "hclk", "cclk";
                interrupt-parent = <&pcf8575>;
                interrupts = <0x00 IRQ_TYPE_LEVEL_LOW>;
                //device-state-gpios = <&gpio3 21 GPIO_ACTIVE_HIGH>;
                //device-wake-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
                reset-gpios = <&pcf8575 0x0d GPIO_ACTIVE_LOW>;
			};

			spi@3 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x3>;
                spi-max-frequency = <10000000>;
			};

			spi@4 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x4>;
                spi-max-frequency = <10000000>;
			};

			spi@5 {
				status = "okay";
				compatible = "tegra-spidev";
				reg = <0x5>;
                spi-max-frequency = <10000000>;
			};
		};
    };
};

dmesg.txt (130.5 KB)

The current error may be caused from that you are using the GPIO from I2C expander.
It is too slow for SPI intialization.
Could you try removing cs-gpios property in device tree to check if there’s still the similar error?

For the chip select of SPI, we would suggest using HW-based CS.(i.e. SPI1_CS0, SPI1_CS1).

After the removal, everything is normal. The function is also normal. We have successfully added one SPI device and it is functioning properly.

We actually need to use 6 SPI devices, so the two native CS signals are not sufficient for our needs.

If you want to use sw-based CS for more SPI devices, please try removing nvidia,enable-hw-based-cs; in device tree and add cs-gpios back to check.
I will suggest using the GPIO from native GPIO controller to verify rather than the GPIO from your I2C expander first.

OK, we have solved this problem. Thank you for your help.