Introduction
This article shows how the SPI bus functionality of the SAMA5D2 Series ARM® Cortex®-A5 Microprocessor Unit (MPU) is enabled in the Linux® kernel and how to access the SPI bus in user space.
Since the SPI device interface was introduced into the Linux kernel, you can access the SPI driver via spi_register_driver() interface via the structure spi_device handle.
You can also access the SPI driver in user space via the /dev/spidev device node. SPI devices have a limited user-space API, supporting basic half-duplex read() and write() access to SPI slave devices. Using ioctl() requests, full-duplex transfers and device I/O configuration are also available. We show you how, using a C-language program.
Prerequisites
This application is developed for the ATSAMA5D27-SOM1-EK1 development platform:
This application is developed using the Buildroot build system.
Hardware
For this application, you will be controlling the SPI bus of the mikroBUS 1 expansion socket of the ATSAMA5D27-SOM1-EK1. The figure below shows the expansion capability of the SOM1-EK1.
The ATSAMA5D27 SOM1 contains five Flexible Serial Communications Controller (FLEXCOM) peripherals to provide serial communications protocols: USART, SPI, and TWI.
You will control pins PD0, PC30, PC29 and PA28 from the ATSAMA5D27 SOM1 which connects to J24 pins 3, 4, 5 and 6 of the mikroBUS 1 connector (labeled NPCS1, SPCK_mBUS1, MISO_mBUS1 and MOSI_mBUS1 on the schematic).
mikroBUS pin | Schematic Name | FLEXCOM I/O | Package Pin |
---|---|---|---|
J24 pin 3 | NPCS1 | FLEXCOM1_IO4 | PD0 |
J24 pin 4 | SPCK_mBUS1 | FLEXCOM1_IO2 | PC30 |
J24 pin 5 | MISO_mBUS1 | FLEXCOM1_IO1 | PC29 |
J24 pin 6 | MOSI_mBUS1 | FLEXCOM1_IO0 | PC28 |
For more details on the SAMA5D2 Package and Pinout, refer to Table 6-2. Pinouts in SAMA5D2 series data sheet.
Buildroot Configuration
Objective:
Using Buildroot, build a bootable image and FLASH onto an SD Memory Card for the ATSAMA5D27-SOM1-EK1 development board.
Follow the steps for building the image in the "Buildroot - Create Project with Default Configuration" page. You will use the default configuration file: atmel_sama5d27_som1_ek_mmc_dev_defconfig.
Device Tree
Objective:
Observe how the FLEXCOM4 peripheral was configured for SPI in the device tree. A small addition is shown for the at91-sama5d27_som1_ek.dts file below for the ability to communicate in user space.
Once Buildroot has completed its build, the SPI definitions for the ATSAMA5D27-SOM1-EK1 were configured by a device tree. The device tree source include (*.dtsi and *.dts) files are located in the Buildroot output directory: /output/build/linux-linux4sam_6.0/arch/arm/boot/dts/.
1
Examine the sama5d2.dtsi file and observe the FLEXCOM4 device assignments:
697 flx4_clk: flx4_clk {
698 #clock-cells = <0>;
699 reg = <23>;
700 atmel,clk-output-range = <0 83000000>;
701 };
.
.
1424 flx4: flexcom@fc018000 {
1425 compatible = "atmel,sama5d2-flexcom";
1426 reg = <0xfc018000 0x200>;
1427 clocks = <&pmc PMC_TYPE_PERIPHERAL 23>;
1428 #address-cells = <1>;
1429 #size-cells = <1>;
1430 ranges = <0x0 0xfc018000 0x800>;
1431 status = "disabled";
1432 };
Line 699 shows the PID for FLEXCOM4 is 23; this definition of the offset will be used to enable FLEXCOM4 clock in PMC.
Line 700 shows the FLEXCOM4 input clock; the max frequency is 83MHz.
Line 1425 specifies which driver will be used for this FLEXCOM device.
Line 1426 shows the FLEXCOM4 base address of 0xfc018000; the size is 0x200.
Line 1427 shows the definition for the FLEXCOM4 clock source.
Line 1431 the status is set to "disabled" by default. It will be set to "okay" in the at91-sama5d27_som1_ek.dts file below.
2
Examine the at91-sama5d27_som1_ek.dts file and observe the SPI device assignments:
258 flx4: flexcom@fc018000 {
259 atmel,flexcom-mode = <ATMEL_FLEXCOM_MODE_SPI>;
260 status = "okay";
261
262 uart6: serial@200 {
263 compatible = "atmel,at91sam9260-usart";
264 reg = <0x200 0x200>;
265 interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
266 clocks = <&pmc PMC_TYPE_PERIPHERAL 23>;
267 clock-names = "usart";
268 pinctrl-names = "default";
269 pinctrl-0 = <&pinctrl_flx4_default>;
270 atmel,fifo-size = <32>;
271 status = "disabled"; /* Conflict with spi3 and i2c3. */
272 };
273
274 spi3: spi@400 {
275 compatible = "atmel,at91rm9200-spi";
276 reg = <0x400 0x200>;
277 interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
278 clocks = <&flx4_clk>;
279 clock-names = "spi_clk";
280 pinctrl-names = "default";
281 pinctrl-0 = <&pinctrl_mikrobus_spi &pinctrl_mikrobus1_spi_cs &pinctrl_mikrobus2_spi_cs>;
282 atmel,fifo-size = <16>;
283 status = "okay"; /* Conflict with uart6 and i2c3. */
// the following code is added to enable spidev in userspace
283a spidev@1{
283b compatible = “spidev”;
283c reg = <1>;
283d spi-max-frequency = <100000>;
283e }
// - - - - - - - - - - - - - - - - - - - - - - -
284 };
285
286 i2c3: i2c@600 {
287 compatible = "atmel,sama5d2-i2c";
288 reg = <0x600 0x200>;
289 interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
290 dmas = <0>, <0>;
291 dma-names = "tx", "rx";
292 #address-cells = <1>;
293 #size-cells = <0>;
294 clocks = <&pmc PMC_TYPE_PERIPHERAL 23>;
295 pinctrl-names = "default";
296 pinctrl-0 = <&pinctrl_flx4_default>;
297 atmel,fifo-size = <16>;
298 status = "disabled"; /* Conflict with uart6 and spi3. */
299 };
300 };
.
.
473 pinctrl_mikrobus1_spi_cs: mikrobus1_spi_cs {
474 pinmux = <PIN_PD0__FLEXCOM4_IO4>;
475 bias-disable;
476 };
.
.
483 pinctrl_mikrobus_spi: mikrobus_spi {
484 pinmux = <PIN_PC28__FLEXCOM4_IO0>,
485 <PIN_PC29__FLEXCOM4_IO1>,
486 <PIN_PC30__FLEXCOM4_IO2>;
487 bias-disable;
488 };
Line 258 specifies SPI mode for this FLEXCOM.
Line 275 enables this device.
Line 275 specifies which driver will be used for this SPI device.
Line 276 sets the register offset address to 0x400, size 0x200.
Line 277 specifies the PID for FLEXCOM4 is 23, high level triggered, priority 7 (used to configure FLEXCOM4 interrupt in the AIC).
Line 278 is the definition for the FLEXCOM4 clock source.
Line 281 is the pin definition for the FLEXCOM4 SPI function.
Line 283 shows the SPI function status is "okay" while the UART and I²C functionality are "disabled".
Line 283a-283e are the changes you make (see the following information box).
See /output/build/linux-linux4sam_6.0/drivers/spi/spi.c of _spi_parse_dt() for more options.
Line 283b specifies which driver will be used for this device.
Line 283c is the definition that will be used as the CS number for SPIDEV.
Line 283d specifies the clock frequency for SPIDEV.
Line 474 assigns pin PD0 to FLEXCOM4_IO4.
Line 484 assigns pin PC28 to FLEXCOM4_IO0.
Line 485 assigns pin PC29 to FLEXCOM4_IO1.
Line 486 assigns pin PC30 to FLEXCOM4_IO2.
It is not recommended to use spidev as a device tree compatible name. It will work, but you will get the following warning:
# dmesg | grep spidev
spidev spi1.1: buggy DT: spidev listed directly in DT
WARNING: CPU: 0 PID: 1 at drivers/spi/spidev.c:730 0xc045d630
Because spidev is a Linux implementation construct, rather than a description of the hardware, it should never be referenced in a device tree without a specific name. To avoid this warning, use another compatible name instead of spidev, for example:
.
spidev@1 {
compatible = "atmel,at91rm9200-spidev";
reg = <1>;
spi-max-frequency = <1000000>;
};
.
Next, edit spidev driver file /output/build/linux-linux4sam_6.0/drivers/spi/spidev.c and add a new compatible name to the compatible table of spidev driver:
.
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
{ .compatible = "lineartechnology,ltc2488" },
{ .compatible = "ge,achc" },
{ .compatible = "semtech,sx1301" },
{ .compatible = "atmel,at91rm9200-spidev" },
{},
};
Kernel
Objective:
Observe how SPI functionality was configured in the Linux kernel.
In this exercise, you will configure the Linux kernel for spidev functionality.
Device Driver
2
Select Device Drivers - - ->
Rootfs
There are two ways to access SPI bus driver:
Kernel space:
Register your SPI driver via spi_register_driver() interface, then access SPI bus driver via the structure spi_device handle.
There is no definition for SPI bus number in device tree files and the bus number of SPI controller will be allocated automatically when registering. For example, the first registered SPI controller will be assigned with bus number 0, and the second assigned bus number should be 1, and so on. The following device node will be used to access SPI bus driver. The first number 1 refers to bus number, and the second number 1 refers to the CS number: /dev/spidev1.1
User space:
You can access the SPI device from user space by enabling the SPIDEV kernel feature as shown above and then access SPI bus driver via /dev/spidev device node as shown in the "Application" section below. SPIDEV is a good choice because all application codes are running in user space (it’s easier for developing). However, you will have to configure the SPIDEV feature first in the Linux kernel.
Application
The following is a C-Language demonstration program for accessing the SPI bus driver via the SPIDEV node:
To compile:
$ buildroot/output/host/bin/arm-buildroot-linux-uclibcgnueabihf-gcc spi_dev.c -o spi_test
Be sure to type in the location of the cross-compiler on your host computer.
Source code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define DEV_SPI "/dev/spidev1.1"
int main(int argc, char *argv[])
{
int fd;
int ret;
unsigned int mode, speed;
char tx_buf[1];
char rx_buf[1];
struct spi_ioc_transfer xfer[2] = {0};
// open device node
fd = open(DEV_SPI, O_RDWR);
if (fd < 0) {
printf("ERROR open %s ret=%d\n", DEV_SPI, fd);
return -1;
}
// set spi mode
mode = SPI_MODE_0;
if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) {
printf("ERROR ioctl() set mode\n");
return -1;
}
if (ioctl(fd, SPI_IOC_RD_MODE32, &ret) < 0) {
printf("ERROR ioctl() get mode\n");
return -1;
} else
printf("mode set to %d\n", (unsigned int)ret);
// set spi speed
speed = 1*1000*1000;
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
printf("ERROR ioctl() set speed\n");
return -1;
}
if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &ret) < 0) {
printf("ERROR ioctl() get speed\n");
return -1;
} else
printf("speed set to %d\n", ret);
// transfer data
tx_buf[0] = 0xa5;
xfer[0].tx_buf = (unsigned long)tx_buf;
xfer[0].len = 1;
xfer[1].rx_buf = (unsigned long)rx_buf;
xfer[1].len = 1;
do {
if (ioctl(fd, SPI_IOC_MESSAGE(2), xfer) < 0)
perror("SPI_IOC_MESSAGE");
usleep(100*1000);
} while (1);
// close device node
close(fd);
return 0;
}
spidev_test Application
There is a spidev_test application that you can configure in Buildroot.
1
Select Target packages - - ->
2
Select Debugging, profiling and benchmark - - ->
Hands On
Copy the spi_test program to the target and execute it.
# chmod +x spi_test
# ./spi_test
The SPI waveform can be monitored on a logic analyzer:
Legend:
- Yellow – NPCS1
- Green – SPCK_mBUS1
- Blue – MOSI_mBUS1
- Red – MISO_mBUS1
Tools and Utilities
spi-tools (https://i2c.wiki.kernel.org/index.php/SPI_Tools) is a tool for SPI bus testing. It is included in the default configuration of Buildroot. You can view the selection by performing the following:
1
From the Buildroot directory, start menuconfig:
$ make menuconfig
2
Select Target packages - - ->
3
Select Hardware handling - - ->
spi-tools Commands
There are two commands in spi-tools:
spi-config:
# spi-config -h
usage: spi-config options…
options:
-d —device=<dev> use the given spi-dev character device.
-q —query print the current configuration.
-m —mode=[0-3] use the selected spi mode:
0: low iddle level, sample on leading edge,
1: low iddle level, sample on trailing edge,
2: high iddle level, sample on leading edge,
3: high iddle level, sample on trailing edge.
-l —lsb={0,1} LSB first (1) or MSB first (0).
-b —bits=[7…] bits per word.
-s —speed=<int> set the speed in Hz.
-h —help this screen.
-v —version display the version number.
# spi-config -d /dev/spidev1.1 -q
/dev/spidev1.1: mode=0, lsb=0, bits=8, speed=1000000
spi-pipe:
# spi-pipe -h
usage: spi-pipe options…
options:
-d —device=<dev> use the given spi-dev character device.
-b —blocksize=<int> transfer block size in byte.
-n —number=<int> number of blocks to transfer (-1 = infinite).
-h —help this screen.
-v —version display the version number.
# spi-pipe -d /dev/spidev1.1 -b 6 -n 1
111111
Input "111111" (six ones) and press the Enter key. The waves could be captured from an oscilloscope accordingly.
The SPI waveform can be monitored on a logic analyzer:
Legend:
- Yellow – NPCS1
- Green – SPCK_mBUS1
- Blue – MOSI_mBUS1
- Red – MISO_mBUS1
Summary
In this article, you used Buildroot to build an image with SPI Bus support for the ATSAMA5D2 Series MPU. You accessed the SPI Bus via two different methods: kernel space using SPI driver via spi_register_driver() interface, then access SPI bus driver via structure spi_device handle, and user space by enabling the SPIDEV kernel feature and then accessing the SPI bus driver via /dev/spidev device node. You walked through the device tree and kernel to observe how the embedded Linux system configures the source code for building.