| ← PREV_LEC | NEXT_LEC → |
In this free Linux device drivers course lecture, we finish the spi multi-transfer driver linux kernel topic that we started in the previous lecture. A single spi_message can carry more than one spi_transfer, and chaining transfers correctly is one of the most useful skills for anyone writing a real SPI client driver on modern kernel 6.x. We will also close out the SPI driver-writing chapter with a complete “putting it all together” checklist you can reuse for any future SPI driver.
What You Will Learn
cs_change field behaviour
spi_sync_transfer() helper
spi_read / spi_write / spi_write_then_read
Complete SPI driver checklist
Build and test workflow
Prerequisites
struct spi_message basics
spi_sync() fundamentals
Basic C and kernel module skills
If you have not gone through the earlier SPI lectures of this free embedded Linux course, read the previous lecture on spi_transfer and spi_message first — use the PREV_LEC link above.
Why Chain Multiple SPI Transfers?
Many real SPI devices need more than one back-to-back exchange to complete a single logical operation. A flash chip might need a command byte written, followed by an address, followed by a data read — all while the chip select line stays asserted the whole time. If you closed the chip select (CS) line between each of these steps, the device would treat them as three unrelated transactions and the operation would fail.
This is exactly the problem that chaining spi_transfer structures inside a single spi_message solves. Every transfer you add to the message runs in sequence over the wire, and you control whether CS toggles between each transfer using the cs_change field.
write register address
read register data
CS stays asserted across both transfers — the device sees one continuous operation.
Understanding cs_change
The cs_change field of struct spi_transfer tells the SPI core what to do with the chip select line after that particular transfer finishes:
| cs_change value | Effect after this transfer |
|---|---|
0 |
CS stays asserted; the next transfer in the message continues the same transaction. |
1 |
CS is deasserted and reasserted before the next transfer — useful when a device needs a fresh CS pulse between commands. |
1 on the last transfer |
CS is released once the message completes (this is the normal case for the final transfer). |
For a simple “write register address, then read the data” sequence, you almost always want cs_change = 0 on every transfer except the last one, so the device sees one uninterrupted transaction.
Original Driver: ep_spi_multixfer_demo
Below is an original, from-scratch driver example (not copied from any book) that reads a device register using a two-transfer chained message. It builds on the ep_spi_msg_demo driver introduced in the previous lecture and targets kernel 6.x.
#define EP_REG_READ_CMD 0x80
struct ep_spi_multixfer {
struct spi_device *spi;
struct mutex lock;
u8 tx_cmd;
u8 rx_val;
};
static int ep_multixfer_read_reg(struct ep_spi_multixfer *priv, u8 reg)
{
struct spi_transfer xfers[2] = { };
int ret;
mutex_lock(&priv->lock);
priv->tx_cmd = EP_REG_READ_CMD | reg;
/* transfer 0: send the register address, keep CS asserted */
xfers[0].tx_buf = &priv->tx_cmd;
xfers[0].len = 1;
xfers[0].cs_change = 0;
/* transfer 1: read back the register value, CS released after */
xfers[1].rx_buf = &priv->rx_val;
xfers[1].len = 1;
ret = spi_sync_transfer(priv->spi, xfers, ARRAY_SIZE(xfers));
mutex_unlock(&priv->lock);
return ret;
}
Notice that this example uses spi_sync_transfer() instead of manually calling spi_message_init() and spi_message_add_tail() for every transfer. This is a modern helper — it builds the message internally and sends it in one call, so short chains like this one become far less boilerplate-heavy than the equivalent code in older kernel documentation.
spi_sync_transfer() vs Manual Message Building
| Approach | When to use it |
|---|---|
Manual spi_message_init() + spi_message_add_tail() + spi_sync() |
You need to reuse the same spi_message object across many calls, or you are queueing it asynchronously with spi_async(). |
spi_sync_transfer(spi, xfers, n) |
A one-shot synchronous transaction built from a plain array of spi_transfer structs — the common case in most drivers. |
spi_read, spi_write, and spi_write_then_read
For very small, simple exchanges, the SPI core also provides three convenience wrappers built on top of spi_sync(). Use them only for small amounts of data, since each one still allocates a temporary message internally.
int spi_read(struct spi_device *spi, void *buf, size_t len);
int spi_write(struct spi_device *spi, const void *buf, size_t len);
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);
- spi_read() — reads
lenbytes intobufwith no data written first. - spi_write() — writes
lenbytes frombufwith nothing read back. - spi_write_then_read() — writes
n_txbytes, then readsn_rxbytes, all with CS held across both halves — effectively a built-in two-transfer chain, and often a shorter way to write the exact driver shown above.
spi_read() / spi_write()
spi_write_then_read()
spi_sync_transfer()
Putting It All Together: SPI Client Driver Checklist
We have now covered every building block of an SPI client driver across this chapter. Here is the complete checklist for writing one on kernel 6.x, recapping every earlier lecture in this free Linux kernel development course:
| Step | What to do |
|---|---|
| 1 | Declare the device IDs your driver supports using struct spi_device_id, and an of_device_id table if device tree matching is needed. |
| 2 | Register both tables with MODULE_DEVICE_TABLE(spi, ...) and MODULE_DEVICE_TABLE(of, ...) so the SPI core and udev can match your driver to hardware. |
| 3 | Write probe() and a void-returning remove() (kernel 5.18+): allocate per-device data, call spi_setup() if you need a non-default mode, and register with the appropriate kernel framework (char device, IIO, hwmon, etc). |
| 4 | Fill a struct spi_driver: set .id_table, .probe, .remove, and inside .driver set .owner = THIS_MODULE, a driver .name, and .of_match_table if applicable. |
| 5 | Register the driver with module_spi_driver(your_driver_struct); instead of writing manual module_init()/module_exit() boilerplate. |
Build and Test Workflow
# build the module against your running kernel headers
make -C /lib/modules/$(uname -r)/build M=$PWD modules
# load it
sudo insmod ep_spi_multixfer_demo.ko
# confirm it bound to the device tree node
ls /sys/bus/spi/devices/
# check the kernel log for the probe message
dmesg | tail
Expected output in dmesg after a successful probe looks similar to:
ep_spi_multixfer_demo spi0.0: probed, register value = 0x2A
Common Mistakes
| Mistake | Why it breaks |
|---|---|
Setting cs_change = 1 between transfers that must stay in one transaction |
The device sees CS toggle and treats the two transfers as unrelated operations. |
Using stack buffers directly as tx_buf/rx_buf |
Many SPI controllers use DMA; stack memory is not guaranteed DMA-safe. Use kmalloc()/devm_kzalloc() buffers instead. |
Forgetting ARRAY_SIZE() when passing the transfer array length |
A hard-coded wrong length silently truncates or overruns the chained transaction. |
Best Practices and Performance Considerations
- Prefer
spi_sync_transfer()for one-shot chained operations — it is less code and just as safe as manual message building. - Batch related exchanges into a single message rather than issuing several separate
spi_sync()calls, since each call has scheduling overhead. - Protect shared driver state with a mutex, since
spi_sync()andspi_sync_transfer()can sleep and must never be called from atomic context. - For high-throughput paths, consider
spi_async()with a completion callback instead of blocking the calling thread on every transfer.
Summary and Key Takeaways
- Chaining
spi_transferstructs inside onespi_messagelets you build multi-step SPI operations that stay atomic under one CS assertion. cs_changecontrols whether CS toggles between transfers or stays asserted through the whole message.spi_sync_transfer()is the modern, low-boilerplate way to send a short chain of transfers.spi_read(),spi_write(), andspi_write_then_read()cover the common small-data cases without building a message by hand.- The five-step checklist in this lecture applies to every SPI client driver you will ever write on kernel 6.x.
Conclusion
This lecture closes the kernel-side half of the SPI driver-writing chapter in this free Linux device drivers course. You now know how to chain transfers correctly, which helper function fits which situation, and the full checklist for wiring a driver into the SPI core. In the next lecture of this free embedded Linux course, we move to the userspace side of SPI and see how the spidev driver lets applications talk to SPI hardware directly through /dev/spidevB.C, without writing any kernel code at all.
Frequently Asked Questions
What happens if I forget to set cs_change on a middle transfer?
The field defaults to 0 if you zero-initialize the struct (as shown in the example), so CS stays asserted by default. Problems only appear if you explicitly set it to 1 by mistake.
Can I mix spi_read() and a chained spi_sync_transfer() call in the same driver?
Yes. Use whichever API matches each operation — a single register read can use spi_read(), while a command-then-data sequence can use spi_sync_transfer().
Is spi_write_then_read() the same as a two-transfer chained message?
Functionally yes — internally it builds a short-lived two-transfer message with CS held across both halves, which is why it is recommended only for small buffers.
Do I need spi_setup() before every transfer?
No. Call spi_setup() once in probe() (or whenever you need to change mode/speed), not on every transaction.
Why does remove() return void on kernel 6.x?
Since kernel 5.18, the SPI core changed spi_driver.remove() to a void-returning function because the return value was never meaningfully used — removal cannot be aborted.
What is the maximum number of transfers I can chain in one message?
There is no fixed kernel-imposed maximum; the practical limit comes from your SPI controller’s descriptor/DMA capabilities and message size limits.
Can spi_sync_transfer() be called from an interrupt handler?
No. It can sleep, so it must only be called from process context, such as inside a workqueue or kernel thread triggered by the interrupt, not the hard IRQ handler itself.
Continue the Free Linux Kernel Development Course
Explore more free lectures on SPI, I2C, device tree, and Linux device drivers on EmbeddedPathashala.
| ← PREV_LEC | NEXT_LEC → |
