regmap and Register Access Abstraction

Before regmap, every driver that talked to a chip over Inter-Integrated Circuit (I2C) or Serial Peripheral Interface (SPI) carried its own pile of register-poking boilerplate: build a buffer with the register address and the value, call i2c_transfer() or spi_write(), handle the bus-specific framing, and reinvent any caching or read-modify-write logic by hand. The exact same driver ported to a memory-mapped variant of the chip had to be rewritten around readl()/writel(). regmap (drivers/base/regmap/) is the kernel subsystem that abstracts that away: it presents one bus-agnostic register-access API — regmap_read(), regmap_write(), regmap_update_bits() — backed by interchangeable transport backends for MMIO, I2C, and SPI (and others), plus optional register caching, range validation, locking, and endianness handling done once in the core. The driver describes its chip in a struct regmap_config and from then on never touches a bus primitive. This note covers regmap against Linux 6.12 LTS; the lower-level MMIO machinery it sits on lives in Memory-Mapped IO and ioremap.

Mental Model

Think of regmap as a thin operating-system-style driver layer for “a thing with numbered registers.” The chip’s semantics — register 0x10 is the gain control, bit 3 enables the amplifier — are identical whether the chip is wired to an I2C bus, an SPI bus, or memory-mapped into the SoC’s address space. Only the transport differs. regmap splits the world along exactly that seam: the driver speaks “read register N / write value V to register N” in the universal upper API, and a per-bus backend translates each call into the framing that bus requires.

flowchart TB
  DRV["Driver: regmap_read / regmap_write<br/>regmap_update_bits"] --> CORE["regmap core<br/>(lock + range checks + cache)"]
  CORE -->|"cacheable & cached?"| CACHE["regcache<br/>FLAT / RBTREE / MAPLE"]
  CACHE -->|"hit: answer from cache"| DRV
  CORE -->|"miss / volatile / write-through"| BUS{"struct regmap_bus<br/>reg_read / reg_write"}
  BUS --> I2C["I2C backend<br/>i2c_transfer()"]
  BUS --> SPI["SPI backend<br/>spi_sync()"]
  BUS --> MMIO["MMIO backend<br/>readl / writel"]
  I2C --> CHIP["Device registers"]
  SPI --> CHIP
  MMIO --> CHIP

One API over three transports plus a cache. What it shows: the driver issues bus-agnostic calls into the regmap core; the core consults the register cache for readable/non-volatile registers and otherwise dispatches through struct regmap_bus’s reg_read/reg_write function pointers to whichever backend (I2C, SPI, or MMIO) was selected at initialization. The insight to take: the value regmap adds is the split — the chip-specific knowledge (register widths, which registers are volatile, defaults) lives in one declarative struct regmap_config, and everything below it (bus framing, caching, locking) is shared code the driver no longer writes. Re-targeting a driver from I2C to MMIO is a one-line change of initializer, not a rewrite.

Why It Exists — The Boilerplate It Killed

The motivation, per the kernel’s own documentation, is that regmap is “an abstraction register access mechanism … that mainly targets SPI, I2C, and memory-mapped registers, with APIs in this framework that are bus agnostic and handle the underlying configuration under the hood” (docs.kernel.org/driver-api/regmap). Concretely, consider writing register 0x10 = 0x42 on an I2C chip the old way: allocate a 2-byte buffer {0x10, 0x42}, populate a struct i2c_msg, call i2c_transfer(), check the return, possibly retry. On SPI the same operation needs a different framing (often the register number with a read/write flag bit in the top byte). A read needs a write-then-read transaction with the bus-specific address phase. Every chip driver re-implemented this, and any caching, address validation, or read-modify-write atomicity was hand-rolled per driver — thousands of lines of near-identical, easy-to-get-wrong code across the tree. regmap consolidated all of it into one subsystem. The payoff is visible in commits that converted drivers to regmap: large deletions of bus-handling code, replaced by a regmap_config and direct regmap_* calls.

struct regmap_config — Describing the Chip

A driver configures regmap by filling a struct regmap_config and passing it to an initializer. The mandatory and most-used fields, with their verbatim kerneldoc from include/linux/regmap.h:

  • reg_bits — “Number of bits in a register address, mandatory.”
  • val_bits — “Number of bits in a register value, mandatory.”
  • reg_stride — “The register address stride. Valid register addresses are a multiple of this value. If set to 0, a value of 1 will be used.” (So an MMIO device whose 32-bit registers sit 4 bytes apart uses reg_stride = 4, and the core rejects unaligned register numbers — regmap_read/regmap_write start with if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL;.)
  • max_register — “Optional, specifies the maximum valid register address.” Bounds the address space and sizes some cache types.
  • cache_type — “The actual cache type” (see below).
  • reg_defaults / num_reg_defaults — “Power on reset values for registers (for use with register cache support)” and “Number of elements in reg_defaults.” These seed the cache so the first read of an untouched register returns the documented reset value without a bus transaction.

