Serial peripheral interface (SPI)

Note This section contains Linux BSP documentation for kernel v4.1. Click here for v4.9 BSP documentation.

The NXP i.MX6UL CPU has four SPI buses.

On the ConnectCore 6UL system-on-module:

On the ConnectCore 6UL SBC Express:

On the ConnectCore 6UL SBC Pro:

Kernel configuration

You can manage the SPI driver support through the kernel configuration option:

SPI slave support can be managed through the kernel configuration option:

Certain example SPI slave drivers can be managed through the following kernel configuration options:

All these options are enabled as built-in on the default ConnectCore 6UL kernel configuration file.

Platform driver mapping

The SPI bus driver for the ConnectCore 6UL system-on-module is located at drivers/spi/spi-imx.c.

Device tree bindings and customization

The i.MX6UL SPI interface device tree binding is documented at Documentation/devicetree/bindings/spi/fsl-imx-cspi.txt.

The common i.MX6UL CPU device tree defines all the SPI ports. The platform device tree must:

Note (1) By default, SPI bus is disabled on the device tree since there are no SPI slave devices available on the board.

Note (2) As of kernel v4.1, the i.MX SPI driver (as master) does not work with native chip selects, only with GPIOs.

Example: SPI1 port (as master) on the ConnectCore 6UL SBC Pro

ConnectCore 6UL SBC Pro device tree
/* ECSPI1 (as master) */
&ecspi1 {
    fsl,spi-num-chipselects = <1>;
    cs-gpios = <&gpio3 26 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi1_master>;
    status = "okay";
 
    /*
     * Add your slave devices here. Next is an example of spidev.
     * Expect a harmless kernel warning if you enable spidev as slave.
     */
//     spidev@0 {
//         reg = <0>;
//         compatible = "spidev";
//         spi-max-frequency = <1000000>;
//     };
};
&iomuxc {
    imx6ul-ccimx6ul {
        pinctrl_ecspi1_master: ecspi1grp1 {
            fsl,pins = <
                MX6UL_PAD_LCD_DATA20__ECSPI1_SCLK    0x10b0
                MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI    0x10b0
                MX6UL_PAD_LCD_DATA23__ECSPI1_MISO    0x10b0
                MX6UL_PAD_LCD_DATA21__GPIO3_IO26     0x10b0 /* Chip Select */
            >;
        };
    };
};

Example: SPI1 port (as slave) on the ConnectCore 6UL SBC Pro

ConnectCore 6UL SBC Pro device tree
/* ECSPI1 (as slave) */
&ecspi1 {
     pinctrl-0 = <&pinctrl_ecspi1_slave>;
     spi-slave;
     status = "okay";
 
     /*
      * Unique slave node. Property 'compatible' must be set to the
      * slave driver that will be registered by default.
      */
     slave@0 {
         reg = <0>;    /* must match the used CS line */
         compatible = "spidev";
         spi-max-frequency = <1000000>;
     };
};
 
&iomuxc {
    imx6ul-ccimx6ul {
        pinctrl_ecspi1_slave: ecspi1grp2 {
            fsl,pins = <
                MX6UL_PAD_LCD_DATA20__ECSPI1_SCLK    0x10b0
                MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI    0x10b0
                MX6UL_PAD_LCD_DATA23__ECSPI1_MISO    0x10b0
                MX6UL_PAD_LCD_DATA21__ECSPI1_SS0     0x10b0 /* Chip Select */
            >;
        };
    };
};

Limitations

Due to i.MX6UL errata ERR009535, the burst completion by SS signal in slave mode is not functional. In practice this means that the slave must know in advance how large the transfer will be, in order to program the burst length, and that the maximum burst length is thus limited to the size of the FIFO (64 words of 32-bits, or 256 bytes). For further information on this errata read NXP Chip Errata for the i.MX 6UltraLite.

SPI user space usage

The SPI bus cannot be accessed directly from user space. Instead, it is accessed via the SPI client drivers.

SPI device interface

The Linux kernel offers a sample client driver called spidev that gives you read and write data access to the SPI bus through the /dev interface:

When the SPI bus is used as master, you can enable spidev as a slave of the SPI port on the device tree, recompile it, and deploy it to your device:

/* ECSPI1 (as master) */
&ecspi1 {
    /*
     * There are no SPI slave devices on the SBC.
     * Enable if adding your slave devices below.
     */
    status = "okay";
 
    /*
     * Add your slave devices here. Next is an example of spidev.
     * Expect a harmless kernel warning if you enable spidev as slave.
     */
    spidev@0 {
        reg = <0>;
        compatible = "spidev";
        spi-max-frequency = <1000000>;
    };
};

When the SPI bus is used as slave, you can enable spidev as the slave driver on the device tree, recompile it, and deploy it to your device:

