Devicetree and nrfx SPI slave driver configuration

First - I haven't been able to find an example of how to configure and use "nordic,nrf-spis" in NCS. If one exists that would probably help.

I configure SPI like this in devicetree (moved to using pinctrl to avoid warnings on pindefinitions - and it seems smart although I haven't fully grasped it yet):

&pinctrl {
	spi0_default: spi0_default {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
				<NRF_PSEL(SPIM_MISO, 0, 14)>,
				<NRF_PSEL(SPIM_MOSI, 0, 16)>;
		};
	};	

	spi0_sleep: spi0_sleep {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
				<NRF_PSEL(SPIM_MISO, 0, 14)>,
				<NRF_PSEL(SPIM_MOSI, 0, 16)>;
			low-power-enable;

		};
	};	
};

&spi0 {
		compatible = "nordic,nrf-spis";
		status = "okay";
		cs-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
		a: spi-dev-a@0 {
			reg = <0>;
		};
		pinctrl-0 = <&spi0_default>;
		pinctrl-1 = <&spi0_sleep>;
		pinctrl-names = "default", "sleep";
		def-char = <0xFF>;
};

However, I can not figure out how to access the DTC info from the code to set the pins in "nrfx_spis_config_t".

For the moment I repeat the PIN numbers in my code - obviously not what I want.

rgds Tage

