Regmap Update Bits and Multi Write- Free Linux Device Drivers Course

Regmap Update Bits and Multi Write

Free Linux Kernel Development Course — Regmap API, Lecture 4

Kernel 6.x Ready
Original Driver Code
Beginner Friendly
free linux kernel development course
free embedded systems course
free linux device drivers course
regmap_update_bits linux kernel
regmap_multi_reg_write
In this free linux kernel development course lecture we study regmap_update_bits() and regmap_multi_reg_write(), two of the most used functions in the Regmap API for real device drivers. Both functions save I2C/SPI driver authors from writing repetitive read-modify-write logic by hand, and both are fully compatible with modern Linux kernel 6.x. This lecture continues our free embedded systems course series on the Regmap API and is part of the larger free linux device drivers course.

What You Will Learn

  • How regmap_update_bits() performs an atomic read-modify-write on a single register
  • How to build a correct mask and val pair for bit-level updates
  • How regmap_multi_reg_write() writes several registers in one call using struct reg_sequence
  • How to write an original driver that uses both functions on kernel 6.x
  • Common mistakes, best practices, and performance notes for regmap-based drivers

Prerequisites

  • Completed lddch9_1 (Regmap API basics) and lddch9_2 (Regmap access tables)
  • Comfortable with regmap_read() and regmap_write() from lddch9_3
  • A Linux kernel 6.x build environment (kernel headers installed)
  • Basic knowledge of I2C client drivers (see our I2C driver lecture series)

Quick Recap: regmap_read and regmap_write

Before moving ahead, remember that regmap_read() and regmap_write() operate on a single register at a time, and both consult the cache and the access tables (readable_reg/writeable_reg) you configured earlier in this series. regmap_update_bits() and regmap_multi_reg_write() build on top of this same foundation.

Understanding regmap_update_bits Function

Many hardware registers pack several unrelated settings into one byte. For example, a single control register might hold a power-enable bit, a mode bit, and a gain field, all in the same 8 bits. If you want to change only the enable bit, you must not disturb the other bits. Doing this manually means: read the register, modify the target bits in software, then write the register back — three separate steps that can race with another part of the driver.

regmap_update_bits() wraps this entire read-modify-write cycle into one call:

int regmap_update_bits(struct regmap *map, unsigned int reg,
                        unsigned int mask, unsigned int val);

Only the bit positions set to 1 in mask are touched. The corresponding bits of val supply the new values for those positions; bits of val outside the mask are ignored. Internally, regmap reads the current register value, clears the masked bits, ORs in the new bits from val, and writes the result back only if it actually changed — this avoids an unnecessary bus write when nothing changes.

Mask and Value Example

Goal mask (binary) val (binary)
Set bit 0 and bit 2 to 1 0000 0101 xxxx x1x1
Clear bit 6 0100 0000 x0xx xxxx
Set a 2-bit mode field (bits 3-4) to 0b10 0001 1000 xxx1 0xxx

Practical Example: Toggling Bits with regmap_update_bits

The following original driver, ep_regmap_ubits_demo, models a control register with an enable bit at position 0 and a 2-bit mode field at positions 1-2. A sysfs attribute lets you flip the enable bit without disturbing the mode field, using a single regmap_update_bits() call.

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>

#define EP_CTRL_REG      0x10
#define EP_CTRL_ENABLE   BIT(0)

struct ep_ubits_data {
	struct regmap *regmap;
};

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

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

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

	ret = regmap_update_bits(data->regmap, EP_CTRL_REG,
				  EP_CTRL_ENABLE,
				  enable ? EP_CTRL_ENABLE : 0);
	if (ret)
		return ret;

	return count;
}
static DEVICE_ATTR_WO(enable);

static int ep_ubits_probe(struct i2c_client *client)
{
	struct ep_ubits_data *data;

	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	data->regmap = devm_regmap_init_i2c(client, &ep_ubits_regmap_config);
	if (IS_ERR(data->regmap))
		return PTR_ERR(data->regmap);

	i2c_set_clientdata(client, data);

	return device_create_file(&client->dev, &dev_attr_enable);
}

