Regmap Init And Read Write API- Free Linux Device Drivers Course

PREV_LEC  |  NEXT_LEC

Regmap Init And Read Write API
Free Linux Kernel Development Course — Regmap API, Part 3
Kernel 6.x Ready
Original Driver Code
Beginner Friendly

regmap_read regmap_write linux kernel
free linux kernel development course
free linux device drivers course
free embedded systems course

This lecture of our free linux kernel development course covers the two things every regmap driver eventually needs: how to actually create the regmap object for your bus type, and the regmap_read regmap_write linux kernel functions used to talk to real hardware once that object exists. We will build an original I2C driver on kernel 6.x that exposes a sysfs attribute backed entirely by regmap_read(), regmap_write(), and regmap_update_bits().

What You Will Learn

  • The difference between manual regmap init/exit and the modern devm-managed approach
  • How regmap_init_i2c() and regmap_init_spi() differ, and why devm variants are preferred today
  • regmap_read(), regmap_write(), and regmap_update_bits() function signatures and behavior
  • How the write path interacts with max_register, access tables, and the register cache
  • Building a full original driver with a working sysfs interface

Prerequisites

  • Completion of the previous two lectures on struct regmap_config and access tables
  • Basic sysfs attribute experience (DEVICE_ATTR) is helpful but not required

Two Ways To Create A Regmap

Every regmap driver needs a struct regmap object before it can read or write anything. There are two families of initialization functions depending on the bus:

Regmap Init Function Families
Bus Manual (needs regmap_exit) Devm-managed (recommended)
I2C regmap_init_i2c() devm_regmap_init_i2c()
SPI regmap_init_spi() devm_regmap_init_spi()

On kernel 6.x, driver authors are strongly encouraged to use the devm_ prefixed variants. A devm-managed regmap is automatically released when the device is unbound, which removes an entire class of bugs where a driver forgets to call regmap_exit() in its remove path or in an error path inside probe. The manual regmap_init_i2c()/regmap_init_spi() functions still exist and are documented in include/linux/regmap.h, but new drivers should default to the devm form unless there is a specific reason the regmap must outlive the device’s own devres cleanup.

Function Signatures

struct regmap *regmap_init_i2c(struct i2c_client *i2c,
                                const struct regmap_config *config);

struct regmap *regmap_init_spi(struct spi_device *spi,
                                const struct regmap_config *config);

void regmap_exit(struct regmap *map);

Both init functions return either a valid pointer on success or an ERR_PTR() encoded error on failure, so every call must be checked with IS_ERR() before use, exactly like any other kernel allocation function that returns a pointer.

The Core Device Access Functions

Once a struct regmap exists, almost all register access in a well-written driver goes through exactly three functions:

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

regmap_write() Step By Step

When a driver calls regmap_write(), regmap performs a series of internal checks before any bus transaction happens:

regmap_write() Internal Flow
  1. Check the register address against max_register — reject with -EIO if out of range
  2. Check writability via writeable_reg callback, or wr_table if no callback is set
  3. If a register cache is enabled (cache_type != REGCACHE_NONE), update the cache entry
  4. Perform the actual write, either through the bus (I2C/SPI) or through a custom reg_write callback if one is set

regmap_read() And The Cache

regmap_read() follows similar logic, but if caching is enabled and the register is not marked volatile, the value can be served directly from the cache instead of generating a new bus transaction. This is one of the biggest practical benefits of regmap: it can dramatically cut down bus traffic for registers that rarely change, while volatile registers (status/interrupt registers) are always fetched fresh from hardware.

regmap_update_bits(): Read-Modify-Write Made Safe

A very common driver pattern is “change only some bits of a register, leave the rest untouched.” Doing this manually requires a read, a modification, and a write, with no protection against another thread touching the same register in between. regmap_update_bits() does this atomically with respect to other regmap callers on the same map, using regmap’s internal lock:

/* Set bit 2 without disturbing any other bit in the register */
regmap_update_bits(map, EP_CTRL_REG, BIT(2), BIT(2));

/* Clear bit 2 */
regmap_update_bits(map, EP_CTRL_REG, BIT(2), 0);

Original Driver Example: ep_regmap_rw_demo

This original example builds a complete kernel 6.x I2C driver exposing a sysfs attribute that reads and writes a control register through regmap.

// ep_regmap_rw_demo.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/sysfs.h>

#define EP_CTRL_REG   0x00
#define EP_ENABLE_BIT BIT(0)

static const struct regmap_config ep_rw_regmap_config = {
    .reg_bits     = 8,
    .val_bits     = 8,
    .max_register = 0x0F,
    .cache_type   = REGCACHE_RBTREE,
};

static ssize_t enable_show(struct device *dev,
                            struct device_attribute *attr, char *buf)
{
    struct regmap *map = dev_get_drvdata(dev);
    unsigned int val;
    int ret;

    ret = regmap_read(map, EP_CTRL_REG, &val);
    if (ret)
        return ret;

    return sysfs_emit(buf, "%d\n", !!(val & EP_ENABLE_BIT));
}

static ssize_t enable_store(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t count)
{
    struct regmap *map = dev_get_drvdata(dev);
    bool enable;
    int ret;

    ret = kstrtobool(buf, &enable);
    if (ret)
        return ret;

    ret = regmap_update_bits(map, EP_CTRL_REG, EP_ENABLE_BIT,
                              enable ? EP_ENABLE_BIT : 0);
    if (ret)
        return ret;

    return count;
}
static DEVICE_ATTR_RW(enable);

static struct attribute *ep_rw_attrs[] = {
    &dev_attr_enable.attr,
    NULL,
};
ATTRIBUTE_GROUPS(ep_rw);

