Accessing SPI Devices in Linux Print

 

The Linux kernel provides a device driver for the SPI controller of the STM32F7. Appropriate kernel configuration options are enabled in the rootfs project that is installed to each STM32F7 System-On-Module shipped by Emcraft. However, you would still have to perform some configuration of the Linux kernel in order to access specific SPI devices connected to the STM32F7 in your embedded design.

There are 6 SPI controllers available in the STM32F7. The STM32F7 allows different alternate pin selection for each SPI controller. If your design makes use of the SPI controllers in a non-default way, you may have to edit the following file in order to define application specific allocation of SPI interfaces to the STM32F7 pins. Consult the "Alternate function mapping" information from the STM32F7 Datasheet to select the appropriate pins and the AF (Alternate Function) values:

linux/arch/arm/boot/dts/stm32-som.dtsi

Another kernel configuration file that you will have to edit for sure defines information about SPI buses enabled in your application as well as about SPI devices connected to each SPI bus:

projects/rootfs/rootfs.dts.STM32F7


Raw SPI Device Access

In this example, we connect two SPI devices to the SPI2 bus on the STM32F7 and use the PB9 and PD6 singals as the chip selects for these two SPI devices. We will want to access these SPI devices in "raw mode" from the Linux user-space. Here is the relevant definition from the rootfs.dts.STM32F7 for this configuration:

/*
* SPI interfaces
*/
&spi_2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi_2>;
cs-gpios = <&gpiob 9 OUT>, <&gpiod 6 OUT>;
timeouts = <3>, <3>;


spidev: spidev@2 {
status = "okay";
compatible = "linux,spidev";
spi-max-frequency = <25000000>;
reg = <0>;
};

anotherspidev: anotherspidev@2 {
status = "okay";
compatible = "linux,spidev";
spi-max-frequency = <25000000>;
reg = <1>;
};
};

The SPI bus node format is described in detail in the linux/Documentation/devicetree/bindings/spi/spi-bus.txt file, so just a few notes:

  • The cs-gpios property is an array with the chip selects per device available on this bus.
  • The timeouts property is an array with the timeouts waiting for each SPI transfer to complete, per device available, in seconds.
  • The compatible property provides a link to the 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 for both devices is SPIDEV (compatible = "linux,spidev";), which provides access to the SPI device from the 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. To enable the SPIDEV interface in the kernel, the rootfs project has the CONFIG_SPI_SPIDEV option enabled by default.
  • The reg property allow to specify the chip select used to access the device (as the index in the
    cs-gpios array).

Having updated the dts file as described above, rebuild the project as described in Building Linux, and install it on the target as described in Installing Linux Images to Flash. Observe the kernel messages about a successfull SPI controller initialization as the kernel boots up on the target:

stm32-pinctrl pin-controller: maps: function spi_2 group spi_2-0 num 4
stm32-spi 40003800.spi: SPI Controller 1 at 40003800,irq=19,hz=50000000

The following device files will be available in this example:

/ # ls -1 /dev/spidev1.*
/dev/spidev1.0
/dev/spidev1.1

Now everything is ready to access the SPI devices 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)
{
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 -1;
}

printf("response(%d): ", status);
for (bp = buf; len; len--)
printf("%02x ", *bp++);
printf("\n");

return 0;
}

On the host, build the application as follows:

-bash-3.2$ arm-uclinuxeabi-gcc -o spidev_flash spidev_flash.c \
-mcpu=cortex-m3 -mthumb

Here is how the application runs on the target. Note the use of NFS mount to get access to the application binary:

/ # mount -o nolock 192.168.1.102:/mnt/work/emcraft/bsp/nfs /mnt
/ # /mnt/spidev_flash /dev/spidev1.0
response(7): 20 20 16 10 00 00


Access SPI Flash as MTD

In this example assume that we have a single SPI Flash device connected to the SPI2 bus of the STM32F7 controlled by the PB9 chip-select. Let's access this SPI Flash as a Linux MTD device with two partitions in it. For this configuration, your dts file will have to look as follows:

/*
* SPI interfaces
*/
&spi_2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi_2>;
cs-gpios = <&gpiob 9 OUT>;
timeouts = <3>;

/*
* Work with m25p80 as an MTD Flash chip
*/
flash: m25p32@2 {
status = "okay";
compatible = "spi-nor";
spi-max-frequency = <25000000>;
reg = <0>;

#address-cells = <1>;
#size-cells = <1>;

partition@0 {
label = "spi_flash_part0";
reg = <0x0 0x100000>;
};

parition@1 {
label = "spi_flash_part1";
reg = <0x100000 0x300000>;
};
};
};

The compatible property provides a link to the 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 MTD_M25P80 (compatible = "spi-nor";), which provides access to the SPI device as a Linux MTD device. To enable this interface in the kernel, the rootfs project has the CONFIG_MTD_M25P80 option enabled by default.

Rebuild the project as described in Building Linux and install it on the target as described in Installing Linux Images to Flash. As you boot Linux on the target, observe how the kernel configures the SPI Flash as an "MTD disk" with two partititions:

...
stm32-pinctrl pin-controller: maps: function spi_2 group spi_2-0 num 4
m25p80 spi1.0: m25p32 (4096 Kbytes)
2 ofpart partitions found on MTD device spi1.0
Creating 2 MTD partitions on "spi1.0":
0x000000000000-0x000000100000 : "spi_flash_part0"
0x000000100000-0x000000400000 : "spi_flash_part1"
stm32-spi 40003800.spi: SPI Controller 1 at 40003800,irq=19,hz=50000000
...

Verify that you may work with the SPI Flash as with an ordinary MTD device:

/ # cat /proc/mtd
dev: size erasesize name
mtd0: 00020000 00020000 "flash_uboot_env"
mtd1: 00a00000 00020000 "flash_linux_image"
mtd2: 005e0000 00020000 "flash_jffs2"
mtd3: 00100000 00010000 "spi_flash_part0"
mtd4: 00300000 00010000 "spi_flash_part1"
/ # flash_eraseall -j /dev/mtd3
Erasing 64 Kibyte @ 100000 - 100% complete.leanmarker written at f0000.
/ # mount -t jffs2 /dev/mtdblock3 /mnt
/ # cp /bin/busybox /mnt/
/ # reboot -f
reboot: Restarting system
...
/ # mount -t jffs2 /dev/mtdblock3 /mnt
/ # ls -lt /mnt
-rwxr-xr-x 1 root root 271256 Jan 1 02:27 busybox
/ # /mnt/busybox echo "Hello with busybox from SPI Flash"
Hello with busybox from SPI Flash