Chapter 47: SPI drivers¶
What: the Linux SPI subsystem,
spi_master(the controller, again written by the SoC vendor),spi_device(one chip on a chip-select),spi_driver(the per-chip code),spi_message(a sequence of transfers). Same shape as I²C but with full-duplex transactions and per-CS independent configuration (mode, speed, word size).Why: SPI carries the high-bandwidth peripherals, NOR flash, LCDs, fast ADCs, IMUs at >1 kHz, Ethernet PHYs over SPI (W5500, ENC28J60), CAN controllers over SPI (MCP2515). Speeds from 1 MHz to 50 MHz are common, vs I²C’s 100–400 kHz. The Linux model lets each chip-select have its own mode and clock without the driver caring about the controller’s specifics.
Focus:
spi_messageis a list ofspi_transfers, executed back-to-back without releasing CS unless you ask. Mastering this lets you build any chip’s command sequence, “write 1 cmd byte, then read 4 data bytes, holding CS through both” is just a two-transfer message.
47.1 How SPI differs from I²C¶
Property |
I²C |
SPI |
|---|---|---|
Wires |
2 (SDA, SCL) |
4 (MOSI, MISO, SCK, CS) + 1 per chip-select |
Topology |
Multi-drop on one pair |
Star: separate CS per chip |
Addressing |
7-bit on the wire |
Implicit (CS asserted) |
Direction |
Half-duplex (one direction at a time) |
Full-duplex (both at once) |
Speed |
100 kHz, 1 MHz typical |
1, 50 MHz typical |
Bus arbitration |
Built-in (NAK, arbitration loss) |
None (only one master per CS) |
Protocol overhead |
Address + ACK per byte |
None, just clock bits |
For an SPI bus, you pick one chip-select per device. The controller asserts CS, clocks bytes, deasserts CS. Each CS has its own configuration: clock speed, polarity (CPOL), phase (CPHA), word size (usually 8 bits, sometimes 16/32 for DMA-friendly).
MCU bridge: Think of DMA like the MCU DMA controller you used for UART or SPI, but with cache coherency, scatter-gather descriptors, and kernel ownership rules added. DMA: Direct Memory Access. Hardware moves data to or from memory without the CPU copying each byte.
The i.MX6ULL has 4 eCSPI controllers (eCSPI1–4), each with up to 4 chip-select lines (so up to 16 SPI devices on the SoC in theory).
47.2 Device tree for SPI¶
&ecspi3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
cs-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* optional GPIO-CS */
status = "okay";
flash@0 {
compatible = "winbond,w25q128", "jedec,spi-nor";
reg = <0>; /* chip-select 0 */
spi-max-frequency = <50000000>; /* 50 MHz */
spi-tx-bus-width = <1>;
spi-rx-bus-width = <1>;
};
adc@1 {
compatible = "linuxlearn,fastadc";
reg = <1>; /* chip-select 1 */
spi-max-frequency = <10000000>; /* 10 MHz */
spi-cpol; /* CPOL=1, CPHA=0 (mode 2) */
};
};
Differences from I²C:
regis the chip-select index, not an address.spi-max-frequencyis per-device. The controller uses the lower of (its max, this).spi-cpol/spi-cphaset the SPI mode (omit both = mode 0. Both = mode 3).cs-gpios: optionally specify GPIO-based CS lines instead of the controller’s native CS pins. Common when the controller’s native CS doesn’t quite behave the way you want (e.g., toggling between messages).
MCU bridge: Think of Linux GPIO like the same pin set/reset block you used on STM32, but accessed through a kernel subsystem that owns numbering, direction, interrupts, and user-space exposure. GPIO: General-Purpose Input/Output, a pin controlled as a digital input, output, or interrupt source.
47.3 An SPI driver skeleton¶
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
struct fastadc_priv {
struct spi_device *spi;
struct mutex lock;
};
static int fastadc_read_word(struct fastadc_priv *p, u16 *out)
{
u8 tx[2] = { 0x80, 0x00 }; /* read command */
u8 rx[2];
struct spi_transfer xfer = {
.tx_buf = tx,
.rx_buf = rx,
.len = 2,
.speed_hz = 10000000, /* override device default if desired */
};
int err = spi_sync_transfer(p->spi, &xfer, 1);
if (err < 0)
return err;
*out = ((u16)rx[0] << 8) | rx[1];
return 0;
}
static int fastadc_probe(struct spi_device *spi)
{
struct fastadc_priv *p;
u16 sample;
int err;
p = devm_kzalloc(&spi->dev, sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
p->spi = spi;
mutex_init(&p->lock);
/* Configure SPI bus parameters */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
err = spi_setup(spi);
if (err)
return dev_err_probe(&spi->dev, err, "spi_setup failed\n");
err = fastadc_read_word(p, &sample);
if (err)
return dev_err_probe(&spi->dev, err, "test read failed\n");
dev_info(&spi->dev, "fastadc ready (test sample = %u)\n", sample);
spi_set_drvdata(spi, p);
return 0;
}
static void fastadc_remove(struct spi_device *spi)
{
/* devm_* handles cleanup */
}
static const struct of_device_id fastadc_of_match[] = {
{ .compatible = "linuxlearn,fastadc" },
{ }
};
MODULE_DEVICE_TABLE(of, fastadc_of_match);
static const struct spi_device_id fastadc_id[] = {
{ "fastadc", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, fastadc_id);
static struct spi_driver fastadc_driver = {
.driver = {
.name = "linuxlearn-fastadc",
.of_match_table = fastadc_of_match,
},
.probe = fastadc_probe,
.remove = fastadc_remove,
.id_table = fastadc_id,
};
module_spi_driver(fastadc_driver);
MODULE_LICENSE("GPL");
Mirror image of the I²C driver from Ch 46, same idioms: module_spi_driver, two match tables, devm_kzalloc, dev_err_probe.
47.4 Transfers and messages¶
The data model is two levels:
spi_transfer: one back-and-forth burst. Hastx_buf(or NULL for read-only),rx_buf(or NULL for write-only),len, optional per-transfer overrides (speed_hz,bits_per_word,cs_change,delay).spi_message: a list ofspi_transfers executed atomically. CS asserts before the first transfer, deasserts after the last (unless overridden).
For simple sequences, spi_sync_transfer(spi, xfers, n) is a one-call wrapper that builds the spi_message, submits it, waits, and returns.
For a complex case, say, write a register address then read 16 bytes, with no STOP between (CS held continuously):
u8 tx_cmd[2] = { 0x80, reg_addr };
u8 rx_data[16];
struct spi_transfer xfers[2] = {
{
.tx_buf = tx_cmd,
.len = 2,
},
{
.rx_buf = rx_data,
.len = 16,
},
};
err = spi_sync_transfer(spi, xfers, 2);
CS is asserted for the entire 18-byte sequence. The first transfer sends 2 bytes (clocking nothing in particular into rx), the second reads 16 bytes (clocking dummy 0s out of tx).
Need to deassert CS between transfers? Set xfers[0].cs_change = 1. Need a delay? xfers[0].delay.value = 10. xfers[0].delay.unit = SPI_DELAY_UNIT_USECS;.
Async transfers¶
For high-rate sampling, spi_async() submits a message without waiting. A callback fires on completion. Drivers that sample continuously (audio codecs, fast IMUs) use this. Initialise:
static void my_complete(void *context)
{
struct my_priv *p = context;
/* runs in workqueue context after transfer finishes */
}
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
msg.complete = my_complete;
msg.context = priv;
spi_async(spi, &msg);
47.5 Half-duplex helpers¶
For chips that don’t really use full-duplex, the helpers spi_write, spi_read, and spi_write_then_read are simpler than building transfers by hand:
err = spi_write(spi, txbuf, len); /* TX only, RX discarded */
err = spi_read(spi, rxbuf, len); /* RX only, TX = 0 */
err = spi_write_then_read(spi, txbuf, tlen, rxbuf, rlen); /* TX then RX, CS held */
spi_write_then_read is what you reach for 90% of the time. It builds two transfers internally with CS held throughout.
47.6 /dev/spidevN, user-space access¶
Template warning: This block contains placeholder values. Replace compatible strings, GPIO numbers, addresses, and paths with values from your board before using it.
Like /dev/i2c-N, there’s a user-space chardev for SPI: /dev/spidev<bus>.<cs>. To enable, add the binding to your DT:
&ecspi3 {
spidev@0 {
compatible = "rohm,dh2228fv"; /* historical placeholder for spidev */
reg = <0>;
spi-max-frequency = <10000000>;
};
};
Why "rohm,dh2228fv"? The kernel maintainers will not accept "spidev" as a generic compatible, spidev is not a chip, just a user-space access mechanism. The dh2228fv is one of several chip names “registered” to spidev as a workaround for development. For production, use the actual chip’s compatible string with a real driver.
Kernel warning since v4.15. If you use this placeholder in a DT,
spidev_probeprintsWARNING: Probing spidev with broken DT entryand refuses to bind unlessCONFIG_SPI_SPIDEVoverrides are set. Modern best practice: either pick a real-chip compatible that already appears indrivers/spi/spidev.c’sspidev_dt_ids[](e.g.,"semtech,sx1301"for a known SPI radio), or write a proper DT overlay that adds acompatibleyour kernel build accepts. The cookbook chapters (Ch 98, 99, 101, 105, 106) userohm,dh2228fvas shorthand. In production swap for the chip’s real string + a tiny accepting driver, or build with thespidev_compatible_arraypatch.
The spidev userspace API uses ioctl:
int fd = open("/dev/spidev2.0", O_RDWR);
u8 mode = SPI_MODE_0;
ioctl(fd, SPI_IOC_WR_MODE, &mode);
u32 speed = 10000000;
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
struct spi_ioc_transfer xfer = {
.tx_buf = (uintptr_t)tx, .rx_buf = (uintptr_t)rx, .len = 4,
};
ioctl(fd, SPI_IOC_MESSAGE(1), &xfer);
Useful for bring-up. Production code should be a real spi_driver.
47.7 A worked example, driving an MCP3008 ADC¶
The MCP3008 is a popular SPI ADC: 10-bit, 8 channels, single-ended. Communication protocol:
Write a 3-byte command:
0x01,(0x80 | channel<<4),0x00.Read 3 bytes back. The 10-bit value lives in
rx[1] & 0x03(high 2 bits) andrx[2](low 8 bits).
A minimal driver function:
static int mcp3008_read_channel(struct mcp3008_priv *p, int channel, u16 *out)
{
u8 tx[3] = { 0x01, (u8)(0x80 | (channel << 4)), 0x00 };
u8 rx[3];
struct spi_transfer xfer = {
.tx_buf = tx, .rx_buf = rx, .len = 3,
};
int err = spi_sync_transfer(p->spi, &xfer, 1);
if (err < 0)
return err;
*out = ((rx[1] & 0x03) << 8) | rx[2];
return 0;
}
Wrap with IIO (Ch 49) and you have an 8-channel ADC exposed via /sys/bus/iio/devices/iio:device0/in_voltage*_raw. The mainline drivers/iio/adc/mcp320x.c does this for the entire MCP320x family.
47.8 Lab¶
Configure ecspi3 in DT (or whichever you have). Add a generic spidev node, verify
/dev/spidev2.0appears.Loopback test. Short MOSI to MISO. Write a small user-space program that opens
/dev/spidev2.0, sends 4 bytes, reads them back. Confirm what came out comes back in.Build the fastadc / MCP3008 skeleton. Wire up an MCP3008 (or use a real spidev device). Verify
dev_inforeports the test sample.Try full-duplex. Send
0xA5while reading. Print what comes back simultaneously. SPI’s full-duplex nature is unique among common buses, appreciate it.Speed sweep. Vary
spi-max-frequencyfrom 100 kHz to 25 MHz in DT. Observe where signal integrity breaks (your scope or analyzer is your friend). Linux honorsspi-max-frequencyliterally. The controller picks the highest divisor below it.Multiple chip-selects. Configure two devices on the same bus (CS0 and CS1) and access both. Verify each gets its own SPI mode/speed.
47.9 Pitfalls¶
Wrong CS polarity. If a chip wants active-high CS, the controller’s default (active-low) won’t drive it correctly. Use
spi-cs-highin DT.CPOL/CPHA wrong. Symptom: reads return 0xFF or junk. Cross-check the chip’s datasheet mode against your DT. Mode 0 is most common. Mode 3 next.
Speed too high for layout. Long traces, missing termination, no ground reference plane → garbage at 20 MHz that works fine at 1 MHz. Start slow, ramp up.
Sending a single read transfer with
lenlarger than yourrx_buf. Buffer overflow → kernel panic. Thelenfield is the SPI clock count, so you need at least that many bytes inrx_buf.Calling
spi_sync_transferfrom atomic context. It sleeps. Usespi_asyncfrom atomic context, or defer to a workqueue.Native CS vs GPIO-CS subtleties. The i.MX eCSPI native CS asserts and deasserts for each
spi_transfer. To hold CS across multiple transfers, either (a) put them all in onespi_message, or (b) use GPIO-based CS viacs-gpios, software holds GPIO-CS for the whole message.bits_per_word!= 8. Most chips use 8-bit words. If you set 16, the controller packs two bytes per “word”, and the byte order may not be what you expect. Stay at 8 unless you have a reason.Forgetting
spi_setupafter changing mode/speed. Changes tospi->mode,spi->bits_per_word, etc., don’t take effect until you callspi_setup. Probe should always call it once.
47.10 Going deeper¶
Documentation/spi/: SPI subsystem documentation.Documentation/devicetree/bindings/spi/: DT bindings for SPI controllers and devices.drivers/spi/spi-imx.c: the i.MX SPI controller driver.drivers/iio/adc/mcp320x.c: production MCP3008 driver as a clean IIO reference.drivers/spi/spidev.c: the spidev chardev driver. How/dev/spidevNis implemented.Documentation/spi/spidev.rst: when/how to use spidev (and the controversialdh2228fvworkaround explained).
Next chapter: Chapter 48: PWM and RTC. Two short but practical subsystems for ubiquitous embedded needs: dimming backlights, generating tones, and keeping time across power cycles. MCU bridge: Think of Linux PWM like an MCU timer output channel, except the driver exposes period, duty cycle, polarity, and enable state through a subsystem. PWM: Pulse-Width Modulation, a timer output whose duty cycle controls average power or encodes timing.