Accessing SPI Devices in Linux Print

 

The Linux kernel provides a device driver for the SPI controller of the STM32F429. To enable the driver in the kernel configuration, run make kmenuconfig, go to Device Drivers and enable SPI Support. Then from SPI Support enable STM32 SPI Controller (CONFIG_SPI_STM32 in the kernel configuration):

Having enabled CONFIG_SPI_STM32, go to System Type -> STM32 I/O interfaces and enable the specific SPI interfaces you require in your application:

For each SPI interface you enable in the kernel configuration, the platform initialization code will register a platform device with the kernel. Refer to linux/arch/arm/mach-stm32/spi.c in the kernel tree:

...
#if defined(CONFIG_STM32_SPI4)
platform_set_drvdata(&spi_stm32_dev4,
(void *) stm32_clock_get(CLOCK_PCLK2));
platform_device_register(&spi_stm32_dev4);
#endif
...

Do not enable any SPI interfaces except for those that you plan to use in your application. For one thing, unless an SPI interface is actually required, you want to keep it in reset in order to save some power. Another consideration is that SPI signals may conflict with other I/O interfaces on the STM32F4 pins. More on that right below.

Allocation of the STM32F429 pins to specific I/O interfaces is defined in linux/arch/arm/mach-stm32/iomux.c. When you have enabled SPI interfaces that you require in your application, make sure that there is appropriate code in iomux.c that routes the SPI signals to those STM32F429 pins that you have allocated for SPI in your application. For example, for SPI4 there is the following code defined in iomux.c:

#if defined(CONFIG_STM32_SPI4)
gpio_dsc.port = 4; /* CLCK */
gpio_dsc.pin = 2;
stm32f2_gpio_config(&gpio_dsc, STM32F2_GPIO_ROLE_SPI4);

gpio_dsc.port = 4; /* DI */
gpio_dsc.pin = 5;
stm32f2_gpio_config(&gpio_dsc, STM32F2_GPIO_ROLE_SPI4);

gpio_dsc.port = 4; /* DO */
gpio_dsc.pin = 6;
stm32f2_gpio_config(&gpio_dsc, STM32F2_GPIO_ROLE_SPI4);

gpio_dsc.port = 4; /* CS */
gpio_dsc.pin = 4;
stm32f2_gpio_config(&gpio_dsc, STM32F2_GPIO_ROLE_OUT);
#endif

Having configured the STM32F4 pins as defined above, the kernel will register a platform device for the SPI4 controller. You should see the following line in the kernel boot messages on the target (note that the kernel counts the SPI interfaces from 0 rather than 1):

spi_stm32 spi_stm32.3: SPI Controller 3 at 40013400,hz=90000000

The next step is to define population of SPI devices residing on each SPI bus in your concrete hardware configuration. This is done in linux/arch/arm/mach-stm32/spi.c using a call to spi_register_board_info:

/*
* Register SPI slaves
*/
spi_register_board_info(&spi_stm32_flash_info__dongle,
sizeof(spi_stm32_flash_info__dongle) /
sizeof(struct spi_board_info));

The first argument to spi_register_board_info is an array of the data structures typed as struct spi_board_info. The second argument is the size of the array. In the example above, we define just a single SPI device so there is a single element in the array and the first argument is an address of a struct spi_board_info variable rather than a pointer to an array. However, you can have more than one SPI device in your hardware configuration, in which case you need an array of several elements.

You must provide some information for each SPI device you define in the struct spi_board_info array. Let's refer to the example defined in spi.c:

static struct spi_board_info
spi_stm32_flash_info__dongle = {
#if defined(CONFIG_SPI_SPIDEV)
.modalias = "spidev",
#endif
.max_speed_hz = 25000000,
.bus_num = 3,
.chip_select = 0,
.controller_data = &spi_stm32_flash_slv__dongle,
};

The modalias field provides a link to a client SPI device driver, which will be used by the kernel to service a specific SPI device. In the example above, the client SPI device driver is SPIDEV, which provides access to the SPI device from user space using "raw" SPI transactions. This interface is frequently used in embedded applications to control SPI devices (such as, for instance, SPI sensors) directly from user space code. Note that to enable the SPIDEV interface in the kernel, you need to enable User mode SPI device driver support in the SPI Support kernel configuration menu (see the first capture in the above text).

