Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
本章思维导图如下:
一、Regmap API简介
1、什么是Regmap
寄存器设置
Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器, SPI 接口的话使用 spi_write/spi_read等。
基于代码复用的原则, Linux 内核引入了 regmap 模型, regmap 将寄存器访问的共同逻辑抽象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmap API函数。这样的好处就是统一使用 regmap,降低了代码冗余,提高了驱动的可以移植性。
通过 regmap 模型提供的统一接口函数来访问器件的寄存器, SOC 内部的寄存器也可以使用 regmap 接口函数来访问。
种通用的接口来操作硬件寄存器。
什么情况下会使用 regmap:
①、硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读写 SOC 内部的硬件寄存器。
②、提高代码复用性和驱动一致性,简化驱动开发过程。
③、减少底层 I/O 操作次数,提高访问效率。
2、Regmap驱动框架
1)框架结构体
2)结构体
51 struct regmap {
52 union {
53 struct mutex mutex;
54 struct {
55 spinlock_t spinlock;
56 unsigned long spinlock_flags;
57 };
58 };
59 regmap_lock lock;
60 regmap_unlock unlock;
61 void *lock_arg; /* This is passed to lock/unlock functions */
62
63 struct device *dev; /* Device we do I/O on */
64 void *work_buf; /* Scratch buffer used to format I/O */
65 struct regmap_format format; /* Buffer format */
66 const struct regmap_bus *bus;
67 void *bus_context;
68 const char *name;
69
70 bool async;
71 spinlock_t async_lock;
72 wait_queue_head_t async_waitq;
73 struct list_head async_list;
74 struct list_head async_free;
75 int async_ret;
......
89 unsigned int max_register;
/*
第 90~100 行有很多的函数以及 table,这些需
要驱动编写人员根据实际情况选择性的初始化, regmap 的初始化通过结构体 regmap_config 来
完成。
*/
90 bool (*writeable_reg)(struct device *dev, unsigned int reg);
91 bool (*readable_reg)(struct device *dev, unsigned int reg);
92 bool (*volatile_reg)(struct device *dev, unsigned int reg);
93 bool (*precious_reg)(struct device *dev, unsigned int reg);
94 const struct regmap_access_table *wr_table;
95 const struct regmap_access_table *rd_table;
96 const struct regmap_access_table *volatile_table;
97 const struct regmap_access_table *precious_table;
98
99 int (*reg_read)(void *context, unsigned int reg,
unsigned int *val);
100 int (*reg_write)(void *context, unsigned int reg,
unsigned int val);
......
147 struct rb_root range_tree;
148 void *selector_work_buf; /* Scratch buffer used for selector */
149 };
3)regmap_config 结构体
regmap_config 结构体就是用来初始化 regmap 的:
186 struct regmap_config {
187 const char *name;
188
189 int reg_bits;
190 int reg_stride;//第 190 行 reg_stride:寄存器地址步长。
191 int pad_bits;//第 191 行 pad_bits:寄存器和值之间的填充位数。
192 int val_bits;//第 192 行 val_bits:寄存器值位数,必填字段。
193
194 bool (*writeable_reg)(struct device *dev, unsigned int reg);//第 194 行 writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用,
并返回 true。
195 bool (*readable_reg)(struct device *dev, unsigned int reg);//第 195 行 readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并
返回 true。
196 bool (*volatile_reg)(struct device *dev, unsigned int reg);//第 196 行 volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调
用,并返回 true。
197 bool (*precious_reg)(struct device *dev, unsigned int reg);
/*
第 197 行 precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中
断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部
的值。
*/
198 regmap_lock lock;
199 regmap_unlock unlock;
200 void *lock_arg;
201
202 int (*reg_read)(void *context, unsigned int reg, unsigned int//第 202 行 reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行。
*val);
203 int (*reg_write)(void *context, unsigned int reg, unsigned int//第 203 行 reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行
val);
204
205 bool fast_io;//第 205 行 fast_io:快速 I/O,使用 spinlock 替代 mutex 来提升锁性能。
206
207 unsigned int max_register;//第 207 行 max_register:有效的最大寄存器地址,可选。
208 const struct regmap_access_table *wr_table;//第 208 行 wr_table:可写的地址范围,为 regmap_access_table 结构体类型。
209 const struct regmap_access_table *rd_table;
210 const struct regmap_access_table *volatile_table;
211 const struct regmap_access_table *precious_table;
212 const struct reg_default *reg_defaults;//第 212 行 reg_defaults:寄存器模式值,为 reg_default 结构体类型,此结构体有两个成员变
量: reg 和 def, reg 是寄存器地址, def 是默认值。
213 unsigned int num_reg_defaults;
214 enum regcache_type cache_type;
215 const void *reg_defaults_raw;
216 unsigned int num_reg_defaults_raw;//第 216 行 num_reg_defaults:默认寄存器表中的元素个数。
217
218 u8 read_flag_mask;//第 218 行 read_flag_mask:读标志掩码。
219 u8 write_flag_mask;//第 219 行 write_flag_mask:写标志掩码。
220
221 bool use_single_rw;
222 bool can_multi_write;
223
224 enum regmap_endian reg_format_endian;
225 enum regmap_endian val_format_endian;
226
227 const struct regmap_range_cfg *ranges;
228 unsigned int num_ranges;
229 };
3、Regmap操作函数
1)Regmap 申请与初始化
SPI 接口初始化函数为 regmap_init_spi,函数原型如下:
struct regmap * regmap_init_spi(struct spi_device *spi,
const struct regmap_
/*
spi: 需要使用 regmap 的 spi_device。
config: regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将
其地址赋值给此参数。
返回值:申请到的并进过初始化的 regmap。
*/
I2C 接口的 regmap 初始化函数为 regmap_init_i2c,函数原型如下: |
struct regmap * regmap_init_i2c(struct i2c_client *i2c,
const struct regmap_config *config)
/*
i2c: 需要使用 regmap 的 i2c_client。
config: regmap_config 结构体,需要程序编写人员初始化一个 regmap_config 实例,然后将
其地址赋值给此参数。
返回值:申请到的并进过初始化的 regmap。
*/
在退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit 这个函数来释放 regmap,函数原型如下:
void regmap_exit(struct regmap *map)
/*
map: 需要释放的 regmap
返回值:无。
*/
2)regmap 设备访问 API 函数
regmap_read 函数:
int regmap_read(struct regmap *map,
unsigned int reg,
unsigned int *val)
/*
map: 要操作的 regmap。
reg: 要读的寄存器。
val:读到的寄存器值。
返回值: 0,读取成功;其他值,读取失败。
*/
regmap_write 函数:
int regmap_write(struct regmap *map,
unsigned int reg,
unsigned int val)
/*
map: 要操作的 regmap。
reg: 要写的寄存器。
val:要写的寄存器值。
返回值: 0,写成功;其他值,写失败。
*/
regmap_update_bits 函数:修改寄存器指定的 bit。
int regmap_update_bits (struct regmap *map,
unsigned int reg,
unsigned int mask,
unsigned int val,
/*
map: 要操作的 regmap。
reg: 要操作的寄存器。
mask: 掩码,需要更新的位必须在掩码中设置为 1。
val:需要更新的位值。
返回值: 0,写成功;其他值,写失败。
*/
regmap_bulk_read 函数:读取多个寄存器的值。
int regmap_bulk_read(struct regmap *map,
unsigned int reg,
void *val,
size_t val_count)
/*
map: 要操作的 regmap。
reg: 要读取的第一个寄存器。
val: 读取到的数据缓冲区。
val_count:要读取的寄存器数量。
返回值: 0,写成功;其他值,读失败。
*/
多个寄存器写函数 regmap_bulk_write:
int regmap_bulk_write(struct regmap *map,
unsigned int reg,
const void *val,
size_t val_count)
/*
map: 要操作的 regmap。
reg: 要写的第一个寄存器。
val: 要写的寄存器数据缓冲区。
val_count:要写的寄存器数量。
返回值: 0,写成功;其他值,读失败。
*/
4、Regmap掩码设置
使用 spi 接口的时候,读取 icm20608 寄存器的时候地址最高位必须置 1,写内部寄存器的是时候地址最高位要设置为 0。
1 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg,
void *buf, int len)
2 {
3
......
21 txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit7 要置 1 */
22 t->tx_buf = txdata; /* 要发送的数据 */
23 t->rx_buf = rxdata; /* 要读取的数据 */
24 t->len = len+1; /* t->len=发送的长度+读取的长度 */
25 spi_message_init(&m); /* 初始化 spi_message */
26 spi_message_add_tail(t, &m);
27 ret = spi_sync(spi, &m); /* 同步发送 */
......
39 return ret;
40 }
使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,由 regmap 框架自动来完成的.
要想在 spi 总线中使用 regmap 框架,首先要使用 regmap_init_spi 函数用于并申请一个 SPI 总线的 regmap。从第 128 行可以看出regmap_init_spi 函数只是对 regmap_init 的简单封装,因此最终完成 regmap 申请并初始化的是regmap_init 函数。
105 static struct regmap_bus regmap_spi = {
106 .write = regmap_spi_write,
107 .gather_write = regmap_spi_gather_write,
108 .async_write = regmap_spi_async_write,
109 .async_alloc = regmap_spi_async_alloc,
110 .read = regmap_spi_read,
111 .read_flag_mask = 0x80,
112 .reg_format_endian_default = REGMAP_ENDIAN_BIG,
113 .val_format_endian_default = REGMAP_ENDIAN_BIG,
114 };
......
125 struct regmap *regmap_init_spi(struct spi_device *spi,
126 const struct regmap_config *config)
127 {
128 return regmap_init(&spi->dev, ®map_spi, &spi->dev, config);
129 }
regmap_init 函数中找到如下所示内容:
598 if (config->read_flag_mask || config->write_flag_mask) {
599 map->read_flag_mask = config->read_flag_mask;
600 map->write_flag_mask = config->write_flag_mask;
601 } else if (bus) {
602 map->read_flag_mask = bus->read_flag_mask;
603 }
/*
第 598~601 行就是用 regmap_config 中的读写掩码来初始化 regmap_bus 中的掩码。由于
regmap_spi 默认将 read_flag_mask 设置为 0X80,当你所使用的 SPI 设备不需要读掩码,在初始
化 regmap_config 的时候一定要将 read_flag_mask 设置为 0X00。
*/
二、驱动编写
1、修改设备结构体,添加 regmap 和 regmap_config
regmap 框架的核心就是 regmap 和 regmap_config 结构体,我们一般都是在自定义的设备结构体里面添加这两个类型的成员变量:
1 struct icm20608_dev {
2 struct spi_device *spi; /* spi 设备 */
3 dev_t devid; /* 设备号 */
4 struct cdev cdev; /* cdev */
5 struct class *class; /* 类 */
6 struct device *device; /* 设备 */
7 struct device_node *nd; /* 设备节点 */
8 signed int gyro_x_adc; /* 陀螺仪 X 轴原始值 */
9 signed int gyro_y_adc; /* 陀螺仪 Y 轴原始值 */
10 signed int gyro_z_adc; /* 陀螺仪 Z 轴原始值 */
11 signed int accel_x_adc; /* 加速度计 X 轴原始值 */
12 signed int accel_y_adc; /* 加速度计 Y 轴原始值 */
13 signed int accel_z_adc; /* 加速度计 Z 轴原始值 */
14 signed int temp_adc; /* 温度原始值 */
15 struct regmap *regmap;
/*
第 15 行, regmap 指针变量, regmap 我们需要使用 regmap_init_spi 函数来申请和初始化,
所以这里是指针类型
*/
16 struct regmap_config regmap_config;
17 };
2、初始化 regmap
一般在 probe 函数中初始化 regmap:
1 static int icm20608_probe(struct spi_device *spi)
2 {
3 int ret;
4 struct icm20608_dev *icm20608dev;
5 6
/* 分配 icm20608dev 对象的空间 */
7 icm20608dev = devm_kzalloc(&spi->dev, sizeof(*icm20608dev),
GFP_KERNEL);
8 if(!icm20608dev)
9 return -ENOMEM;
10
11 /* 初始化 regmap_config 设置
第 11~14 行, regmap_config 的初始化, icm20608 的寄存器地址长度为 8bit,寄存器值也是
8bit,因此 reg_bits 和 val_bits 都设置为 8。由于 icm20608 通过 SPI 接口读取的时候地址寄存器
最高位要设置为 1,因此 read_flag_mask 设置为 0X80。
*/
12 icm20608dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
13 icm20608dev->regmap_config.val_bits = 8; /* 值长度 8bit */
14 icm20608dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 *//*第 17 行,通过 regmap_init_spi 函数来申请并初始化 SPI 总线的 regmap。*/
15
16 /* 初始化 IIC 接口的 regmap */
17 icm20608dev->regmap = regmap_init_spi(spi,
&icm20608dev->regmap_config);
18 if (IS_ERR(icm20608dev->regmap)) {
19 return PTR_ERR(icm20608dev->regmap);
20 }
21
22 /* 注册字符设备驱动 */
23 /* 1、创建设备号 */
24 ret = alloc_chrdev_region(&icm20608dev->devid, 0, ICM20608_CNT,
ICM20608_NAME);
25 if(ret < 0) {
26 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
ICM20608_NAME, ret);
27 goto del_regmap;
28 }
......
61
62 return 0;
63 destroy_class:
64 device_destroy(icm20608dev->class, icm20608dev->devid);
65 del_cdev:
66 cdev_del(&icm20608dev->cdev);
67 del_unregister:
68 unregister_chrdev_region(icm20608dev->devid, ICM20608_CNT);
69 del_regmap:
70 regmap_exit(icm20608dev->regmap);/*第 70 行,如果要删除 regmap 就使用 regmap_exit 函数。*/
71 return -EIO;
72 }
icm20608_remove 函数:
1 static int icm20608_remove(struct spi_device *spi)
2 {
3 struct icm20608_dev *icm20608dev = spi_get_drvdata(spi);
4
......
12 /* 4、注销类 */
13 class_destroy(icm20608dev->class);
14 /* 5、删除 regmap */
15 regmap_exit(icm20608dev->regmap);/*第 17 行,卸载驱动的时候使用 regmap_exit 删除掉 probe 函数中申请的 regmap。*/
16 return 0;
17 }
3、读写设备内部寄存器
直接使用 regmap_read、 regmap_write 等函数读写icm20608 内部寄存器。
1 /*
2 * @description : 读取 icm20608 指定寄存器值,读取一个寄存器
3 * @param – dev : icm20608 设备
4 * @param – reg : 要读取的寄存器
5 * @return : 读取到的寄存器值
6 */
7 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,
u8 reg)
8 {
9 u8 ret;
10 unsigned int data;
11
12 ret = regmap_read(dev->regmap, reg, &data);
13 return (u8)data;
14 }
15
16 /*
17 * @description : 向 icm20608 指定寄存器写入指定的值,写一个寄存器
18 * @param – dev : icm20608 设备
19 * @param – reg : 要写的寄存器
20 * @param – data : 要写入的值
21 * @return : 无
22 */
23
24 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg,
u8 value)
25 {
26 regmap_write(dev->regmap, reg, value);
27 }
28
29 /*
30 * @description : 读取 ICM20608 的数据,读取原始数据,包括三轴陀螺仪、
31 * : 三轴加速度计和内部温度。
32 * @param - dev : ICM20608 设备
33 * @return : 无。
第 35~49 行, icm20608_readdata 函数用于读取 icm20608 内部陀螺仪、加速度计和温度计
的数据,从 ICM20_ACCEL_XOUT_H 寄存器开始,连续读取 14 个寄存器。这里直接使用
regmap_bulk_read 函数来显示多个寄存器的读取。
34 */
35 void icm20608_readdata(struct icm20608_dev *dev)
36 {
37 u8 ret;
38 unsigned char data[14];
39
40 ret = regmap_bulk_read(dev->regmap, ICM20_ACCEL_XOUT_H, data,
14);
41
42 dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
43 dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
44 dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
45 dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
46 dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
47 dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
48 dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
49 }
采用 regmap API 以后驱动程序精简了很多。具体涉及到 SPI 总线的部分全部由 regmap 来处理了,驱动编写人员不用管,极大的方便了我们的驱动编写。而且驱动的可以执行提高了很多,即使将来更换为 IIC 接口,也只需要更改很少的一部分即可。
三、驱动测试
输入如下命令:
depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动模块
./icm20608App /dev/icm20608 //app 读取内部数据
如果 regmap API 工作正常,那么就会正确的初始化 icm20608,并且读出传感器数据:
IIC 总线的 regmap 框架基本和 SPI 一样,只是需要使用 regmap_init_i2c 来申请并初始化对应的 regmap,同样都是使用 regmap_read 和 regmap_write 来读写 I2C 设备内部寄存器。
本笔记为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,未经允许不得用于商业用途。