The four predicate callbacks are what give regmap its safety and caching intelligence. Each returns true/false for a given register number (and each has a table-based alternative, e.g. wr_table, used if the callback pointer is NULL):

  • writeable_reg — “returning true if the register can be written to.” A write to a register this rejects fails rather than silently corrupting a read-only register.
  • readable_reg — “returning true if the register can be read from.”
  • volatile_reg — “returning true if the register value can’t be cached.” This is the most important one for correctness: a status register, a FIFO data port, or an interrupt-flag register changes underneath the driver, so it must never be served from the cache. Marking it volatile forces every regmap_read of it to hit the hardware.
  • precious_reg — “returning true if the register should not be read outside of a call from the driver (e.g., a clear on read interrupt status register).” Reading such a register has a side effect (it clears the interrupt), so regmap must not read it speculatively, e.g. during a cache sync or a debugfs dump.

Other notable fields: fast_io — “Register IO is fast. Use a spinlock instead of a mutex to perform locking” (for MMIO, where access does not sleep); can_sleep — “specifies whether regmap operations can sleep” (I2C/SPI transfers can); read_flag_mask/write_flag_mask — “Mask to be set in the top bytes of the register when doing a read”/write, which is exactly the SPI read/write direction bit; and use_relaxed_mmio — “If set, MMIO R/W operations will not use memory barriers … but drivers should carefully add any explicit memory barriers when they may require them” (the regmap equivalent of choosing readl_relaxed).

Initialization — Picking a Backend

The driver calls one bus-specific initializer; the device-managed (devm_) forms auto-free the regmap on detach (“The regmap will be automatically freed by the device management code”):

struct regmap *devm_regmap_init_i2c(struct i2c_client *i2c,
                                    const struct regmap_config *config);
struct regmap *devm_regmap_init_spi(struct device *dev,
                                    const struct regmap_config *config);
struct regmap *devm_regmap_init_mmio(struct device *dev, void __iomem *regs,
                                     const struct regmap_config *config);

Notice the MMIO form takes a void __iomem *regs — the pointer you already obtained from devm_platform_ioremap_resource. So the MMIO backend is literally a regmap wrapper over readl/writel. Each initializer wires up a struct regmap_bus whose reg_read/reg_write function pointers implement that bus’s transactions, then calls the shared __regmap_init(), which (per regmap.c) allocates and configures the struct regmap, selects the locking strategy, sets up the value/register formatting functions based on reg_bits/val_bits, and calls regcache_init().

The locking choice is automatic and worth understanding: __regmap_init() picks a mutex by default (so regmap_read may sleep, correct for I2C/SPI), but if fast_io is set it uses a spinlock (or a raw spinlock if use_raw_spinlock is set), and if disable_locking is set it installs no-op lock functions (“This regmap is either protected by external means or is guaranteed not to be accessed from multiple threads”). Every public API takes map->lock(map->lock_arg) on entry and map->unlock() on exit, so the read-modify-write of regmap_update_bits() is atomic against concurrent regmap callers without the driver writing a single lock.

The Core Operations

int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
int regmap_update_bits(struct regmap *map, unsigned int reg,
                       unsigned int mask, unsigned int val);  /* wrapper */
  • regmap_read() — “Read a value from a single register … A value of zero will be returned on success, a negative errno will be returned in error cases.” Internally it validates reg_stride alignment, takes the lock, and calls _regmap_read(), which checks the cache first (if the register is cacheable and the cache holds it) and otherwise calls the backend’s reg_read.
  • regmap_write() — “Write a value to a single register.” Same lock + alignment dance, then _regmap_write(), which updates the cache and (unless in cache-only mode) delegates to the backend’s reg_write.
  • regmap_update_bits() — the read-modify-write convenience, a wrapper over regmap_update_bits_base() whose kerneldoc reads “Perform a read/modify/write cycle on a register map.” It reads the current value, applies (old & ~mask) | (val & mask), and writes back only if the value changed. Because the whole sequence runs under map’s lock, it is atomic — replacing the classic three-line racy read; modify; write open-coded in pre-regmap drivers.

Bulk and multi-register variants exist for efficiency: regmap_bulk_read/regmap_bulk_write transfer a contiguous run of registers in one transaction where the bus supports it, and regmap_multi_reg_write() writes a set of {register, value} pairs — “Write multiple registers to the device where the set of register, value pairs are supplied in any order.” It documents an alternative on-bus encoding R1,V1,R2,V2,...,Rn,Vn (versus the normal block form R,V1,V2,...,Vn) for devices that support it.

