Skip to main content

TECH VEDA

Embedded Linux on Edge-AI 23rd Sept 2026 enrollingLinux kernel & Device drivers starts on 24th Oct 2026 enrollingCorporate on-site training - Submit proposal Pick your modulesSharpen your kernel skills: deep dives, drivers, Yocto, CVEs, careers — updated daily. Read the blog →Embedded Linux fast track starts 23rd sept 2026 enrollingEmbedded Linux Mastery track starts 23rd sept 2026 enrollingLinux systems engineering starts 23rd sept 2026 enrolling
Tutorials

The regmap Subsystem: One Register API Across I2C, SPI and MMIO

Build a Linux I2C driver with the regmap subsystem: regmap_config, probe, the register cache, suspend and resume, and debugfs. Complete buildable code.

The regmap Subsystem: One Register API Across I2C, SPI and MMIO

This tutorial builds a small I2C driver that uses the regmap subsystem instead of hand-written bus helpers. You will confirm regmap is enabled on your kernel, describe the device in a struct regmap_config, create the map in probe, read and write registers, turn on the register cache, and read live register values from debugfs. The same driver body works over SPI or MMIO by changing one line.

Before regmap, every I2C or SPI driver wrote its own small helpers to read a register, write a register, and change a few bits inside one. Each helper repeated the same pattern: build a buffer, call the bus transfer function, check the return value. The regmap subsystem removes that repetition. It lives in drivers/base/regmap/ and is exposed through include/linux/regmap.h. You describe the device once in configuration data, and the bus mechanics stay behind a struct regmap_bus that the I2C, SPI and MMIO back-ends implement. This tutorial walks through that end to end with a complete, buildable driver.

What you need

  • A Linux machine with kernel headers installed: sudo apt install linux-headers-$(uname -r) build-essential.
  • Root access, for the debugfs step at the end.
  • Optional: a board with a real I2C peripheral. The driver compiles and loads without one; it will only bind to a device when a matching I2C device or device tree node exists.

Step 1: Confirm the regmap subsystem is enabled

Regmap is a kernel library, so it is only present if the bus back-end your driver needs was built. Check your running kernel’s config:

raghu@techveda.org:~$ grep -E "^CONFIG_REGMAP" /boot/config-$(uname -r)
CONFIG_REGMAP=y
CONFIG_REGMAP_AC97=m
CONFIG_REGMAP_I2C=y
CONFIG_REGMAP_SLIMBUS=m
CONFIG_REGMAP_SPI=y
CONFIG_REGMAP_SPMI=m
CONFIG_REGMAP_W1=m
CONFIG_REGMAP_MMIO=y
CONFIG_REGMAP_IRQ=y
CONFIG_REGMAP_SOUNDWIRE=m
CONFIG_REGMAP_SOUNDWIRE_MBQ=m
CONFIG_REGMAP_SCCB=m
CONFIG_REGMAP_I3C=m
CONFIG_REGMAP_SPI_AVMM=m

CONFIG_REGMAP_I2C=y is the one this tutorial needs. These symbols are not set directly in menuconfig; they are selected automatically by the drivers that use them, which is why a kernel with no regmap users will not have them at all.

Step 2: Describe the device in a regmap_config

The configuration is where the device stops being a pile of bus transfers and becomes a register map. Assume an 8-bit sensor with an ID register at 0x00, a control register at 0x01, and status and data registers at 0x02 and 0x03.

static bool sensor_volatile_reg(struct device *dev, unsigned int reg)
{
        return reg == SENSOR_REG_STATUS || reg == SENSOR_REG_DATA;
}

static const struct regmap_config sensor_regmap_config = {
        .reg_bits     = 8,
        .val_bits     = 8,
        .max_register = 0x1f,
        .volatile_reg = sensor_volatile_reg,
        .cache_type   = REGCACHE_MAPLE,
};

The fields you will set most often are:

  • reg_bits — number of bits in a register address.
  • val_bits — number of bits in a register value.
  • max_register — the highest valid register; used for bounds checks and by debugfs.
  • reg_stride — the address step between registers, when they are not contiguous by one.
  • writeable_reg, readable_reg, volatile_reg, precious_reg — callbacks, or equivalent range tables, that classify each register.
  • cache_type — which register cache to use, if any.

Marking status and data registers volatile is not optional bookkeeping. A volatile register is one whose value can change without the driver writing it, so regmap must always go to the hardware for it. Get this wrong and the driver reads a stale cached value forever.

Step 3: Create the regmap in probe

One call creates the map and ties its lifetime to the device. Here is the complete driver — save it as tvsensor.c:

// SPDX-License-Identifier: GPL-2.0
#include <linux/bits.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>

