A working walkthrough of a Linux I2C device driver: when to write a kernel driver at all, the driver skeleton, the device tree node, regmap, probe deferral and a debugging checklist for when the device does not appear.
A surprising number of I2C devices do not need one. Linux exposes every I2C adapter to user space through the i2c-dev interface, and a small program or a script using the i2c-tools utilities can read and write registers. For a prototype, a one-off sensor or a device that only your own application ever talks to, that is the quickest path, and it keeps a failure in your code from taking the kernel with it.
Write a kernel driver when one of these is true:
For sensors, look at the Industrial I/O (IIO) subsystem before inventing your own interface. It already gives user space a consistent way to read values, scale them and receive buffered samples.
The following is a complete, minimal driver for an imaginary sensor with an 8-bit register map. It reads the chip identifier at probe time and does nothing else, which is exactly what you want for a first bring-up step: it proves the bus, the address, the power and the binding work, before any feature code exists.
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#define REG_ID 0x00
static const struct regmap_config demo_regmap = {
.reg_bits = 8,
.val_bits = 8,
};
static int demo_probe(struct i2c_client *client)
{
struct regmap *map;
unsigned int id;
int ret;
map = devm_regmap_init_i2c(client, &demo_regmap);
if (IS_ERR(map))
return PTR_ERR(map);
ret = regmap_read(map, REG_ID, &id);
if (ret)
return dev_err_probe(&client->dev, ret, "cannot read chip id\n");
dev_info(&client->dev, "chip id 0x%02x\n", id);
i2c_set_clientdata(client, map);
return 0;
}
static const struct of_device_id demo_of_match[] = {
{ .compatible = "acme,demo-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, demo_of_match);
static struct i2c_driver demo_driver = {
.driver = {
.name = "demo-sensor",
.of_match_table = demo_of_match,
},
.probe = demo_probe,
};
module_i2c_driver(demo_driver);
MODULE_LICENSE("GPL");
Three details matter. The probe signature shown, with only the client argument, is the one used from kernel 6.3 onward; older kernels use a different signature, so check the version you build against. devm_ functions release their resources automatically when the device goes away, which removes most cleanup code and most leaks. And regmap handles register access, caching and bus differences, so the same register description works when the part is later offered with an SPI interface.
The driver binds to hardware because the device tree says the hardware exists. The node sits under the I2C controller it is wired to:
&i2c1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
sensor@44 {
compatible = "acme,demo-sensor";
reg = <0x44>;
};
};
The compatible string must match the driver's table exactly, character for character. The reg value is the 7-bit I2C address, without the read and write bit, and the single most common wrong value is the 8-bit address copied from a datasheet. Supplies, reset and interrupt lines belong in this node too, as properties the driver reads, so that a hardware change is a device tree edit and not a code change.
Work through this list in order; each step eliminates a whole class of problems.
Dynamic debug (dyndbg) turns on a driver's debug messages at run time without rebuilding, and a logic analyser on SCL and SDA settles any argument about what actually went on the wire.
Once the identifier reads correctly, add features in this order, testing each: single reads exposed through the right subsystem, then interrupts, then power management (runtime suspend), then calibration data, and only last the optional modes. Keep the register map in one place, never sleep in interrupt context, and make the driver survive being unbound and bound again: echo 1-0044 > /sys/bus/i2c/drivers/demo-sensor/unbind is a quick test that finds most resource-handling bugs.
Treat the datasheet's timing numbers as limits to verify, not assumptions: long cables, slow edges from weak pull-ups and parts that stretch the clock all show up only on the real board.
A driver is one layer of a board bring-up. Our i.MX6 CAN bus data acquisition system is an example of Linux work where the interesting part was the data path around the drivers: acquisition, parsing and storage under load. If you need drivers, device tree work or a full board bring-up done for your hardware, see our Linux BSP and driver service, and our bring-up guide for the wider picture.
Contact us for customized solutions and quotes