I2C and SPI Bus Drivers
Writing drivers for I2C and SPI peripheral devices in the Linux kernel
I2C overview
I2C (Inter-Integrated Circuit) is a two-wire serial protocol for connecting low-speed peripherals (sensors, EEPROMs, touch controllers) to a microprocessor. The Linux I2C subsystem was carried into the mainline kernel through Greg Kroah-Hartman's driver-model work and later co-maintained by Jean Delvare (formerly of the lm-sensors project), who took sole maintainership in September 2005 (LWN: I2C patches for 2.6.12-rc1).
Physical wires:
SDA (data) ────────────────────────
SCL (clock) ────────────────────────
│ │ │
Master Dev1 Dev2
(host CPU) (0x48) (0x50)
↑ ↑
7-bit addresses
I2C transaction format
Start: SDA falls while SCL is high
│
└─ [7-bit address] [R/W bit] [ACK]
│
└─ [data byte 0] [ACK]
[data byte 1] [ACK]
...
Stop: SDA rises while SCL is high
I2C client driver
struct i2c_driver
#include <linux/i2c.h>
#include <linux/module.h>
/* Device-specific private data */
struct my_sensor {
struct i2c_client *client;
struct mutex lock;
int irq;
};
/* Match table for device tree / ACPI */
static const struct of_device_id my_sensor_of_match[] = {
{ .compatible = "vendor,my-sensor" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_sensor_of_match);
static const struct i2c_device_id my_sensor_id[] = {
{ "my-sensor", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, my_sensor_id);
/* Called when device is found */
static int my_sensor_probe(struct i2c_client *client)
{
struct my_sensor *sensor;
int ret;
/* Verify the device supports the operations we need */
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBus byte operations required\n");
return -EOPNOTSUPP;
}
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->client = client;
mutex_init(&sensor->lock);
i2c_set_clientdata(client, sensor);
/* Read and verify device ID register */
ret = i2c_smbus_read_byte_data(client, REG_DEVICE_ID);
if (ret < 0) {
dev_err(&client->dev, "failed to read device ID: %d\n", ret);
return ret;
}
if (ret != EXPECTED_DEVICE_ID) {
dev_err(&client->dev, "unexpected device ID: 0x%02x\n", ret);
return -ENODEV;
}
/* Register with IIO or hwmon subsystem */
return my_sensor_register_iio(sensor);
}
static void my_sensor_remove(struct i2c_client *client)
{
struct my_sensor *sensor = i2c_get_clientdata(client);
my_sensor_unregister_iio(sensor);
}
static struct i2c_driver my_sensor_driver = {
.driver = {
.name = "my-sensor",
.of_match_table = my_sensor_of_match,
},
.probe = my_sensor_probe,
.remove = my_sensor_remove,
.id_table = my_sensor_id,
};
module_i2c_driver(my_sensor_driver);
/* Expands to: module_init + module_exit with i2c_add/del_driver */
I2C transfer functions
SMBus (common for simple devices)
/* Read/write a single byte at a register address: */
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 reg);
s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 reg, u8 value);
/* Read/write a 16-bit word: */
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 reg);
s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 reg, u16 value);
/* Read a block of data: */
s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client, u8 reg,
u8 length, u8 *values);
/* values receives `length` bytes starting from register `reg` */
/* Example: read 6 bytes of accelerometer data from reg 0x28: */
u8 data[6];
ret = i2c_smbus_read_i2c_block_data(client, 0x28, 6, data);
Raw I2C messages (for complex protocols)
/* For devices that don't follow SMBus convention: */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* Example: write register address, then read multiple bytes
(common register access pattern): */
static int my_read_regs(struct i2c_client *client, u8 reg,
u8 *buf, size_t len)
{
struct i2c_msg msgs[2] = {
{
/* Write phase: send register address */
.addr = client->addr,
.flags = 0, /* write */
.len = 1,
.buf = ®,
},
{
/* Read phase: read `len` bytes */
.addr = client->addr,
.flags = I2C_M_RD, /* read */
.len = len,
.buf = buf,
},
};
return i2c_transfer(client->adapter, msgs, 2);
}
regmap: register access abstraction
regmap provides a unified API for register access across I2C, SPI, MMIO, and other buses:
#include <linux/regmap.h>
/* Define the register map configuration: */
static const struct regmap_config my_regmap_config = {
.reg_bits = 8, /* register address width */
.val_bits = 8, /* register value width */
.max_register = 0xFF,
/* Optional: caching for read-only or slow registers */
.cache_type = REGCACHE_RBTREE,
/* Readable/writeable register masks: */
.readable_reg = my_readable_reg,
.writeable_reg = my_writeable_reg,
.volatile_reg = my_volatile_reg, /* not cached */
};
static int my_sensor_probe(struct i2c_client *client)
{
struct my_sensor *sensor;
struct regmap *regmap;
/* Create regmap backed by I2C: */
regmap = devm_regmap_init_i2c(client, &my_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
sensor->regmap = regmap;
/* ... */
}
/* Use regmap for register access: */
unsigned int val;
regmap_read(sensor->regmap, REG_STATUS, &val);
regmap_write(sensor->regmap, REG_CONFIG, 0x01);
/* Read-modify-write (atomic): */
regmap_update_bits(sensor->regmap, REG_CONFIG,
BIT(3) | BIT(2), /* mask */
BIT(3)); /* value: set bit 3, clear bit 2 */
/* Bulk read multiple consecutive registers: */
u8 buf[6];
regmap_bulk_read(sensor->regmap, REG_DATA_START, buf, 6);
SPI overview
SPI (Serial Peripheral Interface) is a synchronous 4-wire protocol. The Linux SPI subsystem was written by David Brownell and merged in Linux 2.6.16 (commit 8ae12a0d):
SCLK ──────────────────── clock (master-driven)
MOSI ──────────────────── master-out slave-in
MISO ──────────────────── master-in slave-out
CS/SS ──────────────────── chip select (one per device, active-low)
Unlike I2C:
- Full-duplex (simultaneous TX and RX)
- No addressing: CS line selects device
- No ACK: master never knows if slave received
- Higher speeds (up to 100+ MHz vs I2C's 3.4 MHz)
SPI device driver
#include <linux/spi/spi.h>
struct my_spi_dev {
struct spi_device *spi;
struct mutex lock;
};
static const struct of_device_id my_spi_of_match[] = {
{ .compatible = "vendor,my-spi-sensor" },
{ }
};
static int my_spi_probe(struct spi_device *spi)
{
struct my_spi_dev *dev;
/* Configure SPI parameters: */
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0; /* CPOL=0, CPHA=0 */
spi->max_speed_hz = 10000000; /* 10 MHz */
spi_setup(spi); /* apply configuration */
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
dev->spi = spi;
spi_set_drvdata(spi, dev);
return my_spi_dev_init(dev);
}
static struct spi_driver my_spi_driver = {
.driver = {
.name = "my-spi-sensor",
.of_match_table = my_spi_of_match,
},
.probe = my_spi_probe,
};
module_spi_driver(my_spi_driver);
SPI transfer functions
/* Simple synchronous read/write: */
u8 tx_buf[4], rx_buf[4];
/* Full-duplex transfer (TX and RX simultaneously): */
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = 4,
.speed_hz = 5000000, /* override default speed */
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m);
/* Convenience wrappers: */
spi_write(spi, tx_buf, 4); /* TX only */
spi_read(spi, rx_buf, 4); /* RX only (sends 0x00) */
spi_write_then_read(spi, tx_buf, 2, rx_buf, 4); /* CS held low between */
/* regmap for SPI (same API as I2C!): */
regmap = devm_regmap_init_spi(spi, &my_regmap_config);
regmap_read(regmap, REG_ID, &val);
Device Tree instantiation
/* Board DTS: */
&i2c1 {
status = "okay";
clock-frequency = <400000>; /* 400 kHz fast mode */
my_sensor: sensor@48 {
compatible = "vendor,my-sensor";
reg = <0x48>; /* I2C address */
vdd-supply = <&ldo3>; /* power supply */
interrupt-parent = <&gpio0>;
interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
};
};
&spi0 {
status = "okay";
cs-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
my_spi_sensor: sensor@0 {
compatible = "vendor,my-spi-sensor";
reg = <0>; /* chip select 0 */
spi-max-frequency = <10000000>;
spi-cpol; /* CPOL=1 */
spi-cpha; /* CPHA=1 → together: SPI_MODE_3 */
};
};
Debugging I2C/SPI
# List I2C buses:
ls /dev/i2c-*
i2cdetect -l
# Scan I2C bus 1 for devices:
i2cdetect -y 1
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
# (device found at address 0x48)
# Read register 0x00 from device at address 0x48:
i2cget -y 1 0x48 0x00
# Write 0x01 to register 0x10:
i2cset -y 1 0x48 0x10 0x01
# Decode I2C traffic (if hardware supports):
# Use /sys/kernel/debug/i2c-0/
cat /sys/kernel/debug/i2c-0/timing
# SPI trace:
echo 1 > /sys/kernel/debug/dynamic_debug/control
# or use spidev_test:
spidev_test -D /dev/spidev0.0 -s 1000000 -p '\x01\x02\x03'
# regmap debugfs:
ls /sys/kernel/debug/regmap/
cat /sys/kernel/debug/regmap/my-sensor-0-0048/registers
# 00: 01
# 01: 0f
# ...
# Trace I2C transactions:
bpftrace -e '
kprobe:i2c_transfer {
printf("i2c_transfer: adapter=%s num_msgs=%d\n",
((struct i2c_adapter *)arg0)->name, (int)arg2);
}'
Further reading
- Platform Drivers — MMIO register-mapped devices
- Device Tree — DTS binding for I2C/SPI devices
- Device Model — bus/device/driver framework
- DMA API — DMA for SPI controllers
drivers/i2c/— I2C subsystemdrivers/spi/— SPI subsysteminclude/linux/regmap.h— regmap APIDocumentation/driver-api/i2c.rst— I2C driver guide