linux device driver development

    Writing a Linux I2C Sensor Driver: Device Tree, probe() and the Mistakes That Cost a Day

    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.

    ·XTELL Engineering Team

    First, decide whether you need a kernel driver at all

    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:

    • The device raises an interrupt that must be handled with low latency.
    • Several programs, or the kernel itself, need to share the device, so access needs arbitration.
    • The device belongs to an existing subsystem (sensors, input, regulators, RTC, audio, display) and the product should use the standard interface for it.
    • It has power management, clocks, regulators or resets that must be sequenced with the rest of the system.

    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 shape of a minimal I2C driver

    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 device tree node

    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.

    When the device does not appear

    Work through this list in order; each step eliminates a whole class of problems.

    1. Is the bus alive? Run i2cdetect -y N for the right bus number. If the address does not answer, it is a hardware or power problem, and no driver will fix it: check pull-up resistors, the supply, the address pins and the voltage levels.
    2. Is the controller enabled? Its node needs status = "okay", and its clock and pin multiplexing must be set. A missing pinctrl entry gives a bus that exists and never works.
    3. Did the driver match? Look in /sys/bus/i2c/devices/. If the device is listed but has no driver link, the compatible string or the module is the problem. dmesg will not say much about a match that never happened.
    4. Did probe run, and what did it return? A return value of -EPROBE_DEFER means a resource the driver needs (a regulator, a clock, a GPIO) is not ready yet; the kernel retries later. Use dev_err_probe(), as above, so the reason is recorded in /sys/kernel/debug/devices_deferred instead of vanishing.
    5. Is the supply really on? A regulator that no node declares as needed can be switched off by the kernel late in boot, after the driver ran once. Declare the supply on the device node.

    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.

    Growing the driver without making a mess

    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.

    Where this sits in a real product

    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.

    Need Professional Services?

    Contact us for customized solutions and quotes