Parents
  • The short answer is 'you're not supposed to'. 

    Zephyr's SPI device driver API is the intended access point for your 'a: spi-dev-a@0' devicetree node.

    The build system knows that the 'a: spi-dev-a@0' node is a child node of the SPI controller node 'spi0' because its declaration follows the rules of how a child node is decleared (ie. inside its parent node).

    The build system knows that the 'spi0' is an SPI controller node because its 'compatible' property is "nordic,nrf-spis". 

    The build system knows that the 'compatible' property of the 'spi0' node refers to a specific devicetree 'binding', in this case zephyr\dts\bindings\spi\nordic,nrf-spis.yaml: 

    description: Nordic nRF family SPIS (SPI slave with EasyDMA)
    
    compatible: "nordic,nrf-spis"
    
    include: nordic,nrf-spi-common.yaml
    
    properties:
        def-char:
          type: int
          required: true
          description: |
              Default character. Character clocked out when the slave was not
              provided with buffers and is ignoring the transaction.

    nordic,nrf-spis.yaml includes another binding, nordic,nrf-spi-common.yaml. Who, in turn, includes the spi-controller.yaml binding.  

    The build system now knows that the 'spi0' node is in fact an SPI controller node, and it needs a driver that has implemented the SPI controller API. In this case it is the spi_nrfx_spis.c driver. 

    If CONFIG_NRFX_SPIS and CONFIG_NRFX_SPIS0 =y the spi_nrfx_spis.c will be built and linked to the zephyr's SPI controller API functions. 

    The zephyr\drivers\spi\spi_nrfx_spis.c line 267-342 will initialize the driver in modules\hal\nordic\nrfx\drivers\src\nrfx_spis.c after the kernel has booted, with the configuration set in your DTS and DTS overlay files. 

    /*
     * Current factors requiring use of DT_NODELABEL:
     *
     * - HAL design (requirement of drv_inst_idx in nrfx_spis_t)
     * - Name-based HAL IRQ handlers, e.g. nrfx_spis_0_irq_handler
     */
    
    #define SPIS(idx) DT_NODELABEL(spi##idx)
    #define SPIS_PROP(idx, prop) DT_PROP(SPIS(idx), prop)
    
    #define SPI_NRFX_SPIS_PIN_CFG(idx)					\
    	COND_CODE_1(CONFIG_PINCTRL,					\
    		(.skip_gpio_cfg = true,					\
    		 .skip_psel_cfg = true,),				\
    		(.sck_pin    = SPIS_PROP(idx, sck_pin),			\
    		 .mosi_pin   = DT_PROP_OR(SPIS(idx), mosi_pin,		\
    					  NRFX_SPIS_PIN_NOT_USED),	\
    		 .miso_pin   = DT_PROP_OR(SPIS(idx), miso_pin,		\
    					  NRFX_SPIS_PIN_NOT_USED),	\
    		 .csn_pin    = SPIS_PROP(idx, csn_pin),			\
    		 .csn_pullup = NRF_GPIO_PIN_NOPULL,			\
    		 .miso_drive = NRF_GPIO_PIN_S0S1,))
    
    #define SPI_NRFX_SPIS_DEFINE(idx)					       \
    	NRF_DT_CHECK_PIN_ASSIGNMENTS(SPIS(idx), 0,			       \
    				     sck_pin, mosi_pin, miso_pin, csn_pin);    \
    	static void irq_connect##idx(void)				       \
    	{								       \
    		IRQ_CONNECT(DT_IRQN(SPIS(idx)), DT_IRQ(SPIS(idx), priority),   \
    			    nrfx_isr, nrfx_spis_##idx##_irq_handler, 0);       \
    	}								       \
    	static struct spi_nrfx_data spi_##idx##_data = {		       \
    		SPI_CONTEXT_INIT_LOCK(spi_##idx##_data, ctx),		       \
    		SPI_CONTEXT_INIT_SYNC(spi_##idx##_data, ctx),		       \
    	};								       \
    	IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_DEFINE(SPIS(idx))));	       \
    	static const struct spi_nrfx_config spi_##idx##z_config = {	       \
    		.spis = {						       \
    			.p_reg = (NRF_SPIS_Type *)DT_REG_ADDR(SPIS(idx)),      \
    			.drv_inst_idx = NRFX_SPIS##idx##_INST_IDX,	       \
    		},							       \
    		.config = {						       \
    			SPI_NRFX_SPIS_PIN_CFG(idx)			       \
    			.mode      = NRF_SPIS_MODE_0,			       \
    			.bit_order = NRF_SPIS_BIT_ORDER_MSB_FIRST,	       \
    			.orc       = SPIS_PROP(idx, overrun_character),	       \
    			.def       = SPIS_PROP(idx, def_char),		       \
    		},							       \
    		.irq_connect = irq_connect##idx,			       \
    		IF_ENABLED(CONFIG_PINCTRL,				       \
    			(.pcfg = PINCTRL_DT_DEV_CONFIG_GET(SPIS(idx)),))       \
    	};								       \
    	DEVICE_DT_DEFINE(SPIS(idx),					       \
    			    spi_nrfx_init,				       \
    			    NULL,					       \
    			    &spi_##idx##_data,				       \
    			    &spi_##idx##z_config,			       \
    			    POST_KERNEL,				       \
    			    CONFIG_SPI_INIT_PRIORITY,			       \
    			    &spi_nrfx_driver_api)
    
    #ifdef CONFIG_SPI_0_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(0);
    #endif
    
    #ifdef CONFIG_SPI_1_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(1);
    #endif
    
    #ifdef CONFIG_SPI_2_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(2);
    #endif
    
    #ifdef CONFIG_SPI_3_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(3);
    #endif

    To use zephyr's SPI device driver API you need to let the build system know that the devicetree node you accessing is in fact an SPI device. This is done by adding the property 'compatible = "spi-device";' to your 'a: spi-dev-a@0' node. 

    Now you can access your SPI device:

    #include <zephyr/drivers/spi.h>
    
    spi_device_callback(const struct device *dev, int result, void *data)
    {
        //Get result and data
    }
    
    void main(void)
    {
        const struct device *const spi_device = DEVICE_DT_GET(DT_NODELABEL(a));
        
        if (!device_is_ready(spi_device)) {
        		printk("%s: device not ready.\n", spi_device->name);
        		return;
        	}
        
        const struct spi_driver_api *spi_device_api = (const struct spi_driver_api *)spi_device->api;
        
        const struct spi_config spi_device_cfg;
        spi_device_cfg.operation = SPI_OP_MODE_SLAVE;
        
        uint8_t spi_device_tx_buf[128];
        struct spi_buf tx_buf;
        tx_buf.buf = &spi_device_tx_buf;
        tx_buf.len = sizeof(spi_device_tx_buf);
        const struct spi_buf_set tx_bufs;
        tx_bufs.buffers = &tx_buf;
        tx_bufs.count = 1;
        
        uint8_t spi_device_rx_buf[128];
        struct spi_buf rx_buf;
        rx_buf.buf = &spi_device_rx_buf;
        rx_buf.len = sizeof(spi_device_rx_buf);
        const struct spi_buf_set rx_bufs;
        rx_bufs.buffers = &rx_buf;
        rx_bufs.count = 1;
        
        int rc = 0;
        rc = spi_device_api.transceive_async(spi_device, &spi_device_cfg, &tx_bufs, &rx_bufs, spi_device_callback, NULL);
    }



  • First I tried this principle with a SPI master. That worked - needed to specify the CS pin in the spi_config like this:

        spi_device_cfg.cs = SPI_CS_CONTROL_PTR_DT(DT_NODELABEL(spiipc), 2);

    Which makes sense for a master SPI. I got the driver reference like this:

    static const struct device *spi_device = DEVICE_DT_GET(DT_CHOSEN(bluejay_ipc_master));

    However, I cannot get the slave working. 

    I have tried different ways of configuring:

    1. With DT_CHOSEN like the working SPI master.
    2. DT_NODELABEL( a ) like you suggest.

    1) compiles but doesn't work. 2) doesn't compile.

    When I break at configure_pins in module "nrfx_spis.c", both skip_gpio_cfg and skip_psel_cfg of p_config is true, leading me to conclude that the CS pin is never configured by the driver.

    What to do?

    rgds Tage

  • During initialization of the nrfx driver PSEL.CSN is not set - all other pins are set.

    If I set the PSEL.CSN like this in spi_nrfx_init

    dev_config->spis.p_reg->PSEL.CSN = 12;

    it works, so obviously my configuration is not right.

    Overlay for slave SPI:

    /delete-node/ &boot_partition;
    /delete-node/ &slot1_partition;
    /delete-node/ &i2c0;
    
    / {
    	chosen {
    		bluejay,ipc-slave = &spi0;
    	};
    };
    &pinctrl {
    	spi0_default: spi0_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
    				<NRF_PSEL(SPIM_MISO, 0, 14)>,
    				<NRF_PSEL(SPIM_MOSI, 0, 16)>;
    		};
    	};	
    
    	spi0_sleep: spi0_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
    				<NRF_PSEL(SPIM_MISO, 0, 14)>,
    				<NRF_PSEL(SPIM_MOSI, 0, 16)>;
    			low-power-enable;
    
    		};
    	};	
    };
    
    &spi0 {
    		compatible = "nordic,nrf-spis";
    		status = "okay";
    		cs-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
    		a: spi-device@0 {
    			reg = <0>;
    		};
    		pinctrl-0 = <&spi0_default>;
    		pinctrl-1 = <&spi0_sleep>;
    		pinctrl-names = "default", "sleep";
    		def-char = <0xFF>;
    		overrun-character = <0xaa>;
    };
    /delete-node/ &led3;
    
    / {
    	leds {
                    led3: led_3 {
    			gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
    			label = "Green LED 3";
                    };
    		gpio_evt: gpio_evt_0 {
                            gpios = <&gpio0 20 (GPIO_PUSH_PULL | GPIO_ACTIVE_LOW)>;
    			label = "GPIO Event Pin";
    		};
    	};
    };
    
    

  • Try this: 

    /delete-node/ &boot_partition;
    /delete-node/ &slot1_partition;
    /delete-node/ &i2c0;
    
    / {
    	chosen {
    		bluejay,ipc-slave = &spi0;
    	};
    };
    &pinctrl {
    	spi0_default: spi0_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
    				<NRF_PSEL(SPIM_MISO, 0, 14)>,
    				<NRF_PSEL(SPIM_MOSI, 0, 16)>,
    				<NRF_PSEL(SPIM_CSN, 0, 12);
    		};
    	};	
    
    	spi0_sleep: spi0_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
    				<NRF_PSEL(SPIM_MISO, 0, 14)>,
    				<NRF_PSEL(SPIM_MOSI, 0, 16)>,
    				<NRF_PSEL(SPIM_CSN, 0, 12);
    			low-power-enable;
    
    		};
    	};	
    };
    
    &spi0 {
    		compatible = "nordic,nrf-spis";
    		status = "okay";
    		a: spi-device@0 {
    			reg = <0>;
    		};
    		pinctrl-0 = <&spi0_default>;
    		pinctrl-1 = <&spi0_sleep>;
    		pinctrl-names = "default", "sleep";
    		def-char = <0xFF>;
    		overrun-character = <0xaa>;
    };
    /delete-node/ &led3;
    
    / {
    	leds {
                    led3: led_3 {
    			gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
    			label = "Green LED 3";
                    };
    		gpio_evt: gpio_evt_0 {
                            gpios = <&gpio0 20 (GPIO_PUSH_PULL | GPIO_ACTIVE_LOW)>;
    			label = "GPIO Event Pin";
    		};
    	};
    };


    From zephyr\dts\bindings\spi\nordic,nrf-spis.yaml: 
    # Copyright (c) 2019 Nordic Semiconductor ASA
    # SPDX-License-Identifier: Apache-2.0
    
    description: Nordic nRF family SPIS (SPI slave with EasyDMA)
    
    compatible: "nordic,nrf-spis"
    
    include: nordic,nrf-spi-common.yaml
    
    properties:
        csn-pin:
          type: int
          deprecated: true
          required: false
          description: |
            IMPORTANT: This option will only be used if the new pin control driver
            is not enabled.
    
            The CSN pin to use. The pin numbering scheme is the same as
            the sck-pin property's.
    

    From zephyr\include\zephyr\dt-bindings\pinctrl\nrf-pinctrl.h:

    #define NRF_FUN_SPIS_SCK 7U
    /** SPI slave MOSI */
    #define NRF_FUN_SPIS_MOSI 8U
    /** SPI slave MISO */
    #define NRF_FUN_SPIS_MISO 9U
    /** SPI slave CSN */
    #define NRF_FUN_SPIS_CSN 10U

    I believe the new pinctrl feature of zephyr is intended to allow for multiple drivers to share access to the same pins, i.e. runtime re-configuration of a GPIO multiplexer. Previously, only one devicetree node could have access to a GPIO, defined at compile-time. 

  • -edit, typo: NRF_PSEL(SPIM_XXXX  ---> NRF_PSEL(SPIS_XXXX

    /delete-node/ &boot_partition;
    /delete-node/ &slot1_partition;
    /delete-node/ &i2c0;
    
    / {
    	chosen {
    		bluejay,ipc-slave = &spi0;
    	};
    };
    &pinctrl {
    	spi0_default: spi0_default {
    		group1 {
    			psels = <NRF_PSEL(SPIS_SCK, 0, 18)>,
    				<NRF_PSEL(SPIS_MISO, 0, 14)>,
    				<NRF_PSEL(SPIS_MOSI, 0, 16)>,
    				<NRF_PSEL(SPIS_CSN, 0, 12);
    		};
    	};	
    
    	spi0_sleep: spi0_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
    				<NRF_PSEL(SPIM_MISO, 0, 14)>,
    				<NRF_PSEL(SPIM_MOSI, 0, 16)>,
    				<NRF_PSEL(SPIM_CSN, 0, 12);
    			low-power-enable;
    
    		};
    	};	
    };
    
    &spi0 {
    		compatible = "nordic,nrf-spis";
    		status = "okay";
    		a: spi-device@0 {
    			reg = <0>;
    		};
    		pinctrl-0 = <&spi0_default>;
    		pinctrl-1 = <&spi0_sleep>;
    		pinctrl-names = "default", "sleep";
    		def-char = <0xFF>;
    		overrun-character = <0xaa>;
    };
    /delete-node/ &led3;
    
    / {
    	leds {
                    led3: led_3 {
    			gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
    			label = "Green LED 3";
                    };
    		gpio_evt: gpio_evt_0 {
                            gpios = <&gpio0 20 (GPIO_PUSH_PULL | GPIO_ACTIVE_LOW)>;
    			label = "GPIO Event Pin";
    		};
    	};
    };

  • Hi Haakon,

    Thanks for this.

    I got it working by adding the following hack in my slave SPI init code:

        // Hack to get CS pin configured for slave
    	NRF_SPIS_Type *spis = ( NRF_SPIS_Type *)DT_REG_ADDR( DT_CHOSEN( bluejay_ipc_slave ));
    	spis->PSEL.CSN = DT_SPI_DEV_CS_GPIOS_PIN( DT_NODELABEL( a ));

    Thank you for providing alternatives.

    Do you know of an upcoming nordic,nrf-spis update that will make it work with pin control driver?

  • Did you read my last reply? 

    The nRF52832's SPIM peripheral does not have an PSEL.CSN register, therefore pinctrl_nrf.c skips configuration of NRF_PSEL(SPIM_CSN, 0, 12).
    You need to use SPIS instead of SPIM in the pinctrl devicetree node.

    From zephyr\drivers\pinctrl\pinctrl_nrf.c:

    int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
    			   uintptr_t reg)
    {
    	for (uint8_t i = 0U; i < pin_cnt; i++) {
    		nrf_gpio_pin_drive_t drive = NRF_GET_DRIVE(pins[i]);
    		uint32_t pin = NRF_GET_PIN(pins[i]);
    		uint32_t write = NO_WRITE;
    		nrf_gpio_pin_dir_t dir;
    		nrf_gpio_pin_input_t input;
    
    		if (pin == NRF_PIN_DISCONNECTED) {
    			pin = 0xFFFFFFFFU;
    		}
    
    		switch (NRF_GET_FUN(pins[i])) {
    
    #if defined(NRF_PSEL_SPIM)
    		case NRF_FUN_SPIM_SCK:
    			NRF_PSEL_SPIM(reg, SCK) = pin;
    			write = 0U;
    			dir = NRF_GPIO_PIN_DIR_OUTPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    		case NRF_FUN_SPIM_MOSI:
    			NRF_PSEL_SPIM(reg, MOSI) = pin;
    			write = 0U;
    			dir = NRF_GPIO_PIN_DIR_OUTPUT;
    			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
    			break;
    		case NRF_FUN_SPIM_MISO:
    			NRF_PSEL_SPIM(reg, MISO) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    #endif /* defined(NRF_PSEL_SPIM) */
    
    #if defined(NRF_PSEL_SPIS)
    		case NRF_FUN_SPIS_SCK:
    			NRF_PSEL_SPIS(reg, SCK) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    		case NRF_FUN_SPIS_MOSI:
    			NRF_PSEL_SPIS(reg, MOSI) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    		case NRF_FUN_SPIS_MISO:
    			NRF_PSEL_SPIS(reg, MISO) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
    			break;
    		case NRF_FUN_SPIS_CSN:
    			NRF_PSEL_SPIS(reg, CSN) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    #endif /* defined(NRF_PSEL_SPIS) */

Reply
  • Did you read my last reply? 

    The nRF52832's SPIM peripheral does not have an PSEL.CSN register, therefore pinctrl_nrf.c skips configuration of NRF_PSEL(SPIM_CSN, 0, 12).
    You need to use SPIS instead of SPIM in the pinctrl devicetree node.

    From zephyr\drivers\pinctrl\pinctrl_nrf.c:

    int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
    			   uintptr_t reg)
    {
    	for (uint8_t i = 0U; i < pin_cnt; i++) {
    		nrf_gpio_pin_drive_t drive = NRF_GET_DRIVE(pins[i]);
    		uint32_t pin = NRF_GET_PIN(pins[i]);
    		uint32_t write = NO_WRITE;
    		nrf_gpio_pin_dir_t dir;
    		nrf_gpio_pin_input_t input;
    
    		if (pin == NRF_PIN_DISCONNECTED) {
    			pin = 0xFFFFFFFFU;
    		}
    
    		switch (NRF_GET_FUN(pins[i])) {
    
    #if defined(NRF_PSEL_SPIM)
    		case NRF_FUN_SPIM_SCK:
    			NRF_PSEL_SPIM(reg, SCK) = pin;
    			write = 0U;
    			dir = NRF_GPIO_PIN_DIR_OUTPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    		case NRF_FUN_SPIM_MOSI:
    			NRF_PSEL_SPIM(reg, MOSI) = pin;
    			write = 0U;
    			dir = NRF_GPIO_PIN_DIR_OUTPUT;
    			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
    			break;
    		case NRF_FUN_SPIM_MISO:
    			NRF_PSEL_SPIM(reg, MISO) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    #endif /* defined(NRF_PSEL_SPIM) */
    
    #if defined(NRF_PSEL_SPIS)
    		case NRF_FUN_SPIS_SCK:
    			NRF_PSEL_SPIS(reg, SCK) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    		case NRF_FUN_SPIS_MOSI:
    			NRF_PSEL_SPIS(reg, MOSI) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    		case NRF_FUN_SPIS_MISO:
    			NRF_PSEL_SPIS(reg, MISO) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_DISCONNECT;
    			break;
    		case NRF_FUN_SPIS_CSN:
    			NRF_PSEL_SPIS(reg, CSN) = pin;
    			dir = NRF_GPIO_PIN_DIR_INPUT;
    			input = NRF_GPIO_PIN_INPUT_CONNECT;
    			break;
    #endif /* defined(NRF_PSEL_SPIS) */

Children
Related