Using Zephyr SPI driver with generic device

I need to connect a device to an nRF5340 SPI port without creating a custom driver.  I'm tempted to use the nrfx_spim driver directly, but I'm trying to do it with Zephyr and the device tree.  I've looked for examples of how to use the Zephyr SPI driver directly (without having to extend it to a custom device driver like the BME280 example), but I could only find the following examples:

I've tried to combine the information from these and have code that compiles and runs, but the pins don't move (viewed by a logic analyzer).  The SCK, MOSI and MISO lines stay low, and the CS# line seems to be floating since it is just noise.  Can you tell me what is missing from my code?

Here is the edited file (fpga_spi_init is called by the main() in main.c, and the LOG information is seen on the console):

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>

LOG_MODULE_REGISTER(fpga_spi, LOG_LEVEL_DBG);

// Register/command addresses.  Even = read (status/readback), odd = write (command/control)
#define VERSION_CMD       0x00
// ...rest redacted

const struct device *spi32_dev = DEVICE_DT_GET(DT_ALIAS(scope_fpga_spi32));

static struct spi_config spi32_cfg = {
    .operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
    .frequency = 32000000,
};

struct {
    uint8_t chip_version[2];
} scope_data;

static int fpga_reg_read(uint8_t reg, uint8_t *data, uint8_t size)
{
    int err;

    uint8_t tx_buffer = reg;
    struct spi_buf tx_spi_buf = {.buf = (void *)&tx_buffer, .len = 1};
    struct spi_buf_set tx_spi_buf_set = {.buffers = &tx_spi_buf, .count = 1};
    struct spi_buf rx_spi_buf = {.buf = data, .len = size};
    struct spi_buf_set rx_spi_buf_set = {.buffers = &rx_spi_buf, .count = 1};

    err = spi_transceive(spi32_dev, &spi32_cfg, &tx_spi_buf_set, &rx_spi_buf_set);
    if (err < 0) {
        LOG_ERR("spi_transceive() failed, err: %d", err);
        return err;
    }

    return 0;
}

int fpga_spi_init(void)
{
    int err;

    if (!device_is_ready(spi32_dev)) {
        LOG_ERR("SPI32 device %s is not ready", spi32_dev->name);
        return -ENODEV;
    }

    err = fpga_reg_read(VERSION_CMD, scope_data.chip_version, sizeof(scope_data.chip_version));
    if (err < 0) {
        LOG_DBG("FPGA version read failed: %d", err);
        return err;
    }
    // Validate the version
    if (scope_data.chip_version[0] == 1 && scope_data.chip_version[1] == 2) {
        LOG_INF("FPGA version OK");
    } else {
        LOG_ERR("Bad FPGA version id %d.%d", scope_data.chip_version[0], scope_data.chip_version[1]);
        return -ENOTSUP;
    }

    return 0;
}

The device_is_ready call returns true, and the read returns, but with zero data, and no movement on the pins.  Note that this uses SPI4 which is capable of 32 MHz, and lowering the frequency to 8 MHz doesn't work either.

Here is the nrf7002dk_nrf5340_cpuapp.overlay file which removes the nRF7002DK's mx25R6435f Flash chip support and sets the alias.  The board has been modified to cut power to the Flash chip and solder jump the SPI4 pins to the headers as described in the nRF7002DK documentation and board.

// Override the 32 Mbps SPI to use for the main FPGA connection
// The nRF7002DK's onboard serial flash chip on this port will be disabled by solder cuts and jumps
&spi4 {
	/delete-node/ mx25r6435f@0;
};

/ {
	aliases {
		// scope-fpga-spi8 = &spi1;
		scope-fpga-spi32 = &spi4;
	};
};

&clock {
	status = "okay";
};

I checked the build/zephyr/zephyr.dts file to confirm that the device tree is compiling the way I intend it, and it looks ok to me, with `status = "okay"`, the cs-gpios set to pin 0.11, etc.  Here are the relevant sections:

				aliases {
		led0 = &led0;
		led1 = &led1;
		pwm-led0 = &pwm_led0;
		sw0 = &button0;
		sw1 = &button1;
		bootloader-led0 = &led0;
		mcuboot-button0 = &button0;
		mcuboot-led0 = &led0;
		scope-fpga-spi32 = &spi4;
	};

...

			spi4: spi@a000 {
				compatible = "nordic,nrf-spim";
				#address-cells = < 0x1 >;
				#size-cells = < 0x0 >;
				reg = < 0xa000 0x1000 >;
				interrupts = < 0xa 0x1 >;
				max-frequency = < 0x1e84800 >;
				easydma-maxcnt-bits = < 0x10 >;
				rx-delay-supported;
				rx-delay = < 0x2 >;
				status = "okay";
				pinctrl-0 = < &spi4_default >;
				pinctrl-1 = < &spi4_sleep >;
				pinctrl-names = "default", "sleep";
				cs-gpios = < &gpio0 0xb 0x1 >;
			};
			
...

		spi4_default: spi4_default {
			phandle = < 0x8 >;
			group1 {
				psels = < 0x40008 >, < 0x6000a >, < 0x50009 >;
			};
		};
		spi4_sleep: spi4_sleep {
			phandle = < 0x9 >;
			group1 {
				psels = < 0x40008 >, < 0x6000a >, < 0x50009 >;
				low-power-enable;
			};
		};

			

What am I missing to get the SPI pins to actually work?

  • I know that the SPI4 port is unusual, so I tried the same code with the SPI1 port at 8 MHz.  Same results:  The SCK, MOSI and MISO pins are stuck at ground, and the CS# pin seems to be floating (noise on the logic analyzer).

  • I'm embarrassed to say that part of the problem was that I had the logic analyzer set for 3.3V logic, not realizing that the nRF7002 board uses 1.8V!

    But in the mean time I have switched over to using the nrfx_spim library instead, and have made good progress with it.  I feel SO much more comfortable working more directly with the lower level drivers and hardware than the Zephyr abstractions, after a few years of working with the nRF5 SDK.

    So there is no need to answer this original ticket, and feel free to delete it.

Related