world leader in high performance signal processing
Trace: » spi

Serial Peripheral Interface (SPI) Bus

The Serial Peripheral Interface (SPI) Bus is a four wire master/slave full duplex synchronous bus. You can hook up multiple slave devices by utilizing chip select lines.

The bus is composed of two data pins, one clock pin, and one chip select pin:

  • SCLK - Serial Peripheral Interface Clock Signal (generated by the master) (also referred to as SCK)
  • MOSI - Master Out Slave In data (output from the master)
  • MISO - Master In Slave Out (output from the slave)
  • CS - Chip Select (also referred to as Slave Select (SS))

It is not uncommon to use the bus with just one master and one slave, but it is certainly possible to use it as a real bus with many devices hanging off of it.

The Blackfin processor typically has about 7 SPI chip selects. Please refer to the HRM for your processor variant to see exactly what resources are available.

SPI Modes

Each slave may operate at different clock frequencies as well as different clock polarities and clock phases with respect to the data. The permutations of polarities and phases are referred to as SPI modes. Below you can see the relationship between modes and the polarity/phase of the clock.

For the Blackfin specific timing information, please see the diagrams in the next section.

Mode Polarity (CPOL) Phase (CPHA) Comment
SPI_MODE_000The Slave Select Line goes inactive (high) between each serial transfer
SPI_MODE_101
SPI_MODE_210The Slave Select Line goes inactive (high) between each serial transfer
SPI_MODE_311

Blackfin Interface

The figure below provides a block diagram of the SPI. The interface is essentially a shift register that serially transmits and receives data bits, one bit at a time at the SCLK rate, to and from other SPI devices. SPI data is transmitted and received at the same time through the use of a shift register. When an SPI transfer occurs, data is simultaneously transmitted (shifted serially out of the shift register) as new data is received (shifted serially into the other end of the same shift register). The SCLK synchronizes the shifting and sampling of the data on the two serial data pins.

Linux Framework

There are a few aspects to the Linux SPI framework. First, you need to declare the resources that a device will be utilizing in your system (the clock settings, the chip select the device uses, etc…). This is done on a per-board basis so that individual boards can easily customize things without munging up drivers. Then you need to write the driver which will take care of actually managing the device.

Device Resources

You want to place this information in your board file. Here we will refer to the BF537 Stamp board linux-2.6.x/arch/blackfin/mach-bf537/boards/stamp.c.

The board setup code should take care of automatically calling the spi_register_board_info function so that your resources are registered at boot. This is the typical method of registering resources.

Note that while it is possible to encode all of the resources in a module and do dynamic resource registration on the fly during runtime, we will not cover that functionality here.

Blackfin Controller Resources

First we define the Blackfin SPI controller resources:

#include <asm/bfin5xx_spi.h>
...
static struct bfin5xx_spi_chip ad5304_chip_info = {
    .ctl_reg = 0x4,            /* Blackfin-specific fields in SPI_CTL MMR; default = 0 */
    .enable_dma = 0,           /* Use DMA to transfer to the device; default = 0 */
    .bits_per_word = 16,       /* How many bits per word to transfer at a time (8 or 16); default = 8 */
    .cs_chg_udelay = 0,        /* How long to delay when changing the chip select between words; default = 0 */
};

Only use the ctl_reg member to enable features that the generic SPI framework does not expose as options. For example, if you want to set CPOL or CPHA, use the mode member and the SPI_MODE_# settings in the common framework, but if you need to set Send Zero option, you should set the ctl_reg field.

SPI Resources

Then we define the SPI framework resources:

#include <linux/spi/spi.h>
...
static struct spi_board_info bfin_spi_board_info[] __initdata = {
...
    {
        .modalias = "ad5304_spi",
        .max_speed_hz = 12500000,
        .bus_num = 1,
        .chip_select = 2,
        .platform_data = NULL,
        .controller_data = &ad5304_chip_info,
        .mode = SPI_MODE_1,
    },
...
};

The max_speed_hz field represents the max SPI clock speed in Hz.

GPIO-based chip select (old way)

To make use of this feature, set chip_select = 0 and add a proper cs_gpio to your controller_data. Note that the device will be named as e.g. spidev0.0 .
Add or modify the two structs according to the hints below.

static struct bfin5xx_spi_chip
...
        .cs_gpio = GPIO_P###,
static struct spi_board_info
...
        .chip_select = 0,
GPIO-based chip select (new way)

To make use of this feature, set chip_select = MAX_CTRL_CS + a proper GPIO number.

static struct spi_board_info
...
        .chip_select = GPIO_PFXX + MAX_CTRL_CS,	/* GPIO controlled SSEL */

