Accessing SPI Devices in Linux Print


The Linux kernel provides a device driver for the SPI controller of the Kinetis. To enable the driver in the kernel configuration, run make kmenuconfig from the project directory, go to Device Drivers and enable SPI Support. Then from SPI Support enable Freescale Faraday DSPI controllers (CONFIG_SPI_KINETIS in the kernel configuration):

Having enabled CONFIG_SPI_KINETIS, go to System Type -> Freescale Kinetis I/O interfaces and enable the specific SPI interfaces you require in your application. For purposes of this application note, we intend to make use of the SPI1 interface so we enable it in the kernel configuration:

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-kinetis/spi.c in the kernel tree:

#ifdef CONFIG_KINETIS_SPI1
platform_device_add_data(&kinetis_spi1_device,
&kinetis_spi1_pdata, sizeof(kinetis_spi1_pdata));
platform_device_register(&kinetis_spi1_device);
#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 Kinetis pins. More on that right below.

Allocation of the Kinetis pins to specific I/O interfaces is defined in linux/arch/arm/mach-kinetis/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 Kinetis pins that you have allocated for SPI in your design. For example, for SPI1 there is the following code defined in iomux.c:

#if defined(CONFIG_KINETIS_SPI1)
/* B.10 = SPI1_PCS0 */
{{KINETIS_GPIO_PORT_B, 10}, KINETIS_GPIO_CONFIG_MUX(2)},
/* B.11 = SPI1_SCK */
{{KINETIS_GPIO_PORT_B, 11}, KINETIS_GPIO_CONFIG_MUX(2)},
/* B.16 = SPI1_SOUT */
{{KINETIS_GPIO_PORT_B, 16}, KINETIS_GPIO_CONFIG_MUX(2)},
/* B.17 = SPI1_SIN */
{{KINETIS_GPIO_PORT_B, 17}, KINETIS_GPIO_CONFIG_MUX(2)},
#endif /* CONFIG_KINETIS_SPI1 */

Having configured the Kinetis pins as defined above, the kernel will register a platform device for the SPI1 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):

DSPI: controller 1 at hz=75000000,irq=27

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

/*
* Register SPI slaves
*/
spi_register_board_info(&spi_kinetis_flash_info__dongle,
sizeof(spi_kinetis_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 slave in your hardware configuration, in which case you need an array of several elements.

You must provide some information for each SPI slave you define in the struct spi_board_info array. Let's refer to the example defined in spi.c. In this example, we define configuration for an SPI Flash device manually wired to the SPI1 interface available on the breadboard area of the TWR-K70-SOM-BSB baseboard:

static struct spi_board_info
spi_kinetis_flash_info__dongle = {

.modalias = "spidev",
.max_speed_hz = 25000000,
.chip_select = 0,
.bus_num = 1,
.controller_data = &spi_kinetis_flash_slv__dongle,
.mode = SPI_MODE_3,
};

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 corresponding 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 1 for SPI1.

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.

Note also how the specific SPI slave device (SPI Flash in our example) is described for the SPI device driver further by a data structure pointed by the controller_data field:

static struct spi_mvf_chip
spi_kinetis_flash_slv__dongle = {
.mode = SPI_MODE_3,
.bits_per_word = 8,
};

Having defined a population of SPI slave devices in the kernel as described above, you will have an SPIDEV device on SPI1 at Chip Select 0. You will need to create a Linux 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 in the system. mdev is part of the multi-call busybox utility. To enable mdev in the busybox 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/spidev1.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");
}

On the host build the application as follows:

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

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,rsize=1024 172.17.0.1:/home/vlad/ /mnt
~ # /mnt/spidev_flash /dev/spidev1.0
response(7): 20 20 16 10 00 00
~ #