SPI (Serial Peripheral Interface) is one of the most widely used high-speed synchronous buses in embedded Linux systems. It connects SoCs to peripherals such as NOR Flash, EEPROMs, ADCs, displays, and IMU sensors, offering low latency and simple hardware requirements.
This article explains both SPI as a bus protocol and how Linux models SPI devices and drivers, ending with a practical sensor-driver example.
🔌 SPI Bus Fundamentals #
Bus Signals and Roles #
SPI follows a master–slave architecture and typically uses four signals:
- MOSI (Master Out, Slave In) — Data from master to slave
- MISO (Master In, Slave Out) — Data from slave to master
- SCLK (Serial Clock) — Clock generated by the master
- CS / nCS (Chip Select) — Active-low slave selection line
Each slave has a dedicated chip-select, allowing multiple devices to share the same data and clock lines.
Timing and Bit Exchange #
SPI behaves like a bidirectional shift register:
- One bit is transmitted and received per clock cycle
- Data is usually sent MSB first
- Transmission is full-duplex by design
While one side shifts data out, it simultaneously shifts data in.
SPI Modes (CPOL / CPHA) #
SPI defines four timing modes using clock polarity and phase:
- Mode 0: CPOL = 0, CPHA = 0 (idle low, sample on rising edge)
- Mode 1: CPOL = 0, CPHA = 1
- Mode 2: CPOL = 1, CPHA = 0
- Mode 3: CPOL = 1, CPHA = 1 (idle high, sample on rising edge)
Correct mode selection is critical—mismatches cause subtle data corruption.
🧩 Linux SPI Framework Architecture #
Linux cleanly separates SPI responsibilities into three layers:
-
SPI Core
Common infrastructure that manages devices, masters, and message scheduling. -
SPI Master (Controller) Driver
Hardware-specific driver for the SoC SPI controller (e.g., i.MX ECSPI, SPI NOR controller). -
SPI Protocol / Device Driver
Peripheral-specific driver (sensor, display, flash, etc.).
This separation allows the same device driver to work across multiple platforms.
🧱 Core SPI Data Structures #
spi_device
#
Represents a physical SPI slave device. It contains:
- Chip select number
- SPI mode (CPOL / CPHA)
- Maximum clock frequency
- Pointer to the associated
spi_master
Each SPI device typically corresponds to one node in the device tree.
spi_master / spi_controller
#
Represents the SPI controller hardware inside the SoC. It:
- Queues SPI messages
- Controls clock generation and chip-select timing
- Handles PIO or DMA transfers
spi_transfer and spi_message
#
-
spi_transfer
The smallest data unit: one TX buffer and/or RX buffer. -
spi_message
An ordered list of transfers that execute atomically.
Once started, no other SPI message may interrupt it.
This design ensures protocol correctness for multi-phase commands.
🔄 SPI Data Transfer Workflow #
A typical SPI transaction follows this pattern:
- Initialize a
spi_message - Configure one or more
spi_transferstructures - Append transfers to the message
- Submit using:
spi_sync()for blocking callsspi_async()for non-blocking calls
The SPI core schedules execution, while the master driver handles hardware interaction.
🧪 Example: ICM20608 SPI Sensor Driver #
The following example demonstrates reading multiple registers from an ICM20608 IMU using Linux SPI APIs.
static int icm20608_read_regs(struct icm20608_dev *dev,
u8 reg, void *buf, int len)
{
int ret;
u8 txdata[1];
struct spi_message msg;
struct spi_transfer xfer[2];
struct spi_device *spi = dev->private_data;
memset(xfer, 0, sizeof(xfer));
/* Transfer 1: send register address */
txdata[0] = reg | 0x80; /* MSB=1 indicates read */
xfer[0].tx_buf = txdata;
xfer[0].len = 1;
/* Transfer 2: read data */
xfer[1].rx_buf = buf;
xfer[1].len = len;
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
spi_message_add_tail(&xfer[1], &msg);
ret = spi_sync(spi, &msg);
return ret;
}
Probe Function #
static int icm20608_probe(struct spi_device *spi)
{
spi->mode = SPI_MODE_0;
spi_setup(spi);
/* Hardware initialization */
icm20608_reginit();
return 0;
}
Key points illustrated here:
- Multi-phase SPI operations are modeled as a single
spi_message - Chip-select handling is usually automatic
- The driver remains portable across SPI controllers
🧠Summary #
The Linux SPI subsystem provides a clean and scalable abstraction over diverse hardware controllers. By structuring communication as atomic spi_message objects composed of spi_transfer segments, Linux ensures correctness, flexibility, and performance.
For embedded Linux developers, mastering SPI means understanding message composition, timing modes, and driver layering—once these are clear, adding new SPI peripherals becomes straightforward and robust.