The GPIO controlled SPI Slave Select option allows you also to extend the number of simultaneous supported SPI slaves since the GPIO Slave Select can be any GPIO capable pin.

Chip Select behavior

With the Blackfin on-chip SPI peripheral, there is some logic tied to the CPHA bit whether the Slave Select Line is controlled by hardware (CPHA=0) or controlled by software (CPHA=1). However, the Linux SPI bus driver assumes that the Slave Select being asserted during the entire SPI transfer.

In most cases you can utilize SPI mode 3 instead of mode 0 to workaround this behavior. If your SPI slave device in question requires SPI mode 0 or 2 timing, you can utilize the GPIO controlled SPI Slave Select option instead. You can even use the same pin whose peripheral mode is a SSEL, but use it as a GPIO instead.

Configure Kernel

Device Drivers  --->
    [*] SPI support  --->
            <*>   SPI controller driver for ADI Blackfin5xx /* choose this for bf5xx soc */
            <*>   SPI controller driver for ADI Blackfin6xx /* choose this for bf60x soc */
            <*>   User mode SPI device driver support       /* user space spi driver */

Kernel Driver

The steps a SPI chip driver generally takes include:

  • register SPI driver
  • probe SPI device
  • utilize SPI device
Register SPI driver

Here you just want to announce to the kernel you are making your driver available. In your init/exit functions, you only want to handle things that need to be done on a per-driver level, not on a per-chip level. Remember that since we are using SPI here, you can have more than one chip device on the SPI bus using the same driver!

#include <linux/spi/spi.h>
...
static struct spi_driver ad5304_spi_driver = {
    .driver = {
        .name   = "ad5304_spi",
        .bus    = &spi_bus_type,
        .owner  = THIS_MODULE,
    },
    .probe  = ad5304_spi_probe,
    .remove = __devexit_p(ad5304_spi_remove),
};
...
static int __init ad5304_spi_init(void)
{
    return spi_register_driver(&ad5304_spi_driver);
}
static void __exit ad5304_spi_exit(void)
{
    spi_unregister_driver(&ad5304_spi_driver);
}
module_init(ad5304_spi_init);
module_exit(ad5304_spi_exit);
Probe SPI device

In your probe/remove functions you want to take care of any resource allocation/setup that is needed for each chip. Often times, this may involve setting up a per-device spinlock.

static int __devinit ad5304_spi_probe(struct spi_device *spi)
{
    struct ad5304_data *data;
    data = kzalloc(sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    spin_lock_init(&data->lock);
    dev_set_drvdata(&spi->dev, data);
    return 0;
}
static int __devexit ad5304_spi_remove(struct spi_device *spi)
{
    struct ad5304_data *data = dev_get_drvdata(&spi->dev);
    kfree(data);
    return 0;
}
Utilize SPI device

Now for the open ended part. To access your device, you'll use a suite of functions. Here we'll cover some of the common ones.

int some_func(..., struct spi_device *spi, ...)
{
...
    /* Write out 5 bytes from a buffer and sleep until done */
    u8 buf[10] = { 0, 1, 2, 3, 4 };
    int ret = spi_write(spi, buf, 5);
...
    /* Read in 4 bytes to a buffer and sleep until done */
    u8 buf[10];
    int ret = spi_read(spi, buf, 4);
...
}

Userspace Driver

For many simple SPI devices, all management can be done directly from userspace using the spidev device driver introduced in Linux-2.6.22. The normal kernel vs userspace design decisions hold true here.

Just like the custom SPI kernel driver, you need to declare the SPI device resources in your board file. See the Device Resources section above but for the .modalias field, use spidev. Otherwise, you do not implement anything else in the kernel as the spidev device driver handles all of it.

Once you boot up, the device nodes in /dev/ should be automatically created. They will have the form /dev/spidev<bus>.<chipselect>. So if you have a device using chip select 2 on the first SPI bus, the device will be named /dev/spidev0.2.

If you want to access the hardware as a simple half-duplex character device, then you can just use the standard open/close/read/write functions.

If you need to do full duplex transactions, you will have to utilize the extended API via ioctl() calls. The spidev file in the kernel documentation covers this.

A simple test application can be found here:

file: Documentation/spi/spidev_test.c

scm failed with exit code 1:
file does not exist in git

External Resources

For the latest and greatest SPI framework information, please read the following files in the kernel source:

  • linux-2.6.x/include/linux/spi/spi.h
  • linux-2.6.x/Documentation/spi/spi-summary
  • linux-2.6.x/Documentation/spi/spidev

The images/information used here were acquired from various sources including Blackfin HRMs and Wikipedia articles.

Complete Table of Contents/Topics