在编写 I2C 或 SPI 设备驱动时,我们经常需要读写寄存器。为了减少重复代码(如处理锁、缓存、字节序、总线差异),Linux 内核引入了 Regmap 子系统。
Regmap 将 I2C、SPI、MMIO 等底层总线操作抽象出来,提供统一的寄存器读写接口,并支持寄存器缓存(Cache),大大提高了驱动的开发效率和性能。
1. 为什么要用 Regmap?
- 统一接口: 无论是 I2C 还是 SPI 设备,驱动逻辑代码可以使用相同的
regmap_read/write接口。 - 自动缓存: 支持多种缓存策略(如 LZO, RB-Tree),减少慢速总线(I2C/SPI)的访问次数,提高性能。
- 调试方便: 通过 Debugfs (
/sys/kernel/debug/regmap/) 可以直接查看寄存器映射和值。 - 内置锁机制: 自动处理并发访问的互斥锁。
2. 使用步骤
2.1 定义 Regmap 配置
c
static const struct regmap_config my_regmap_config = {
.reg_bits = 8, // 寄存器地址位宽
.val_bits = 8, // 寄存器值位宽
.max_register = 0xFF, // 最大寄存器地址
// 可选配置
.cache_type = REGCACHE_RBTREE, // 使用红黑树缓存
.volatile_reg = my_volatile_reg, // 判断寄存器是否易失(不可缓存)
};1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2.2 初始化 Regmap
在 probe 函数中初始化。
对于 I2C 设备:
c
#include <linux/regmap.h>
struct my_data {
struct regmap *map;
// ...
};
static int my_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct my_data *data;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
// 初始化 regmap
data->map = devm_regmap_init_i2c(client, &my_regmap_config);
if (IS_ERR(data->map))
return PTR_ERR(data->map);
i2c_set_clientdata(client, data);
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
对于 SPI 设备: 只需将 devm_regmap_init_i2c 换成 devm_regmap_init_spi。
2.3 读写寄存器
c
int ret;
unsigned int val;
// 写寄存器
ret = regmap_write(data->map, 0x10, 0xAB);
// 读寄存器
ret = regmap_read(data->map, 0x10, &val);
// 更新位 (Read-Modify-Write)
// 将 0x10 寄存器的 Bit 0 和 Bit 1 置为 1,其他位不变
ret = regmap_update_bits(data->map, 0x10, 0x03, 0x03);1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
3. 高级特性
3.1 volatile_reg
有些寄存器(如状态寄存器、中断标志位)的值会由硬件自动改变,或者读操作有副作用(如读后清除),这些寄存器不能被缓存。需要提供 volatile_reg 回调函数,返回 true 表示该寄存器是易失的。
3.2 range_cfg
对于寄存器地址不连续或有分页(Page)机制的复杂设备,可以使用 regmap_range_cfg 来描述。
4. 总结
Regmap 是现代 Linux 驱动开发的标配。除非设备极其简单(只有一两个寄存器),否则强烈建议使用 Regmap API 来管理 I2C/SPI 设备的寄存器访问。