static int ep_regmap_rw_probe(struct i2c_client *client)
{
    struct regmap *map;

    map = devm_regmap_init_i2c(client, &ep_rw_regmap_config);
    if (IS_ERR(map)) {
        dev_err(&client->dev, "regmap init failed: %ld\n", PTR_ERR(map));
        return PTR_ERR(map);
    }

    dev_set_drvdata(&client->dev, map);

    /* Start with the device disabled */
    regmap_write(map, EP_CTRL_REG, 0x00);

    dev_info(&client->dev, "ep_regmap_rw_demo probed successfully\n");
    return 0;
}

static void ep_regmap_rw_remove(struct i2c_client *client)
{
    dev_info(&client->dev, "ep_regmap_rw_demo removed\n");
}

static const struct of_device_id ep_rw_of_match[] = {
    { .compatible = "ep,regmap-rw-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_rw_of_match);

static struct i2c_driver ep_regmap_rw_driver = {
    .driver = {
        .name = "ep_regmap_rw_demo",
        .of_match_table = ep_rw_of_match,
        .dev_groups = ep_rw_groups,
    },
    .probe  = ep_regmap_rw_probe,
    .remove = ep_regmap_rw_remove,
};
module_i2c_driver(ep_regmap_rw_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original regmap read/write/update_bits demo driver");

Device Tree Node

ep_switch@51 {
    compatible = "ep,regmap-rw-demo";
    reg = <0x51>;
};

Build And Test Walkthrough

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_regmap_rw_demo.ko
$ dmesg | tail -n 2
$ cat /sys/bus/i2c/devices/1-0051/enable
$ echo 1 | sudo tee /sys/bus/i2c/devices/1-0051/enable
$ cat /sys/bus/i2c/devices/1-0051/enable

Expected output:

[   88.114552] i2c i2c-1: ep_regmap_rw_demo probed successfully
0
1

The first cat shows 0 because probe wrote 0x00 to the control register. After writing 1 to the sysfs file, regmap_update_bits() sets only bit 0, and the following read confirms the change without disturbing any other bit in the register.

Common Mistakes

Mistake Result
Calling regmap_exit() on a devm-allocated regmap Double-free / use-after-free at unbind
Manual read-modify-write instead of regmap_update_bits() Race condition between concurrent register accesses
Ignoring the return value of regmap_read()/regmap_write() Silent hardware failures go unnoticed
Forgetting IS_ERR() check after devm_regmap_init_i2c() NULL/garbage pointer dereference on init failure

Best Practices

  • Default to devm_regmap_init_i2c()/devm_regmap_init_spi() unless you have a specific reason to manage lifetime manually
  • Always use regmap_update_bits() for read-modify-write instead of separate read and write calls
  • Always check return values — regmap calls can fail if the underlying bus transaction fails
  • Mark truly hardware-driven registers (status/interrupt) as volatile so the cache never serves stale data

Performance Considerations

Enabling a register cache (REGCACHE_RBTREE or REGCACHE_FLAT) can meaningfully reduce bus traffic on chips with many configuration registers that are written once and read often afterward. The tradeoff is a small amount of kernel memory for the cache itself, and the requirement that volatile registers be marked correctly, otherwise a driver may read stale cached data instead of the live hardware value.

Summary / Key Takeaways

  • devm_regmap_init_i2c()/devm_regmap_init_spi() are the modern, preferred way to create a regmap
  • regmap_read(), regmap_write(), and regmap_update_bits() are the three functions almost every regmap driver relies on
  • regmap_update_bits() gives you an atomic, race-free read-modify-write
  • Return values must always be checked, since bus transactions can genuinely fail

Conclusion

With regmap initialization and the three core access functions covered, you now have everything needed to write a complete, modern regmap-based driver from scratch: a properly configured struct regmap_config, correct devm-based initialization, and safe register access through regmap_read(), regmap_write(), and regmap_update_bits(). This closes out the Regmap API chapter of the free Linux kernel development course.

FAQ: Regmap Initialization And Read/Write

Should I use regmap_init_i2c() or devm_regmap_init_i2c()?

Use the devm variant for almost all new drivers. It automatically frees the regmap when the device is unbound, avoiding manual cleanup bugs.

Do I ever need to call regmap_exit() manually?

Only if you used the non-devm regmap_init_i2c()/regmap_init_spi() functions. Devm-managed regmaps must never have regmap_exit() called on them.

Why does regmap_read() sometimes not touch the hardware at all?

If a register cache is enabled and the register is not marked volatile, regmap can return the cached value instead of issuing a new bus transaction.

What does regmap_update_bits() actually protect against?

It prevents a race where two threads perform a read-modify-write on the same register at the same time, which could otherwise cause one thread’s change to be silently overwritten.

Can regmap_write() fail even if the register address is valid?

Yes. The underlying I2C or SPI bus transaction itself can fail (for example if the device is unresponsive), so the return value must always be checked.

Is caching mandatory when using regmap?

No. Setting cache_type to REGCACHE_NONE disables caching entirely, and every read/write goes straight to hardware.

What is the difference between regmap_write() and a custom reg_write callback?

regmap_write() is the driver-facing API you call. The custom reg_write field in struct regmap_config is an internal hook regmap uses instead of the standard I2C/SPI transport, only needed for non-standard buses.

 

Continue The Free Linux Kernel Development Course

You’ve completed the Regmap API chapter with original, kernel 6.x driver code.

PREV_LEC  |  NEXT_LEC

Leave a Reply

Your email address will not be published. Required fields are marked *