A realistic usage sketch:

static const struct regmap_config widget_regmap = {
        .reg_bits     = 8,                 /* 8-bit register addresses   */
        .val_bits     = 8,                 /* 8-bit register values      */
        .max_register = WIDGET_REG_MAX,
        .volatile_reg = widget_volatile,   /* status/FIFO regs uncached  */
        .cache_type   = REGCACHE_MAPLE,    /* sparse cache               */
        .reg_defaults = widget_defaults,
        .num_reg_defaults = ARRAY_SIZE(widget_defaults),
};
 
static int widget_i2c_probe(struct i2c_client *i2c)
{
        struct regmap *map;
 
        map = devm_regmap_init_i2c(i2c, &widget_regmap);  /* I2C backend */
        if (IS_ERR(map))
                return PTR_ERR(map);
 
        /* Enable bit 0 of the control reg without disturbing other bits. */
        regmap_update_bits(map, WIDGET_CTRL, BIT(0), BIT(0));
        return 0;
}

To re-target this driver to a memory-mapped variant of the same chip, only the initializer changes — devm_regmap_init_mmio(dev, base, &widget_regmap) with reg_bits/val_bits/reg_stride adjusted to 32/32/4 — and every regmap_update_bits() call is untouched. That is the entire value proposition.

Register Caching — regcache

The cache is regmap’s second big feature. For registers that hold persistent configuration (not volatile status), regmap can serve reads from an in-memory copy and, crucially, replay the whole configuration to hardware after the chip loses power. The cache type is chosen with cache_type; the enum regcache_type (regmap.h) is:

enum regcache_type {
        REGCACHE_NONE,    /* no caching: every access hits the bus      */
        REGCACHE_RBTREE,  /* red-black tree: sparse register maps       */
        REGCACHE_FLAT,    /* flat array: dense, small, fastest lookup   */
        REGCACHE_MAPLE,   /* maple tree: sparse, modern default         */
};

REGCACHE_FLAT allocates one array slot per register up to max_register — O(1) lookup, but wasteful and only sensible for small dense maps. REGCACHE_RBTREE stores only the registers that have been touched in a red-black tree, suited to sparse maps with scattered registers. REGCACHE_MAPLE is the newer alternative built on the kernel’s maple tree (a range-optimized B-tree-like structure), offering good behavior for sparse maps and increasingly the recommended choice over RBTREE.

Uncertain

Verify: that REGCACHE_MAPLE is the recommended/default-preferred sparse cache as of 6.12 (versus RBTREE still being the documented default), and the precise release in which REGCACHE_MAPLE was introduced. Reason: the enum is present in the 6.12 header, but the “which to prefer” guidance and introduction release were not pinned to a primary source in this pass. To resolve: read Documentation/driver-api/regmap source/kerneldoc and the maple-tree regcache commit history at/around v6.12. uncertain

The power-management lifecycle is where caching earns its keep, and it relies on three functions whose verbatim kerneldoc (regcache.c) tells the story:

  • regcache_cache_only(map, true) — “When a register map is marked as cache only writes to the register map API will only update the register cache, they will not cause any hardware changes. This is useful for allowing portions of drivers to act as though the device were functioning as normal when it is disabled for power saving reasons.” So during suspend, the driver flips cache-only mode on, the hardware powers down, and the rest of the driver keeps “writing registers” — those writes accumulate in the cache.
  • regcache_mark_dirty(map) — “Indicate that HW registers were reset to default values … Inform regcache that the device has been powered down or reset, so that on resume, regcache_sync() knows to write out all non-default values stored in the cache.” Without this call, “regcache_sync() will assume that the hardware state still matches the cache state.”
  • regcache_sync(map) — “Sync the register cache with the hardware … Any registers that should not be synced should be marked as volatile.” On resume, this walks the cache and writes every non-default (or all dirty) register back to the freshly powered chip, restoring its configuration in one sweep. It refuses to run on a REGCACHE_NONE map (WARN_ON(map->cache_type == REGCACHE_NONE); return -EINVAL;).

The canonical suspend/resume pair is therefore:

static int widget_suspend(struct device *dev) {
        struct regmap *map = dev_get_regmap(dev, NULL);
        regcache_cache_only(map, true);   /* writes now go to cache only  */
        regcache_mark_dirty(map);         /* HW will lose its state       */
        widget_power_off(dev);            /* cut the rail                 */
        return 0;
}
static int widget_resume(struct device *dev) {
        struct regmap *map = dev_get_regmap(dev, NULL);
        widget_power_on(dev);
        regcache_cache_only(map, false);  /* real HW access again         */
        return regcache_sync(map);        /* replay all config to HW      */
}