/* ECSPI1 (as slave) */
&ecspi1 {
    pinctrl-0 = <&pinctrl_ecspi1_slave>;
    spi-slave;
    status = "okay";
 
    /*
     * Unique slave node. Property 'compatible' must be set to the
     * slave driver that will be registered by default.
     */
    slave@0 {
        reg = <0>;    /* must match the used CS line */
        compatible = "spidev";
        spi-max-frequency = <1000000>;
    };
};

Note Spidev is not a real hardware SPI slave device but a detail of how Linux controls a device. Expect a harmless kernel warning for having spidev in the device tree. For reference, see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=956b200a846e324322f6211034c734c65a38e550

Linux will create a device node in the form /dev/spidevX.Y device node where X corresponds to the SPI port index, starting at zero (as per the ports enabled in the device tree), and Y corresponds to the SPI bus chip select, starting at zero.

SPI device test application

Build the package dey-examples-spi in your Yocto project to install the test application spidev_test.

This spidev_test application is a simple utility used to test SPI functionality via the spidev device.

Syntax

To display the application's syntax run:

spidev_test --help

Example 1: SPI as master (loopback  test)

Short-circuit the MISO and MOSI lines of your SPI bus to create a loopback that allows the bus to receive the same data it is sending.

Run the application using your spidev node /dev/spidevX.Y, where X is the SPI bus index and Y is the SPI bus chip select:

~# spidev_test -D /dev/spidev0.0 -v
spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 KHz)
TX | FF FF FF FF FF FF 40 00 00 00 00 95 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F0 0D | ......@....�..................�.
RX | FF FF FF FF FF FF 40 00 00 00 00 95 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F0 0D | ......@....

You should receive the same data displayed in the preceding excerpt.

Example 2: SPI as slave

For this test you will need one of these to work as SPI master:

Connect both SPI buses directly (MISO to MISO, MOSI to MOSI).

Due to the errata mentioned in Limitations, you must first run the spidev_test application on the slave so that it pre-fills the FIFO with the message. On the slave, run:

# spidev_test -D /dev/spidev0.0 -p "SLAVE_HELLO_TO_MASTER" -v

The slave is now blocked waiting to receive clocks from the master, to shift out the message.

On the master (another device, another SPI port, or an SPI analyzer) send a similar message "MASTER_HELLO_TO_SLAVE". Note that it must send the exact number of bytes the slave expects. If the master is another Linux device or port, you can use the spidev_test application as well:

# spidev_test -D /dev/spidev0.0 -p "MASTER_HELLO_TO_SLAVE" -v

If it is an analyzer, you may send the message "MASTER_HELLO_TO_SLAVE" in hexadecimal:

4D 41 53 54 45 52 5F 48 45 4C 4C 4F 5F 54 4F 5F 53 4C 41 56 45

The slave message appears on the master at the same time the master's message appears on the slave.

SPI slave class and sample slave drivers

The SPI slave framework on the Linux kernel has a class that allows you to register slave drivers to reply when the bus is working as SPI slave. By means of a sysfs entry, you can change at run-time the SPI slave driver that is registered.

See the following SPI slave driver examples as reference for your own implementation:

SPI slave handler reporting boot-up time

This driver reports the system uptime at the reception of the previous SPI message. It does so by sending two 32-bit unsigned integers in binary format and in network byte order representing the number of seconds and microseconds since boot up. You can find the driver at drivers/spi/spi-slave-time.c.

To register this slave driver:

# echo spi-slave-time > /sys/class/spi_slave/spi0/slave

Once registered, the FIFO is pre-filled with the system uptime. When the slave receives a message with 8 bytes, it will reply with the uptime in the FIFO, and will fill the FIFO again with the new uptime to send on the next SPI message.

Notice this driver disregards what the master sends, it just needs the master to send any 8 bytes. Send any 8 bytes with an SPI analyzer or with another device (master) using the spidev_test application:

# spidev_test -D /dev/spidev0.0 -p "XXXXXXXX"

The slave will reply with data like this:

00 00 0D 6F 00 0F 0F 13

where:

SPI slave handler controlling system state

This driver is an example of how to take actions on the arrival of certain messages from the master. It allows remote control of the system with reboot, power off, suspend, and halt actions. You can find the driver at drivers/spi/spi-slave-system-control.c.

To register this slave driver:

# echo spi-slave-system-control > /sys/class/spi_slave/spi0/slave

Once registered, the slave awaits the arrival of a 2-byte message from the master with the following codes, to take a system action:

SPI message code

Action

0x7c50

Reboot

0x713f

Power off

0x3876

Halt

0x1b1b

Suspend

Notice this driver does not send any relevant information on its reply, just dummy data.

Send one of the above 2-byte codes with an SPI analyzer or with another device (master) using the spidev_test application:

# reboot='\x7c\x50'
# poweroff='\x71\x3f'
# halt='\x38\x76'
# suspend='\x1b\x1b'
# spidev_test -D /dev/spidev0.0 -p $suspend     # or $reboot, $poweroff, $halt