Problem using I2S and SPI on the GPIO header of the Rock Pi 4B

I’m trying to get a Rock Pi 4B v1.4 working with two peripherals:

  1. The HifiBerry DAC+ Pro XLR, an I2S based DAC and
  2. a Waveshare 3.5 inch RPi LCD (B) display, which is SPI based

I have adapted drivers for the DAC+ Pro and created the relevant device tree bindings and it works fine. I’ve also created device tree bindings for the display, together with a short wiring harness that connects it either to SPI2, which is free, or to SPI1 which is used for the on-board flash, so that I needed to use an additional GPIO CS line in the device tree binding.

My problem is this: with the DAC+ Pro board connected and the display connected on top of it, if I blacklist the DAC’s kernel modules, the display seems to work more or less fine at a 10MHz SPI clock. I can use modetest to make it display a test pattern.

Probing the SPI1 lines with an oscilloscope shows the clock line being like a sawtooth (or rather what you would get from a square waveform, if the low-to-high transitions were linear ramps), rising to only 2V before dropping back to 0V. I would ascribe this to capacitive loading of the line due to the DAC+ headers and wiring harness, but the curious thing is that the data lines show up much better on the oscilloscope, reaching 3V with minimal capacitive loading showing.

If I allow the DAC+ drivers to load, then the SPI bus becomes unusable at anything above 100kHz or so. The display doesn’t work any more and probing the clock line shows again a sawtooth waveform, now barely reaching 0.5V or so. The data lines are also sawtooth like, although they can reach higher voltages, depending on the number of consecutive high states. In order to get the SPI bus to work, I have to drop the clock speed to something on the order of 100 kHz or so.

The situation is similar with the SPI2 bus.

I’m running Libreelec with a 6.6 version kernel. Any hints as to how this behavior might be explained and what I might look into, would be most welcome.

Waveshare 3.5 inch LCD (B) is already adapt, you can refer to https://github.com/radxa-pkg/radxa-overlays/blob/main/arch/arm64/boot/dts/rockchip/overlays/rk3399-spi1-waveshare35b.dts

To use it, you need to update your system, then enable the LCD overlay, finnaly reboot your system.

You may need to change the pcfg_pull_up of these pinctrl to pcfg_pull_up_20ma in your overlay

Thanks for the quick response! I’m using Libreelec currently (because I use the Rock Pi as a media player), so I can’t try out the overlay directly. I would try to install your image, but then I won’t be able to use the DAC+ Pro and without it, I know the display will work fine. It looks similar to mine with a couple of differences:

  • It seems to be made for the fbtft driver, while I use the tinydrm driver. That shouldn’t affect SPI performance, as far as I can see. I have tried probing the SPI1 lines with just the on-board SPI flash attached, and the clock line is again much more sawtooth-like than the data lines.
  • There’s an interesting reference to a “high_speed” pinctrl state, but no such state seems to be defined (I can see no pinctrl-1). I see no separate “high speed” SPI pin configuration for the Pi 4B in the Linux kernel. If there’s anything I’m missing, please let me know.

What I wonder though, is why the situation gets so much worse when I load the DAC driver. Note that:

  • The DAC uses the IS1 and I2C7 interface. I can leave the DAC and display physically connected, leave the device tree bindings for everything enabled and simply not load the DAC’s driver and the SPI bus works ok. If I load the driver, the rise time gets several (maybe 4-5) times larger.
  • The DAC has no common pins (apart from ground and power pins) on the GPIO header with SPI devices. As I said, it uses the I2C7 and I2S1 interfaces only, which are on different pins.

Thanks for the suggestion! I had tried that and it didn’t make any difference. I now note though that the change doesn’t seem to take effect. I have verified, by loading the running device tree from /sys/firmware/devicetree/base, that the pins are configured as pcfg_pull_up_20ma, yet in /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinconf-pins, I see:

pin 39 (gpio1-7): input bias pull up (1 ohms), output drive strength (6 mA)
pin 40 (gpio1-8): input bias pull up (1 ohms), output drive strength (6 mA)
pin 41 (gpio1-9): input bias pull up (1 ohms), output drive strength (6 mA)
pin 42 (gpio1-10): input bias pull up (1 ohms), output drive strength (6 mA)