#define SENSOR_REG_ID      0x00
#define SENSOR_REG_CTRL    0x01
#define SENSOR_REG_STATUS  0x02
#define SENSOR_REG_DATA    0x03

#define SENSOR_CTRL_ENABLE BIT(0)

struct sensor_data {
        struct regmap *map;
};

static bool sensor_volatile_reg(struct device *dev, unsigned int reg)
{
        return reg == SENSOR_REG_STATUS || reg == SENSOR_REG_DATA;
}

static const struct regmap_config sensor_regmap_config = {
        .reg_bits     = 8,
        .val_bits     = 8,
        .max_register = 0x1f,
        .volatile_reg = sensor_volatile_reg,
        .cache_type   = REGCACHE_MAPLE,
};

static int sensor_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev;
        struct sensor_data *sensor;
        unsigned int id;
        int ret;

        sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
        if (!sensor)
                return -ENOMEM;

        sensor->map = devm_regmap_init_i2c(client, &sensor_regmap_config);
        if (IS_ERR(sensor->map))
                return dev_err_probe(dev, PTR_ERR(sensor->map),
                                     "regmap init failed\n");

        ret = regmap_read(sensor->map, SENSOR_REG_ID, &id);
        if (ret)
                return dev_err_probe(dev, ret, "cannot read ID register\n");

        ret = regmap_update_bits(sensor->map, SENSOR_REG_CTRL,
                                 SENSOR_CTRL_ENABLE, SENSOR_CTRL_ENABLE);
        if (ret)
                return ret;

        i2c_set_clientdata(client, sensor);
        dev_info(dev, "sensor id 0x%02x, enabled\n", id);

        return 0;
}

static const struct of_device_id sensor_of_match[] = {
        { .compatible = "techveda,tvsensor" },
        { }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);

static const struct i2c_device_id sensor_id[] = {
        { "tvsensor" },
        { }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);

static struct i2c_driver sensor_driver = {
        .driver = {
                .name           = "tvsensor",
                .of_match_table = sensor_of_match,
        },
        .probe    = sensor_probe,
        .id_table = sensor_id,
};
module_i2c_driver(sensor_driver);

MODULE_DESCRIPTION("regmap tutorial sensor driver");
MODULE_AUTHOR("Raghu Bharadwaj");
MODULE_LICENSE("GPL");

The devm_ prefix on devm_regmap_init_i2c() ties the map’s lifetime to the device, so it is freed automatically and the driver needs no teardown path. To move this driver to SPI, change that one call to devm_regmap_init_spi(); for a memory-mapped block, devm_regmap_init_mmio(). Nothing else in the file changes.

Step 4: Build and load the module

Save this as Makefile in the same directory. The indented lines must begin with a tab, not spaces.

obj-m += tvsensor.o

KDIR ?= /lib/modules/$(shell uname -r)/build

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
raghu@techveda.org:~$ make
raghu@techveda.org:~$ modinfo -F alias ./tvsensor.ko
of:N*T*Ctechveda,tvsensor
of:N*T*Ctechveda,tvsensorC*
i2c:tvsensor
raghu@techveda.org:~$ sudo insmod ./tvsensor.ko

Those aliases are what let the module load on demand: scripts/mod/file2alias.c turns each MODULE_DEVICE_TABLE entry into them at build time. The device tree entry produces two, the I2C entry one. Leave MODULE_DEVICE_TABLE out and the driver still works when loaded by hand, but nothing will load it automatically.

You can also check what the module links against with modinfo -F depends. Whether regmap-i2c shows up there depends on your kernel config: with CONFIG_REGMAP_I2C=m the back-end is a separate module and appears as a dependency, while with CONFIG_REGMAP_I2C=y it is built into the kernel and no dependency is listed. Both are fine.

The driver now loads but stays idle. Loading a driver does not create a device: probe runs only when a matching I2C device appears — either a device tree node with compatible = "techveda,tvsensor", or a device instantiated by hand through the I2C bus’s new_device interface.

Step 5: Read, write, and change bits

With the map created, every access is one call and none of them mention I2C:

unsigned int val;

regmap_read(map, SENSOR_REG_STATUS, &val);
regmap_write(map, SENSOR_REG_CTRL, 0x01);
regmap_update_bits(map, SENSOR_REG_CTRL, SENSOR_CTRL_ENABLE, 0);

regmap_update_bits() is the one with a behaviour worth memorising. It is a read-modify-write: regmap reads the current value, computes new = (old & ~mask) | (val & mask), and writes the register only if the new value differs from the old one. That avoids redundant bus traffic, and for registers where a write has a side effect it avoids triggering that side effect when nothing changed. When the write must always happen, call regmap_write_bits() instead.