static void ep_ubits_remove(struct i2c_client *client)
{
	device_remove_file(&client->dev, &dev_attr_enable);
}

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

static struct i2c_driver ep_ubits_driver = {
	.driver = {
		.name = "ep_regmap_ubits_demo",
		.of_match_table = ep_ubits_of_match,
	},
	.probe = ep_ubits_probe,
	.remove = ep_ubits_remove,
};
module_i2c_driver(ep_ubits_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala regmap_update_bits demo driver");

Notice that only the enable bit is masked, so writing echo 1 > /sys/bus/i2c/devices/.../enable never touches the mode field, even though both live in the same register.

Understanding regmap_multi_reg_write Function

Many real sensors and PMICs need a sequence of register writes during initialization — enable oscillator, wait a few microseconds, set gain, wait again, then enable the output. Writing these one by one with regmap_write() plus manual udelay() calls is repetitive and easy to get wrong. regmap_multi_reg_write() solves this with one call:

int regmap_multi_reg_write(struct regmap *map,
                            const struct reg_sequence *regs,
                            int num_regs);

Each entry in the sequence carries three pieces of information: which register to write, what value to write, and how many microseconds to wait after that particular write before moving to the next one. This makes device bring-up sequences self-documenting and removes scattered delay calls from your probe function.

reg_sequence Fields

Field Meaning
reg Register address to write
def Value to write into that register
delay_us Microseconds to wait after this write before the next one (0 if no delay needed)

Practical Example: Sensor Init Sequence with regmap_multi_reg_write

This original driver, ep_sensor_init_demo, brings a fictional sensor out of reset using a four-step register sequence, with a short settling delay after enabling the oscillator.

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>

#define EP_RESET_REG   0x00
#define EP_OSC_REG     0x01
#define EP_GAIN_REG    0x02
#define EP_ENABLE_REG  0x03

static const struct reg_sequence ep_sensor_init_seq[] = {
	{ EP_RESET_REG, 0x01, 0    },
	{ EP_OSC_REG,   0x01, 200  },
	{ EP_GAIN_REG,  0x04, 0    },
	{ EP_ENABLE_REG, 0x01, 0   },
};

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

static int ep_sensor_probe(struct i2c_client *client)
{
	struct regmap *regmap;
	int ret;

	regmap = devm_regmap_init_i2c(client, &ep_sensor_regmap_config);
	if (IS_ERR(regmap))
		return PTR_ERR(regmap);

	ret = regmap_multi_reg_write(regmap, ep_sensor_init_seq,
				      ARRAY_SIZE(ep_sensor_init_seq));
	if (ret) {
		dev_err(&client->dev, "sensor init sequence failed: %d\n", ret);
		return ret;
	}

	dev_info(&client->dev, "ep_sensor_init_demo bring-up complete\n");
	return 0;
}

static const struct of_device_id ep_sensor_of_match[] = {
	{ .compatible = "ep,sensor-init-demo" },
	{ }
};
MODULE_DEVICE_TABLE(of, ep_sensor_of_match);

static struct i2c_driver ep_sensor_driver = {
	.driver = {
		.name = "ep_sensor_init_demo",
		.of_match_table = ep_sensor_of_match,
	},
	.probe = ep_sensor_probe,
};
module_i2c_driver(ep_sensor_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala regmap_multi_reg_write demo driver");

Building and Testing the Driver

Use a standard out-of-tree kernel module Makefile:

obj-m += ep_regmap_ubits_demo.o
KDIR := /lib/modules/$(shell uname -r)/build

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

clean:
	make -C $(KDIR) M=$(PWD) clean

Build and load the module, then check kernel logs:

make
sudo insmod ep_regmap_ubits_demo.ko
dmesg | tail -n 10

Expected output in dmesg after loading (exact device path depends on your bus number):

ep_regmap_ubits_demo 1-0050: ep_regmap_ubits_demo probe complete

Test the sysfs attribute:

echo 1 | sudo tee /sys/bus/i2c/devices/1-0050/enable

For the init-sequence driver, loading it directly produces the bring-up message:

sudo insmod ep_sensor_init_demo.ko
dmesg | tail -n 5
# ep_sensor_init_demo 1-0051: ep_sensor_init_demo bring-up complete

regmap_write vs regmap_update_bits vs regmap_multi_reg_write

Function Use When Registers Touched
regmap_write() You want to overwrite an entire register One, fully replaced
regmap_update_bits() You want to change only specific bits, atomically One, partially replaced
regmap_multi_reg_write() You need an ordered sequence of writes, optionally with delays Many, in order

Common Mistakes

Mistake Consequence Fix
Setting mask bits you did not intend to change Unrelated hardware settings get corrupted Double-check the mask against the register’s bitfield layout
Passing val bits outside the mask expecting them to apply Confusing, silent no-op on those bits Only bits inside mask have any effect; keep val clean
Ignoring delay_us requirements from the datasheet Device may not respond correctly after reset Fill delay_us for every step the datasheet requires
Not checking the return value of regmap_multi_reg_write() Driver continues with a half-initialized device Always check ret and bail out on failure

Best Practices

  • Define bit masks with BIT() or named macros instead of magic numbers
  • Group related init sequences into a single reg_sequence array for readability
  • Prefer regmap_update_bits() over manual read-modify-write for any shared register
  • Keep delay_us values close to what the datasheet specifies, not arbitrary round numbers

Performance Considerations

regmap_update_bits() only issues a write if the computed value actually differs from the cached value, which saves a bus transaction when nothing changes. regmap_multi_reg_write() batches several writes logically, but each write and delay still happens in sequence, so long delay chains during probe can slow down device bring-up — keep delays as short as the datasheet allows.

Security Considerations

Never expose raw regmap_update_bits() or regmap_multi_reg_write() calls directly to userspace input without validating the register and bit ranges first; combine them with your writeable_reg access table from lddch9_2 so userspace cannot target protected registers through a sysfs or ioctl interface.

Summary

  • regmap_update_bits() performs an atomic read-modify-write using a mask/val pair
  • regmap_multi_reg_write() writes an ordered sequence of registers with per-step delays via struct reg_sequence
  • Both functions reduce boilerplate and race conditions compared to manual register access
  • Always validate return values and respect existing access tables

Conclusion

With regmap_update_bits() and regmap_multi_reg_write() you can now handle both single-bit tweaks and full device bring-up sequences cleanly on kernel 6.x, without writing repetitive read-modify-write code by hand. This keeps your driver readable and safe from race conditions, which is exactly the kind of practical skill this free linux kernel development course focuses on. In the next lecture of this free linux device drivers course we look at regmap_bulk_read(), regmap_bulk_write(), and regmap’s built-in caching system.

Frequently Asked Questions

What is the difference between regmap_write and regmap_update_bits?

regmap_write() replaces the entire register value, while regmap_update_bits() changes only the bits selected by the mask and leaves the rest untouched.

Does regmap_update_bits always write to the register?

No. It only writes back to hardware if the new value differs from the current one, saving an unnecessary bus transaction.

Can I use regmap_multi_reg_write without any delay between steps?

Yes, set delay_us to 0 for any step that does not require a wait.

Is regmap_multi_reg_write faster than several regmap_write calls?

The number of bus transactions is the same, but the code is cleaner and delays are handled consistently in one place instead of scattered udelay calls.

Does regmap_update_bits work with SPI devices?

Yes, it works with any bus regmap supports, including I2C and SPI, since regmap abstracts the underlying bus.

What happens if I pass an invalid register to regmap_multi_reg_write?

If the register fails your writeable_reg or max_register checks, the call returns an error and stops the sequence at that point.

Should I check the return value of regmap_update_bits?

Yes, always check it. A non-zero return means the underlying bus write failed and your driver should handle that error.

Continue the Free Linux Kernel Development Course

 

Leave a Reply

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