Any idea why that might be? Drive strength does seem to be an issue, although probably not the only issue. If I probe the SPI clock line, configured to run at 10 MHz) on the bare Rock Pi, i.e. without DAC or display attached, I see that it barely manages to reach 3V in the 50ns of on time. That doesn’t seem right.

If I attach the devices but load no drivers, they contribute some capacitance, so that the clock now only manages to reach 2.5V or so in 50ns. If I load the DAC’s driver, it barely manages to reach 0.5V in 50ns.

So one issue seems to be drive strength, in combination with capacitance. Another is that the DAC driver somehow affects the SPI bus, although I can’t see how. Any ideas would be greatly appreciated.

Please show me your dts source code, I will test on Monday

Thanks! Regarding the drive strength, I merely changed pcfg_pull_up to pcfg_pull_up_20ma, as you suggested. I could verify in the running device tree (via the resulting phandle in the pin configuration) that the pin was indeed set in the device tree as 20ma. The kernel debug information showed it as 6ma though.

For the rest I’ve made changes to the dts file for the 4B in the kernel (i.e. I didn’t use an overlay). I include the relevant file below. All other included dts files are as found in the mainline Linux kernel, v6.6.

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Akash Gajjar <Akash_Gajjar@mentor.com>
 * Copyright (c) 2019 Pragnesh Patel <Pragnesh_Patel@mentor.com>
 */

/dts-v1/;
#include "rk3399-rock-pi-4.dtsi"
#include "rk3399-opp.dtsi"

/ {
	model = "Radxa ROCK Pi 4B";
	compatible = "radxa,rockpi4b", "radxa,rockpi4", "rockchip,rk3399";

	aliases {
		mmc2 = &sdio0;
	};

        dacpro_osc: dacpro-osc {
                compatible = "hifiberry,dacpro-clk";
                #clock-cells = <0>;
        };

        hifiberry_dacplus: hifiberry-dacplus {
                compatible = "hifiberry,hifiberry-dacplus";
                i2s-controller = <&i2s1>;
                status = "okay";
        };
};

&pwm1 {
	status = "okay";
};

&sdio0 {
	status = "okay";

	brcmf: wifi@1 {
		compatible = "brcm,bcm4329-fmac";
		reg = <1>;
		interrupt-parent = <&gpio0>;
		interrupts = <RK_PA3 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-names = "host-wake";
		pinctrl-names = "default";
		pinctrl-0 = <&wifi_host_wake_l>;
	};
};

&spi1 {
	status = "okay";
        cs-gpios = <0>, <&gpio4 RK_PD2 GPIO_ACTIVE_HIGH>;
	pinctrl-0 = <&spi1_clk &spi1_tx &spi1_rx &spi1_cs0 &spi1_cs1>;

	flash@0 {
		compatible = "jedec,spi-nor";
		reg = <0>;
		spi-max-frequency = <10000000>;
	};

        tft@1 {
                compatible = "ilitek,ili9486";
                status = "okay";
                reg = <1>;

                pinctrl-names = "default";
                pinctrl-0 = <&tft_reset &tft_dc>;

                spi-max-frequency = <10000000>;
                reset-gpios = <&gpio4 RK_PD5 GPIO_ACTIVE_HIGH>;
                dc-gpios = <&gpio4 RK_PD4 GPIO_ACTIVE_HIGH>;

                init = <0x10000b0 0x00
                        0x1000011
                        0x20000ff
                        0x1000021
                        0x100003a 0x55
                        0x10000c2 0x33
                        0x10000c5 0x00 0x1e 0x80
                        0x1000036 0x28
                        0x10000b1 0xb0
                        0x10000e0 0x00 0x13 0x18 0x04 0x0f 0x06 0x3a 0x56 0x4d 0x03 0x0a 0x06 0x30 0x3e 0x0f
                        0x10000e1 0x00 0x13 0x18 0x01 0x11 0x06 0x38 0x34 0x4d 0x06 0x0d 0x0b 0x31 0x37 0x0f
                        0x1000011
                        0x20000ff
                        0x1000029>;
        };
};

&pinctrl {
        spi1-cs-gpios {
                spi1_cs1: spi1-cs1 {
			rockchip,pins = <4 RK_PD2 0 &pcfg_pull_up>;
		};
        };

        tft {
                tft_dc: tft-dc {
                          rockchip,pins = <4 RK_PD4 0 &pcfg_pull_none>;
                };

                tft_reset: tft-reset {
                          rockchip,pins = <4 RK_PD5 0 &pcfg_pull_none>;
                };
        };
};