For several registers at once, regmap_bulk_read() and regmap_bulk_write() move a contiguous run, and regmap_multi_reg_write() applies a list of address-value pairs, which suits an initialization sequence. If you find yourself shifting and masking the same bits repeatedly, declare a named field with the REG_FIELD() macro, allocate it with devm_regmap_field_alloc(), and use regmap_field_read() and regmap_field_write().

Step 6: Let the cache handle suspend and resume

Setting cache_type in Step 2 already did most of the work: regmap now keeps a shadow copy of every non-volatile register, serves reads from it, and updates it on every write. The current cache types are:

  • REGCACHE_NONE — no cache. This is the default when cache_type is left unset.
  • REGCACHE_MAPLE — a maple-tree-backed cache; the recommended choice for new drivers.
  • REGCACHE_FLAT — a plain array. It never allocates at runtime, which suits code paths that must not sleep or fail on allocation, at the cost of memory for sparse maps.
  • REGCACHE_RBTREE — the older red-black-tree cache, now considered legacy.

The payoff is a power-management path you do not have to hand-write. In suspend, mark the shadow dirty because the hardware is about to lose its state; in resume, sync it back:

static int sensor_suspend(struct device *dev)
{
        struct sensor_data *sensor = dev_get_drvdata(dev);

        regcache_cache_only(sensor->map, true);
        regcache_mark_dirty(sensor->map);

        return 0;
}

static int sensor_resume(struct device *dev)
{
        struct sensor_data *sensor = dev_get_drvdata(dev);

        regcache_cache_only(sensor->map, false);

        return regcache_sync(sensor->map);
}

regcache_cache_only() tells regmap that the hardware is unreachable, so accesses during the window are served from the shadow instead of failing. regcache_sync() then writes every dirty non-volatile register back. The device is restored without the driver keeping its own table of values. These helpers live in drivers/base/regmap/regcache.c.

Step 7: Inspect live registers through debugfs

When CONFIG_DEBUG_FS is enabled, regmap exposes every map it has created. This is the fastest way to confirm the driver is writing what you think it is:

raghu@techveda.org:~$ sudo ls /sys/kernel/debug/regmap/
1-0068   spi0.0

raghu@techveda.org:~$ sudo cat /sys/kernel/debug/regmap/1-0068/registers
00: 3b
01: 00
10: a4
11: 07

Every map the regmap subsystem creates shows up here, with no work from the driver. Directory names follow the device, so 1-0068 is the I2C device at address 0x68 on bus 1. Reads through this file go through the same path as driver reads, so registers marked precious_reg — those that must only ever be touched by the driver — are not shown. Writing registers from debugfs is gated behind a separate build option and is off by default, which keeps the interface safe on production images.

If you are building this kind of driver from the ground up, our Linux device drivers training works through regmap alongside the probe path and the device model.

Key takeaways

  • The regmap subsystem replaces per-driver bus helpers with one struct regmap_config; the same driver body runs over I2C, SPI or MMIO by changing the init call alone.
  • Check CONFIG_REGMAP_I2C and friends before assuming regmap is available; those symbols are selected by drivers, not set by hand.
  • Mark status and data registers volatile, or the cache will serve stale values forever.
  • regmap_update_bits() skips the write when the value does not change; use regmap_write_bits() to force it.
  • regcache_mark_dirty() plus regcache_sync() gives you suspend and resume without a hand-written value table.
  • MODULE_DEVICE_TABLE is what generates the module aliases; without it nothing loads the driver automatically.
  • /sys/kernel/debug/regmap/ dumps live register values for quick verification.
Was this worth your time?

Frequently asked questions

Do I have to use a register cache with regmap?
No. If cache_type is left unset it defaults to REGCACHE_NONE and every access goes to the hardware. A cache is worth adding when the device has many registers the driver writes and re-reads, and when you want suspend and resume support through regcache_sync().

Which cache type should a new driver pick?
Use REGCACHE_MAPLE. It is the recommended default. Choose REGCACHE_FLAT only when the code path must never allocate memory at runtime, and treat REGCACHE_RBTREE as legacy.

Why does my write through regmap_update_bits() sometimes not reach the device?
Because it tracks changes: if the masked bits already hold the requested value, regmap does not issue the write. Mark the register volatile if its state can change outside the driver, or call regmap_write_bits() when the write must always happen.

The module loads but probe never runs. What is wrong?
Loading a driver does not create a device. Probe runs only when a matching I2C device exists, which means a device tree node with compatible = "techveda,tvsensor", or a device instantiated by hand on the I2C bus.

Further reading

RB
Raghu Bharadwaj

Founder, TECH VEDA — 20+ years teaching the Linux kernel, device drivers and embedded systems.

Follow on LinkedIn

Get new posts by email

Kernel, embedded Linux and AI-era engineering — a few sharp reads a month. No spam.

We email occasionally and never share your address.