The max_speed_hz field defines the frequency that the SPI device driver is to use to access a specific SPI device. The device driver will attempt to use a maximum frequency that is less or equal to max_speed_hz and possible to derive from the clock the SPI bus is running from.

The bus_num field is the number assigned by the kernel to the specific SPI interface. As mentioned above, the kernel counts interfaces from 0 so bus_num must be set to 3 for SPI4.

The chip_select is the chip select index you assign to a specific SPI device. A unique chip_select must be assigned to each device on a single SPI bus.

It is important to note that the STM32F429 SPI device driver controls the Chip Select signals as plain GPIO pins. The driver activates the Chip Select GPIO signal when there is an SPI transaction targeting the corresponding SPI device and then de-activates the GPIO signal when an SPI transaction is completed. A GPIO pin used for Chip Select to a specific SPI device is described in a data structure pointed by the controller_data field:

static struct spi_stm32_slv spi_stm32_flash_slv__dongle = {
.cs_gpio =
STM32_GPIO_PORTPIN2NUM(
SPI_FLASH_CS_PORT__STM32F4_DISCO,
SPI_FLASH_CS_PIN__STM32F4_DISCO),
.timeout = 3,
};

...
.controller_data = &spi_stm32_flash_slv__dongle,
};

Make sure you de-activate the Chip Select GPIO before registering the SPI device:

gpio_direction_output(STM32_GPIO_PORTPIN2NUM(
SPI_FLASH_CS_PORT__STM32F4_DISCO,
SPI_FLASH_CS_PIN__STM32F4_DISCO), 1);

Having defined a population of SPI devices in the kernel as described above, you will have an SPIDEV device on SPI4 at Chip Select 0. You will need to create a device node that your application code would be able to open() on the target in order to get access to the device via the SPIDEV API. The easiest way to create the device node on the target is to issue a call to mdev -s. mdev is a user-space Linux utility that can be used to populate the /dev directory with device files corresponding to devices present on the system. mdev is part of the multi-call busybox utility. To enable mdev in the busbox configuration, run make bmenuconfig, then go to Linux system utilities and enable mdev:

Also, you will need to add a symlink for mdev to the target file system. This is done by adding the following line to the <project>.initramfs file:

slink /bin/mdev busybox 777 0 0

Having updated your project configuration as described above, build the bootable Linux image (<project>.uImage) by running make in the project directory.

On the target run mdev -s, either from the shell interactive interface or from a start-up script such as /etc/rc, to create device nodes for devices registered with the kernel:

~ # mdev -s
~ # ls -lt /dev/spi*
crw-rw---- 1 root root 153, 0 Jan 1 00:00 /dev/spidev3.0
~ #

Now finally everything is ready to access the SPI device from a user-space application using the SPIDEV interface. There is abundant documentation in the Internet on how to use SPIDEV in application code. The basics are described, for instance, in the following article:

https://www.kernel.org/doc/Documentation/spi/spidev

Here is a very simple demo application that shows how to read the Flash ID from an SPI Flash device:

/*
* Sample application that makes use of the SPIDEV interface
* to access an SPI slave device. Specifically, this sample
* reads a Device ID of a JEDEC-compliant SPI Flash device.
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(int argc, char **argv)
{
/*
* This assumes that a 'mdev -s' has been run to create
* /dev/spidev* devices after the kernel bootstrap.
* First number is the "bus" (SPI contoller id), second number
* is the "chip select" of the specific SPI slave
* ...
* char *name = "/dev/spidev1.1";
*/
char *name;
int fd;
struct spi_ioc_transfer xfer[2];
unsigned char buf[32], *bp;
int len, status;

name = argv[1];
fd = open(name, O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}

memset(xfer, 0, sizeof xfer);
memset(buf, 0, sizeof buf);
len = sizeof buf;

/*
* Send a GetID command
*/
buf[0] = 0x9f;
len = 6;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 1;

xfer[1].rx_buf = (unsigned long) buf;
xfer[1].len = 6;

status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return;
}
printf("response(%d): ", status);
for (bp = buf; len; len--)
printf("%02x ", *bp++);
printf("\n");
}

Here is how the application runs on the STM32F4 target:

~ # /spidev_flash /dev/spidev3.0
response(7): 20 20 16 10 00 00
~ #