&uart0 {
	status = "okay";

	bluetooth {
		compatible = "brcm,bcm4345c5";
		clocks = <&rk808 1>;
		clock-names = "lpo";
		device-wakeup-gpios = <&gpio2 RK_PD3 GPIO_ACTIVE_HIGH>;
		host-wakeup-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_HIGH>;
		shutdown-gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>;
		max-speed = <1500000>;
		pinctrl-names = "default";
		pinctrl-0 = <&bt_host_wake_l &bt_wake_l &bt_enable_h>;
		vbat-supply = <&vcc3v3_sys>;
		vddio-supply = <&vcc_1v8>;
	};
};

&i2s1 {
        status = "okay";
        #sound-dai-cells = <0>;
};

&i2c7 {
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";

        pcm5122@4d {
                #sound-dai-cells = <0>;
                compatible = "ti,pcm5122";
                reg = <0x4d>;
                clocks = <&dacpro_osc>;
                AVDD-supply = <&vcc3v3_sys>;
                DVDD-supply = <&vcc3v3_sys>;
                CPVDD-supply = <&vcc3v3_sys>;
                status = "okay";
        };
};

I had a look at the datasheet for the RK3399 and the GPIO pins for the SPI1 and SPI2 interfaces only go up to a drive strength of 12mA, so that’s why the 20mA setting didn’t take effect. After setting the pins at 12mA, the situation improves in the expected way: When driving the display without the DAC’s driver loaded, the clock now reaches 3V instead of the 2-2.5V it reached before. With the driver loaded it reaches 1V instead of 0.5V.

So the increased drive strength helps, but enabling the DAC’s driver seems to somehow affect the SPI bus, as if the capacitance on the lines is increased. I can’t imagine how though, since the DAC does not use the pins for either of the SPI1/2 buses.

I had noticed another curiosity, which seems like it might be important. When probing the SPI lines I noticed their level pulsating in sync with the blue led on the Pi4B, which is configured as a heartbeat by default.

I can disable the led by setting its trigger to none and the level stays high (well as high as it gets). I can leave it on by setting it to default-on and the level stays low. When probing data lines with lots of consecutive 1s, the level does reach 3V eventually but it takes longer.

So it seems like the SPI GPIO driver can’t provide enough currrent. I now suspect that the deterioriation in the SPI bus’s performance when the DAC is operating, may simply be due to it drawing current, much like the LED.

Another curious thing, is that the status LED, SPI busses and I2S bus are all on different GPIO banks. The 3V3 and 5V lines on the GPIO are all very close to their nominal value. Note that the above happens even when the display and DAC are not connected, probing the SPI NOR flash. It also happens both on the Libreelec and Radxa Bullseye images and both with a 15W power supply and a power bank and two different USB-C cables.

Do you also get such behavior, or is it something on my setup?

It’s probably safer to use a 20-30w power supply, the dac can sometimes reach nearly 10w of power consumption

I just tried to power it with a 65W laptop power supply. Same behavior: when the blue led is on, the SPI lines seem to be able to deliver only about the half the current compared to when it is off.

Can you try using a dupont cable to connect only the dac’s data cable, 3.3v or 5v data cable through an external supply? to rule out power supply issues

I don’t think it’s a matter of the supply pins. When I have the blue on-board LED off and the display connected, drawing a substantial current to power the backlight, and I read the SPI flash in a loop, this is the waveform I get on the clock line. Note that it reaches 0.5V within the 50ns on time, corresponding to the default 10Mhz setting of the SPI flash in the mainline kernel.

1

If I turn the backlight off, I get no change whatsoever; it still reaches about 0.5V.

If, on the other hand, I turn on the blue status LED, which gets powered from a GPIO pin, then the waveform is similar, but now reaches only 300mv or so:

2

For reference, here are the waveforms with display and DAC disconnected, i.e. with only the Pi 4B. With the blue status LED off, the waveform starts to resemeble a square wave, at least in that it reaches 3V, a bit before dropping back to zero:

3

If I turn the blue LED on, I get a 50% duty sawtooth wave again, peaking a bit below 3V:

4

I find it puzzling that the SPI signals are pretty close to unusable at the 10MHz default setting for the SPI flash. Perhaps something is wrong with my board (which is version 1.4), although I would find that unlikely. If you could verify whether you get the same behavior, that would be very helpful.