Bus drivers (I2C and SPI)
SkillAI & modelsUse when writing a Linux i2c_driver or spi_driver, doing bus register access, DMA-safe SPI transfers, or debugging -EREMOTEIO. Not for MMIO platform drivers: use platform-device-model.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Bus drivers (I2C and SPI) skill
What this skill tells your AI
The instructions your AI receives, as published by outlinedriven/outline-driven-development in .devin/skills/bus-drivers-i2c-spi/SKILL.md and read by ahel’s review.
Contract
| Field | Bound contract |
|---|---|
| Trigger | Writing a Linux I2C or SPI client driver: bus registration, i2c_transfer or spi_sync, regmap over the bus, DMA-safe SPI buffers, device tree binding on bus children, or NACK and -EREMOTEIO debugging. |
| Authority | Read-only. Writes nothing. Chat output only. No remote mutation. |
| Side effect | Returns driver skeletons and debug commands. No source files are modified. |
| Done | The client driver skeleton, the transfer or regmap access pattern, the DT child node, and a debug path for the reported symptom are delivered. |
Inputs
- Device and bus (required): the chip address on I2C or chip-select on SPI, the adapter or controller, and the datasheet register map.
- Transfer shape (optional): register width and value width, which set
regmap_config. - Failure report (optional): the symptom, such as
-EREMOTEIO, a stuck bus, or a probe that never runs.
Procedure
-
Write the I2C client driver skeleton. The bus core owns the adapter; the driver owns registers only.
#include <linux/i2c.h> #include <linux/mod_devicetable.h> static void my_remove(struct i2c_client *client) {} static struct i2c_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "mysensor", .of_match_table = my_of_id, }, .id_table = my_id, }; module_i2c_driver(my_driver);removereturnsvoid; the int-returning form was removed from the bus driver structs in kernel 6.11, and the kernel floor here (LTS 6.18, mainline 7.2) is past it. Done when: the driver struct carries probe, remove, both match tables, and the module registration macro. -
Bind from the device tree child node. The
regproperty is the bus address.&i2c1 { sensor@48 { compatible = "vendor,sensor"; reg = <0x48>; }; };static const struct of_device_id my_of_id[] = { { .compatible = "vendor,sensor" }, { } }; MODULE_DEVICE_TABLE(of, my_of_id);Done when: the DT
compatiblematches oneof_device_identry and the address matches the datasheet. -
Use regmap for register access. It centralizes endianness, caching, and retries; hand-rolled
i2c_transferpairs are for unusual protocols only.static const struct regmap_config my_regmap_config = { .reg_bits = 8, /* register address width */ .val_bits = 8, /* register value width */ .max_register = 0x7F, }; map = devm_regmap_init_i2c(client, &my_regmap_config); if (IS_ERR(map)) return PTR_ERR(map); regmap_read(map, REG_STATUS, &val); regmap_write(map, REG_CTRL, CTRL_ENABLE); regmap_update_bits(map, REG_CTRL, MASK, ENABLE);-EIOfrom regmap usually meansreg_bitsorval_bitsdisagrees with the chip. Done when: every register access goes through regmap and the config matches the datasheet widths. -
Write a raw
i2c_transferonly when regmap does not fit, for example a write-then-read with a repeated start.u8 reg = 0x0F, val; struct i2c_msg msgs[] = { { .addr = client->addr, .flags = 0, .len = 1, .buf = ® }, { .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, }; ret = i2c_transfer(client->adapter, msgs, 2);Done when: the message pair encodes the write-then-read and the return value is checked.
-
Write the SPI driver with explicit transfer settings from the datasheet.
static int spi_probe(struct spi_device *spi) { spi->mode = SPI_MODE_0; /* CPOL/CPHA from the datasheet */ spi->bits_per_word = 8; spi_setup(spi); struct spi_transfer t = { .tx_buf = tx, .rx_buf = rx, .len = len, }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); return spi_sync(spi, &m); }Register with
module_spi_driver. For bulk transfers, keep buffers DMA-safe:kmalloced, not stack. The SPI core can bounce small or non-DMA-safe buffers, but the copy costs throughput. Done when: mode, word size, and buffer lifetime match the controller's DMA needs. -
Debug the reported symptom on the real bus.
i2c-toolsruns from user space with no driver loaded.i2cdetect -y 1 # scan addresses for an ACK i2cdump -y 1 0x48 # dump registers cat /sys/bus/i2c/devices/i2c-1/1-0048/nameRoute deeper work:
spi-i2c-baremetalfor register-level protocol mechanics,device-treefor node and binding work,platform-device-modelfor driver-model context,writing-char-driversfor a userspace char interface over the sensor. Done when: the symptom maps to a confirmed address, wiring, or binding cause.
Failure and recovery
| Symptom | Cause | Recovery |
|---|---|---|
-EREMOTEIO on transfer | NACK: wrong address or no pull-ups | i2cdetect to scan; check reg in the DT node and the wiring. |
| Garbled SPI data | CPOL/CPHA mismatch | Set spi->mode from the datasheet timing diagram. |
| Probe never runs | DT compatible typo or missing child node | Match of_match_table exactly; confirm the node sits under the right bus. |
regmap returns -EIO | Register or value width mismatch | Fix reg_bits/val_bits in regmap_config. |
| Probe deferred | Clock or regulator supplier not ready | Return -EPROBE_DEFER; fix the supplier in DT. |
| Slow bulk SPI | Buffers not DMA-safe | Allocate with kmalloc for transfer buffers. |
Output
The client driver skeleton for the requested bus; the DT child node and match table; the regmap config and access pattern, or the raw transfer; the SPI transfer settings with DMA-safety rules; the debug command run against the reported symptom; the routing to related skills.
Signals
- GitHub stars
- 52
- Forks
- 9
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
bus-drivers-i2c-spi- Source
- github.com/outlinedriven/outline-driven-development