Two more controls complete the picture: regcache_cache_bypass(map, true) — “Put a register map into cache bypass mode” — forces accesses straight to hardware ignoring the cache entirely (used by debugfs and forced re-reads), and volatile_reg (in the config) excludes registers from caching in the first place. The interplay is exact: a volatile register is never cached at all; a precious register is never read except by an explicit driver call (so regcache_sync and dumps skip it).

regmap-irq — Memory-Mapped Interrupt Controllers

Many peripherals expose a bank of interrupt status/mask registers and raise a single parent interrupt line; demultiplexing “which sub-interrupt fired” means reading the status register, masking/acking, and dispatching. regmap-irq (drivers/base/regmap/regmap-irq.c) implements this once as a generic irq_chip on top of any regmap. The driver describes the layout in a struct regmap_irq_chip — “Description of a generic regmap irq_chip” — and calls:

int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
                        int irq_base, const struct regmap_irq_chip *chip,
                        struct regmap_irq_chip_data **data);

Its kerneldoc: “Use standard regmap IRQ controller handling … @irq: The IRQ the device uses to signal interrupts … @chip: Configuration for the interrupt controller … @data: Runtime data structure for the controller, allocated on success. Returns 0 on success or an errno on failure.” There is a managed form, devm_regmap_add_irq_chip(), whose data “will be automatically released when the device is unbound.” Because the status/ack/mask reads and writes all go through regmap_read/regmap_write, regmap-irq works unchanged whether the controller is on I2C, SPI, or memory-mapped — the same transport abstraction, now applied to the interrupt path. (Note the precious predicate matters here: clear-on-read status registers must be marked precious so regmap never reads them outside the IRQ handler.)

Failure Modes

  • Caching a volatile register. Forgetting to list a status/FIFO register in volatile_reg means regmap_read returns a stale cached value forever. Classic symptom: a poll loop that never observes the bit it is waiting for.
  • Reading a precious register during sync/dump. If a clear-on-read interrupt register is not marked precious, regcache_sync() or a debugfs register dump reads it and silently clears a pending interrupt. Symptom: lost interrupts after resume or after someone cats the debugfs file.
  • Missing regcache_mark_dirty() across power-down. Resume calls regcache_sync(), which assumes the hardware still matches the cache and writes nothing, leaving the freshly powered chip in its reset state. Symptom: device works before suspend, dead after resume.
  • NULL vs ERR_PTR. devm_regmap_init_* returns an ERR_PTR(); check with IS_ERR(), not if (!map).
  • Wrong reg_stride on MMIO. A 32-bit MMIO device with 4-byte register spacing needs reg_stride = 4 (or register numbers expressed in 32-bit units with appropriate reg_shift); getting it wrong yields -EINVAL on every access or reads of the wrong offsets.
  • fast_io mismatch. Setting fast_io (spinlock, non-sleeping) on an I2C/SPI regmap whose backend must sleep is a bug — the transfer cannot run in atomic context. fast_io is for MMIO.

Alternatives and When to Choose Them

  • Raw writel — when the device is MMIO-only, performance is critical on a hot register path, and you want no caching/locking overhead. regmap’s MMIO backend adds a function-pointer indirection and lock per access; a tight DMA-doorbell path may bypass it.
  • Raw i2c_transfer() / spi_sync() — only for one-off, irregular transactions that do not fit the “numbered registers” model. For anything register-shaped, regmap is strictly less code.
  • regmap with REGCACHE_NONE — when you want the bus abstraction and range validation but the registers are all volatile or caching is pointless.
  • regmap with a cache — the default for configuration-heavy chips (audio codecs, PMICs, sensors) that must restore state across suspend/resume. This is where regmap shines and where hand-rolled drivers most often had bugs.

Production Notes

regmap originated in the Audio for Linux / ASoC world (codec drivers had enormous, error-prone register-cache code) and spread across the tree to power-management ICs (PMICs), sensors, clock and pin controllers, and multi-function devices (MFDs). A typical MFD driver creates one regmap for the parent chip and shares it with all the sub-device drivers via dev_get_regmap(), so the I2C/SPI transport and cache are configured exactly once. The conversion of subsystems to regmap is a recurring theme in kernel changelogs precisely because it deletes large amounts of duplicated bus and cache code while adding correctness (atomic update_bits, validated ranges, debugfs register access at /sys/kernel/debug/regmap/). The use_relaxed_mmio flag and the fast_io spinlock path exist for the same reason the relaxed accessors do: regmap is used in latency-sensitive controller drivers where the default barriers and mutex would be too heavy.

See Also