Bus drivers (I2C and SPI)

SkillAI & models

Use 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.

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

FieldBound contract
TriggerWriting 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.
AuthorityRead-only. Writes nothing. Chat output only. No remote mutation.
Side effectReturns driver skeletons and debug commands. No source files are modified.
DoneThe client driver skeleton, the transfer or regmap access pattern, the DT child node, and a debug path for the reported symptom are delivered.

Inputs

  1. Device and bus (required): the chip address on I2C or chip-select on SPI, the adapter or controller, and the datasheet register map.
  2. Transfer shape (optional): register width and value width, which set regmap_config.
  3. Failure report (optional): the symptom, such as -EREMOTEIO, a stuck bus, or a probe that never runs.

Procedure

  1. 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);
    

    remove returns void; 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.

  2. Bind from the device tree child node. The reg property 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 compatible matches one of_device_id entry and the address matches the datasheet.

  3. Use regmap for register access. It centralizes endianness, caching, and retries; hand-rolled i2c_transfer pairs 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);
    

    -EIO from regmap usually means reg_bits or val_bits disagrees with the chip. Done when: every register access goes through regmap and the config matches the datasheet widths.

  4. Write a raw i2c_transfer only 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 = &reg },
        { .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.

  5. 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.

  6. Debug the reported symptom on the real bus. i2c-tools runs 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/name
    

    Route deeper work: spi-i2c-baremetal for register-level protocol mechanics, device-tree for node and binding work, platform-device-model for driver-model context, writing-char-drivers for a userspace char interface over the sensor. Done when: the symptom maps to a confirmed address, wiring, or binding cause.

Failure and recovery

SymptomCauseRecovery
-EREMOTEIO on transferNACK: wrong address or no pull-upsi2cdetect to scan; check reg in the DT node and the wiring.
Garbled SPI dataCPOL/CPHA mismatchSet spi->mode from the datasheet timing diagram.
Probe never runsDT compatible typo or missing child nodeMatch of_match_table exactly; confirm the node sits under the right bus.
regmap returns -EIORegister or value width mismatchFix reg_bits/val_bits in regmap_config.
Probe deferredClock or regulator supplier not readyReturn -EPROBE_DEFER; fix the supplier in DT.
Slow bulk SPIBuffers not DMA-safeAllocate 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