Interfacing with the SPI driver

Note that this SPI guide uses Raspberry Pi 4 as the target. Hence, it uses the spi-bcm2711 driver. Different boards might require a different driver, but interfacing with the driver should be similar across different boards

Starting the SPI driver

To start the SPI resource manager on your target if it's not already running, you can use the command:

spi-bcm2711

By default, the SPI driver starts with a default config file located in /etc/system/config/spi/spi.conf. You can make your own custom SPI config file that may look something like below. For more information on the SPI configuration file, visit the "Configuring SPI" page in the SPI Framework Technote.

[globals]
verbose=5

[bus]
busno=0
name=spi0
base=0xfe204000
irq=150
input_clock=500000000
bs=rpanic=48,tpanic=16
dma_attach_opts=num_cbs=256,range_min=0,range_max=14,typed_mem=sysram&below1G
dma_thld=4

[dev]
parent_busno=0
devno=0
name=dev0
clock_rate=5000000
cpha=1
cpol=0
bit_order=msb
word_width=32
idle_insert=1

[dev]
parent_busno=0
devno=1
name=dev1
cpha=0
cpol=1
word_width=32
clock_rate=5000000

[bus]
busno=3
name=spi3
base=0xfe204600
irq=151
input_clock=500000000

[dev]
parent_busno=3
devno=0
name=dev0
cpha=0
cpol=1
word_width=8
clock_rate=5000000

Use the use command to display more information about starting the driver or visit the "Starting a SPI device driver" page in the SPI Framework Technote.

root@qnxpi:/data/home/qnxuser# use spi-bcm2711
SPI common options

Syntax:
spi-bcm2711 [-c config_file] [-v]

    -c config_file      Path to the SPI configuration file
                        (default: /etc/system/config/spi/spi.conf)
    -v                  Increase verbosity. This will override the "verbose" value in the config file.

Note: SPI config file template can be found at "lib/io-spi/config_files/spi-template.conf".

Examples:

# Start the driver using a custom config file path
spi-bcm2711 -c /etc/custom/spi.conf

Driver-specific options:
(To be set in the config file by parameter 'bs' under section 'bus'.)

    rpanic=num      Defines read panic threshold (default: 48)
    tpanic=num      Defines write panic threshold (default: 16)

Exchanging data with the SPI device

Once you've started the SPI driver, it exposes a path under /dev/io-spi/spi<bus_number>/dev<device_number>. You can use the devctl() command to interface with the SPI driver. Optionally, you can also use the normal open(), read(), write(), and close() commands to interface with the SPI driver. You can find the full interface in hw/io-spi.h.

The following example demonstrates writing and reading data from a SPI device. First, you need to define the data exchange structure:

typedef struct {
    uint32_t    nbytes;           /* Exchange data length */
    uint8_t     data[0];          /* Exchange data */
} spi_xchng_t;

Before communicating, you need to configure the SPI device by using the spi_cfg_t structure:

typedef struct {
    uint32_t    mode;        /* SPI mode */
    uint32_t    clock_rate;  /* bus speed */
} spi_cfg_t;

An example on how to set up and perform SPI communication could look something like this:

// 1. Open the SPI device
char spi_device_name[20];
sprintf(spi_device_name, "/dev/io-spi/spi%d/dev%d", bus_number, device_number);
int fd = open(spi_device_name, O_RDWR);

// 2. Configure the device
spi_cfg_t spi_device_cfg = {
.mode = mode, // SPI mode
.clock_rate = spi_device_speed_hz // Clock rate in Hz
};
// Configure the SPI device
devctl(fd, DCMD_SPI_SET_CONFIG, &spi_device_cfg, sizeof(spi_cfg_t), NULL);

// 3. Prepare data for transfer
spi_xchng_t spi_xchng_msg = malloc(sizeof(spi_xchng_t) + data_size); // allocate enough memory for the data
spi_xchng_msg->nbytes = data_size;
// Fill data buffer
for (int i = 0; i < data_size; i++) {
spi_xchng_msg->data[i] = data_buffer[i];
}

// 4. Perform the transfer
devctl(fd, DCMD_SPI_DATA_XCHNG, spi_xchng_msg, sizeof(spi_xchng_t) + data_size, NULL);

// 5. Clean up
free(spi_xchng_msg);
close(fd);

Look at the rpi_spi_configure_device() and rpi_spi_write_read_data() function for more information. You can take a similar approach for other devctl() DCMDs.

You can find a list of the devctl() commands in the "DCMD_SPI_*" section of the Devctl and Ioctl Commands guide.

Sample SPI device

Now that you have the necessary functions to communicate with your SPI device in the rpi_spi folder, take a look at the example user application code that talks with the ws281x device using SPI.

Hardware configuration for ws281x:

  • Mode: 0b00010000010000100000

  • Frequency: 6500000

Given the hardware configuration, you can configure the device using the rpi_spi_configure_device() function from the SPI interface:

rpi_spi_configure_device(bus_number, device_number,
0b00010000010000100000, 6500000)

For the full initialization implementation for ws281x, you can look at the functions ws2811_init() and spi_init().

After configuring the device, you can transfer data through SPI communication using the function rpi_spi_write_read_data():

rpi_spi_write_read_data(bus_number, device_number, data, data_size)

For the full implementation of data transfer for ws281x, refer to the ws2811_render() and spi_transfer() functions.

Porting Linux SPI device to QNX

When porting an SPI device from Linux to QNX, the key difference lies in how the SPI bus is accessed. In Linux, communication typically involves ioctl() calls with commands like SPI_IOC_WR_MODE, SPI_IOC_WR_BITS_PER_WORD, and SPI_IOC_WR_MAX_SPEED_HZ to configure the SPI device parameters, and uses SPI_IOC_MESSAGE command to transfer data. In contrast, QNX relies mostly on devctl() with commands such as DCMD_SPI_DATA_XCHNG for data exchange and DCMD_SPI_SET_CONFIG for device configuration. It's also important to note that QNX and Linux use different message structures to communicate with the SPI device. For example, Linux uses spi_ioc_transfer structures for data transfer operations, while QNX uses spi_xchng_t for data exchange and spi_cfg_t for configuration.

Furthermore, QNX provides additional functionality through devctl() commands like DCMD_SPI_GET_DRVINFO and DCMD_SPI_GET_DEVINFO to query driver and device information, which isn't directly available in the Linux SPI interface. Also, note that both Linux and QNX supports the regular open(), read(), write(), and close() operations for communicating through SPI.

Page updated: