I2C with Regmap

Regmap provides a unified API for register access, abstracting the underlying bus (I2C, SPI, MMIO). It also provides caching, range checking, and other features.

Why Regmap?

flowchart TB
    subgraph Without["Without Regmap"]
        D1[Driver]
        I2C1[I2C Functions]
        SPI1[SPI Functions]
        MMIO1[MMIO Functions]
        D1 --> I2C1
        D1 --> SPI1
        D1 --> MMIO1
    end

    subgraph With["With Regmap"]
        D2[Driver]
        RM[Regmap API]
        I2C2[I2C]
        SPI2[SPI]
        MMIO2[MMIO]
        D2 --> RM
        RM --> I2C2
        RM --> SPI2
        RM --> MMIO2
    end

    style Without fill:#8f6d72,stroke:#c62828
    style With fill:#7a8f73,stroke:#2e7d32

Basic Setup

#include <linux/regmap.h>

/* Register map configuration */
static const struct regmap_config my_regmap_config = {
    .reg_bits = 8,           /* Register address size */
    .val_bits = 8,           /* Register value size */
    .max_register = 0xFF,    /* Highest register address */
};

static int my_probe(struct i2c_client *client)
{
    struct regmap *regmap;

    /* Create regmap for I2C */
    regmap = devm_regmap_init_i2c(client, &my_regmap_config);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    /* Now use regmap API */
    regmap_write(regmap, REG_CONFIG, 0x60);
    regmap_read(regmap, REG_STATUS, &value);

    return 0;
}

Configuration Options

static const struct regmap_config my_regmap_config = {
    /* Basic configuration */
    .reg_bits = 8,              /* Bits in register address */
    .val_bits = 16,             /* Bits in register value */
    .max_register = 0x7F,       /* Max register for range check */

    /* Endianness */
    .val_format_endian = REGMAP_ENDIAN_BIG,

    /* Caching */
    .cache_type = REGCACHE_RBTREE,  /* or REGCACHE_FLAT, REGCACHE_NONE */

    /* Register defaults for cache init */
    .reg_defaults = my_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(my_reg_defaults),

    /* Volatile registers (always read from hardware) */
    .volatile_reg = my_volatile_reg,

    /* Readable/writeable checks */
    .readable_reg = my_readable_reg,
    .writeable_reg = my_writeable_reg,

    /* Read/write stride */
    .reg_stride = 1,

    /* Use hardware spinlock */
    .use_hwlock = false,
};

Register Access

Basic Read/Write

unsigned int val;

/* Single register read */
ret = regmap_read(regmap, REG_STATUS, &val);
if (ret)
    return ret;

/* Single register write */
ret = regmap_write(regmap, REG_CONFIG, 0x60);
if (ret)
    return ret;

/* Read-modify-write */
ret = regmap_update_bits(regmap, REG_CONFIG,
                         MASK_ENABLE | MASK_MODE,  /* Mask */
                         MASK_ENABLE);             /* Value */

Bulk Operations

u8 regs[4];

/* Bulk read */
ret = regmap_bulk_read(regmap, REG_DATA, regs, 4);

/* Bulk write */
ret = regmap_bulk_write(regmap, REG_DATA, regs, 4);

/* Write multiple registers with different values */
static const struct reg_sequence init_regs[] = {
    { REG_CONFIG, 0x60 },
    { REG_THRESH, 0x80 },
    { REG_ENABLE, 0x01 },
};
ret = regmap_multi_reg_write(regmap, init_regs, ARRAY_SIZE(init_regs));

Bit Operations

/* Set specific bits */
ret = regmap_set_bits(regmap, REG_CONFIG, BIT(3) | BIT(5));

/* Clear specific bits */
ret = regmap_clear_bits(regmap, REG_CONFIG, BIT(3) | BIT(5));

/* Test bit */
ret = regmap_test_bits(regmap, REG_STATUS, BIT(7));
if (ret > 0)
    /* Bit is set */

/* Update bits with mask */
ret = regmap_update_bits(regmap, REG_CONFIG, 0x0F, new_value & 0x0F);

Cache Configuration

Cache Types

/* No caching - always access hardware */
.cache_type = REGCACHE_NONE,

/* Flat array - good for small, dense register maps */
.cache_type = REGCACHE_FLAT,

/* Red-black tree - good for sparse register maps */
.cache_type = REGCACHE_RBTREE,

/* Maple tree - newer, more efficient for most cases */
.cache_type = REGCACHE_MAPLE,

Register Defaults

static const struct reg_default my_reg_defaults[] = {
    { REG_CONFIG, 0x00 },
    { REG_THRESH_LOW, 0x4B },
    { REG_THRESH_HIGH, 0x50 },
};

static const struct regmap_config my_regmap_config = {
    /* ... */
    .reg_defaults = my_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(my_reg_defaults),
    .cache_type = REGCACHE_RBTREE,
};

Volatile Registers

Volatile registers bypass the cache:

static bool my_volatile_reg(struct device *dev, unsigned int reg)
{
    switch (reg) {
    case REG_STATUS:     /* Status changes without writes */
    case REG_DATA:       /* Data register */
    case REG_IRQ_STATUS: /* Interrupt status */
        return true;
    default:
        return false;
    }
}

static const struct regmap_config my_regmap_config = {
    /* ... */
    .volatile_reg = my_volatile_reg,
};

Cache Sync

/* Sync cache to hardware (e.g., after resume) */
ret = regmap_cache_sync(regmap);

/* Mark cache as dirty (e.g., before suspend) */
regmap_cache_mark_dirty(regmap);

/* Bypass cache temporarily */
regcache_cache_bypass(regmap, true);
regmap_read(regmap, REG_STATUS, &val);  /* Direct hardware access */
regcache_cache_bypass(regmap, false);

Complete Example

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

#define REG_CONFIG      0x00
#define REG_STATUS      0x01
#define REG_DATA_L      0x02
#define REG_DATA_H      0x03
#define REG_THRESH_L    0x04
#define REG_THRESH_H    0x05

struct my_adc {
    struct device *dev;
    struct regmap *regmap;
};

static const struct reg_default my_adc_defaults[] = {
    { REG_CONFIG, 0x00 },
    { REG_THRESH_L, 0x00 },
    { REG_THRESH_H, 0xFF },
};

static bool my_adc_volatile(struct device *dev, unsigned int reg)
{
    return reg == REG_STATUS || reg == REG_DATA_L || reg == REG_DATA_H;
}

static bool my_adc_readable(struct device *dev, unsigned int reg)
{
    return reg <= REG_THRESH_H;
}

static bool my_adc_writeable(struct device *dev, unsigned int reg)
{
    return reg == REG_CONFIG || reg == REG_THRESH_L || reg == REG_THRESH_H;
}

static const struct regmap_config my_adc_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = REG_THRESH_H,
    .cache_type = REGCACHE_RBTREE,
    .reg_defaults = my_adc_defaults,
    .num_reg_defaults = ARRAY_SIZE(my_adc_defaults),
    .volatile_reg = my_adc_volatile,
    .readable_reg = my_adc_readable,
    .writeable_reg = my_adc_writeable,
};

static int my_adc_read_raw(struct iio_dev *indio_dev,
                           struct iio_chan_spec const *chan,
                           int *val, int *val2, long mask)
{
    struct my_adc *adc = iio_priv(indio_dev);
    unsigned int data_l, data_h;
    int ret;

    if (mask != IIO_CHAN_INFO_RAW)
        return -EINVAL;

    /* Read 16-bit data from two registers */
    ret = regmap_read(adc->regmap, REG_DATA_L, &data_l);
    if (ret)
        return ret;

    ret = regmap_read(adc->regmap, REG_DATA_H, &data_h);
    if (ret)
        return ret;

    *val = (data_h << 8) | data_l;
    return IIO_VAL_INT;
}

static const struct iio_info my_adc_info = {
    .read_raw = my_adc_read_raw,
};

static const struct iio_chan_spec my_adc_channels[] = {
    {
        .type = IIO_VOLTAGE,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    },
};

static int my_adc_probe(struct i2c_client *client)
{
    struct my_adc *adc;
    struct iio_dev *indio_dev;
    int ret;

    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adc));
    if (!indio_dev)
        return -ENOMEM;

    adc = iio_priv(indio_dev);
    adc->dev = &client->dev;

    /* Initialize regmap */
    adc->regmap = devm_regmap_init_i2c(client, &my_adc_regmap_config);
    if (IS_ERR(adc->regmap))
        return dev_err_probe(&client->dev, PTR_ERR(adc->regmap),
                             "Failed to init regmap\n");

    /* Configure device using regmap */
    ret = regmap_write(adc->regmap, REG_CONFIG, 0x80);  /* Enable */
    if (ret)
        return ret;

    /* Setup IIO device */
    indio_dev->name = "my-adc";
    indio_dev->info = &my_adc_info;
    indio_dev->channels = my_adc_channels;
    indio_dev->num_channels = ARRAY_SIZE(my_adc_channels);

    return devm_iio_device_register(&client->dev, indio_dev);
}

static const struct of_device_id my_adc_of_match[] = {
    { .compatible = "vendor,my-adc" },
    { }
};
MODULE_DEVICE_TABLE(of, my_adc_of_match);

static struct i2c_driver my_adc_driver = {
    .driver = {
        .name = "my-adc",
        .of_match_table = my_adc_of_match,
    },
    .probe = my_adc_probe,
};
module_i2c_driver(my_adc_driver);

MODULE_LICENSE("GPL");

Regmap for Different Buses

/* I2C */
regmap = devm_regmap_init_i2c(client, &config);

/* SPI */
regmap = devm_regmap_init_spi(spi, &config);

/* Memory-mapped I/O */
regmap = devm_regmap_init_mmio(&pdev->dev, base, &config);

/* SPMI (System Power Management Interface) */
regmap = devm_regmap_init_spmi_base(sdev, &config);

Summary

  • Regmap abstracts bus-specific register access
  • Provides caching, range checking, and locking
  • Use devm_regmap_init_i2c() for I2C devices
  • Configure volatility for status/data registers
  • Use regmap_update_bits() for read-modify-write
  • Cache sync helps with power management

Next

Learn about the SPI subsystem.


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.