正点原子imx6ull-mini-Linux驱动之Regmap API 实验

我们在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关 的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质 上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入 了 Regmap 子系统,本章我们就来学习一下如何使用 RegmapAPI 函数来读写 I2C/SPI 设备寄存 器

1:Regmap API 简介

1.1:什么是 Regmap

Linux 下大部分设备的驱动开发都是操作其内部寄存器,比如 I2C/SPI 设备的本质都是一样 的,通过 I2C/SPI 接口读写芯片内部寄存器。芯片内部寄存器也是同样的道理,比如 I.MX6ULL 的 PWM、定时器等外设初始化,最终都是要落到寄存器的设置上。 Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器,SPI 接口的话使用 spi_write/spi_read 等。I2C/SPI 芯片又非常的多,因此 Linux 内核里面就会充斥了大量的 i2c_transfer 这类的冗余 代码,再者,代码的复用性也会降低。比如 icm20608 这个芯片既支持 I2C 接口,也支持 SPI 接 口。假设我们在产品设计阶段一开始将 icm20608 设计为 SPI 接口,但是后面发现 SPI 接口不够 用,或者 SOC 的引脚不够用,我们需要将 icm20608 改为 I2C 接口。这个时候 icm20608 的驱动 就要大改,我们需要将 SPI 接口函数换为 I2C 的,工作量比较大。 基于代码复用的原则,Linux 内核引入了 regmap 模型,regmap 将寄存器访问的共同逻辑抽 象出来,驱动开发人员不需要再去纠结使用 SPI 或者 I2C 接口 API 函数,统一使用 regmapAPI 函数。这样的好处就是统一使用 regmap,降低了代码冗余,提高了驱动的可以移植性。regmap 模型的重点在于: 通过 regmap 模型提供的统一接口函数来访问器件的寄存器,SOC 内部的寄存器也可以使 用 regmap 接口函数来访问。 regmap 是 Linux 内核为了减少慢速 I/O 在驱动上的冗余开销,提供了一种通用的接口来操 作硬件寄存器。另外,regmap 在驱动和硬件之间添加了 cache,降低了低速 I/O 的操作次数,提 高了访问效率,缺点是实时性会降低。 什么情况下会使用 regmap:

①、硬件寄存器操作,比如选用通过 I2C/SPI 接口来读写设备的内部寄存器,或者需要读 写 SOC 内部的硬件寄存器。

②、提高代码复用性和驱动一致性,简化驱动开发过程。

③、减少底层 I/O 操作次数,提高访问效率。 本章教程我们就来重点学习一下如何将《第六十二章 Linux SPI 驱动实验》中编写的 SPI 接口的 icm20608 驱动改为使用 regmap API。

1.2:Regmap 驱动框架

1.2.1:regmap 框架结构

regmap 驱动框架如下图所示:

 regmap 框架分为三层:

①、底层物理总线:regmap 就是对不同的物理总线进行封装,目前 regmap 支持的物理总 线有 i2c、i3c、spi、mmio、sccb、sdw、slimbus、irq、spmi 和 w1

②、regmap 核心层,用于实现 regmap,我们不用关心具体实现。

③、regmapAPI 抽象层,regmap 向驱动编写人员提供的 API 接口,驱动编写人员使用这些 API 接口来操作具体的芯片设备,也是驱动编写人员重点要掌握的。

2、regmap 结构体 Linux 内 核 将 regmap 框 架 抽 象 为 regmap 结 构 体 , 这 个 结 构 体 定 义 在 文 件 drivers/base/regmap/internal.h 中,结构体内容如下(有缩减):

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

要使用 regmap,肯定要先给驱动分配一个具体的 regmap 结构体实例,一会讲解如何分配 regmap 实例。大家可以看到示例代码 74.1.2.1 中第 90~100 行有很多的函数以及 table,这些需 要驱动编写人员根据实际情况选择性的初始化,regmap 的初始化通过结构体 regmap_config 来 完成。

1.2.2:regmap_config 结构体

顾名思义,regmap_config 结构体就是用来初始化 regmap 的,这个结构体也定义在 include/linux/regmap.h 文件中,结构体内容如下:

186 struct regmap_config {
187 const char *name;
188
189 int reg_bits;
190 int reg_stride;
191 int pad_bits;
192 int val_bits;
193
194 bool (*writeable_reg)(struct device *dev, unsigned int reg);
195 bool (*readable_reg)(struct device *dev, unsigned int reg);
196 bool (*volatile_reg)(struct device *dev, unsigned int reg);
197 bool (*precious_reg)(struct device *dev, unsigned int 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
*val);
203 int (*reg_write)(void *context, unsigned int reg, unsigned int
val);
204
205 bool fast_io;
206
207 unsigned int max_register;
208 const struct regmap_access_table *wr_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;
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;
217
218 u8 read_flag_mask;
219 u8 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 };

Linux 内核里面已经对 regmap_config 各个成员变量进行了详细的讲解,这里我们只看一些 比较重要的:

第 187 行 name:名字。

第 189 行 reg_bits:寄存器地址位数,必填字段。

第 190 行 reg_stride:寄存器地址步长。

第 191 行 pad_bits:寄存器和值之间的填充位数。

第 192 行 val_bits:寄存器值位数,必填字段。

第 194 行 writeable_reg:可选的可写回调函数,寄存器可写的话此回调函数就会被调用, 并返回 true。

第 195 行 readable_reg:可选的可读回调函数,寄存器可读的话此回调函数就会被调用,并 返回 true。

第 196 行 volatile_reg:可选的回调函数,当寄存器值不能缓存的时候此回调函数就会被调 用,并返回 true。

第 197 行 precious_reg:当寄存器值不能被读出来的时候此回调函数会被调用,比如很多中 断状态寄存器读清零,读这些寄存器就可以清除中断标志位,但是并没有读出这些寄存器内部 的值。

第 202 行 reg_read:可选的读操作回调函数,所有读寄存器的操作此回调函数就会执行。

第 203 行 reg_write:可选的写操作回调函数,所有写寄存器的操作此回调函数就会执行。

第 205 行 fast_io:快速 I/O,使用 spinlock 替代 mutex 来提升锁性能。

第 207 行 max_register:有效的最大寄存器地址,可选。

第 208 行 wr_table:可写的地址范围,为 regmap_access_table 结构体类型。后面的 rd_table、 volatile_table、precious_table、wr_noinc_table 和 rd_noinc_table 同理。

第 212 行 reg_defaults:寄存器模式值,为 reg_default 结构体类型,此结构体有两个成员变 量:reg 和 def,reg 是寄存器地址,def 是默认值。

第 216 行 num_reg_defaults:默认寄存器表中的元素个数。

第 218 行 read_flag_mask:读标志掩码。

第 219 行 write_flag_mask:写标志掩码。 关于 regmap_config 结构体成员变量就介绍这些,其他没有介绍的自行查阅 Linux 内核中的 相关描述。

1.3:Regmap 操作函数

1.3.1:Regmap 申请与初始化

前面说了,regmap 支持多种物理总线,比如 I2C 和 SPI,我们需要根据所使用的接口来选 择合适的 regmap 初始化函数。Linux 内核提供了针对不同接口的 regmap 初始化函数,SPI 接口 初始化函数为 regmap_init_spi,函数原型如下:

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

函数参数和返回值含义如下:

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 初始化函数,这里就不介绍了,大家直接查阅 Linux 内核即可,基本和 SPI/I2C 的初始化函数相同 在退出驱动的时候需要释放掉申请到的 regmap,不管是什么接口,全部使用 regmap_exit 这 个函数来释放 regmap,函数原型如下:

void regmap_exit(struct regmap *map)

函数参数和返回值含义如下:

map:需要释放的 regmap

返回值:无。

我们一般会在 probe 函数中初始化 regmap_config,然后申请并初始化 regmap。

1.3.2:regmap 设备访问 API 函数

不管是 I2C 还是 SPI 等接口,还是 SOC 内部的寄存器,对于寄存器的操作就两种:读和 写。regmap 提供了最核心的两个读写操作:regmap_read 和 regmap_write。这两个函数分别用来 读/写寄存器,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_read 和 regmap_write 的基础上还衍生出了其他一些 regmap 的 API 函数,首先是 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,写成功;其他值,写失败。 比如要将寄存器的 bit1 和 bit2 置 1,那么 mask 应该设置为 0X00000011,此时 val 的 bit1 和 bit2 应该设置为 1,也就是 0Xxxxxxx11。如果要清除寄存器的 bit4 和 bit7,那么 mask 应该 设置为 0X10010000,val 的 bit4 和 bit7 设置为 0,也就是 0X0xx0xxxx。 接下来看一下 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,写成功;其他值,读失败。

关于 regmap 常用到 API 函数就讲解到这里,还有很多其他功能的 API 函数,大家自行查 阅 Linux 内核即可,内核里面对每个 API 函数都有详细的讲解。

1.4:regmap_config 掩码设置

结构体 regmap_config 里面有三个关于掩码的成员变量:read_flag_mask 和 write_flag_mask, 这二个掩码非常重要,本节我们来学习一下如何使用这三个掩码。我们在学习 icm20608 的时候 讲过了,icm20608 支持 i2c 和 spi 接口,但是当使用 spi 接口的时候,读取 icm20608 寄存器的 时候地址最高位必须置 1,写内部寄存器的是时候地址最高位要设置为 0。因此这里就涉及到对 寄存器地址最高位的操作,在《第六十二章 SPI 驱动实验》中我们在使用 SPI 接口函数读取 icm20608 内部寄存器的时候手动将寄存器地址的最高位置 1,代码如下所示:

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 }

示例代码 74.1.4.1 就是标准的 SPI 驱动,其中第 21 行将寄存器的地址 bit7 置 1,表示这是 一个读操作。 当我们使用 regmap 的时候就不需要手动将寄存器地址的 bit7 置 1,在初始化 regmap_config 的时候直接将 read_flag_mask 设置为 0X80 即可,这样通过 regmap 读取 SPI 内部寄存器的时候 就会将寄存器地址与 read_flag_mask 进行或运算,结果就是将 bit7 置 1,但是整个过程不需要 我们来操作,全部由 regmap 框架来完成的。 同理 write_flag_mask 用法也一样的,只是 write_flag_mask 用于写寄存器操作。 打开 regmap-spi.c 文件,这个文件就是 regmap 的 spi 总线文件,找到如下所示内容:

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, &regmap_spi, &spi->dev, config);
129 }

第 105~114 行初始化了一个 regmap_bus 实例:regmap_spi,我们重点看一下第 111 行中 read_flag_mask 默认为 0X80。注意,这里是将 regmap_bus 的 read_flag_mask 成员变量设置为 0X80。regmap_bus 结构体大家自行查看一下,这里就不讲了。

第 125~129 行为 regmap_init_spi 函数,前面说了要想在 spi 总线中使用 regmap 框架,首先 要使用 regmap_init_spi 函数用于并申请一个 SPI 总线的 regmap

从第 128 行可以看出 regmap_init_spi 函数只是对 regmap_init 的简单封装,因此最终完成 regmap 申请并初始化的是 regmap_init 函数。在 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。 regmap 框架就讲解到这里,接下来学习如何将《第六十二章 Linux SPI 驱动实验》中编写 的 icm20608 驱动改为 regmap 框架。

2:实验程序编写

本实验不需要修改设备树,直接使用《第六十二章 Linux SPI 驱动实验》中的 ICM20608 设 备树。注意!第六十二章以前版本教程里面没有使用 Linux 内核自带的片选信号,新版本教程 (V1.6 及以后版本以后)改为了使用内部片选信号。因此如果你的 ICM20608 设备树节点按照以 前教程编写的,在这里请将其参考新版教程修改。 本实验对 应的例程路径为: 开发 板光盘 -> 01、程序源 码 ->02、 Linux 驱动例 程 ->26_regmap->spi。

2.1:修改设备结构体,添加 regmap 和 regmap_config

regmap 框架的核心就是 regmap 和 regmap_config 结构体,我们一般都是在自定义的设备结 构体里面添加这两个类型的成员变量,所以我们首先在 icm20608_dev 结构体里面添加 regmap 和 regmap_config,修改完成以后的 icm20608_dev 结构体内容如下:

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; 
16 struct regmap_config regmap_config;
17 };

第 15 行,regmap 指针变量,regmap 我们需要使用 regmap_init_spi 函数来申请和初始化, 所以这里是指针类型。 第 16 行,regmap_config 结构体成员变量,从来配置 regmap。

2.2:初始化 regmap

一般在 probe 函数中初始化 regmap,本章节就是 icm20608_probe 函数,初始化内容如下:

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 设置 */
12 icm20608dev->regmap_config.reg_bits = 8; /* 寄存器长度 8bit */
13 icm20608dev->regmap_config.val_bits = 8; /* 值长度 8bit */
14 icm20608dev->regmap_config.read_flag_mask = 0x80; /* 读掩码 */
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);
71 return -EIO;
72 }

第 11~14 行,regmap_config 的初始化,icm20608 的寄存器地址地址长度为 8bit,寄存器值 也是 8bit,因此 reg_bits 和 val_bits 都设置为 8。由于 icm20608 通过 SPI 接口读取的时候地址寄 存器最高位要设置为 1,因此 read_flag_mask 设置为 0X80。

第 17 行,通过 regmap_init_spi 函数来申请并初始化 SPI 总线的 regmap。

第 70 行,如果要删除 regmap 就使用 regmap_exit 函数。 同理,在 remove 函数中就要删除 probe 里面申请的 regmap,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);
16 return 0;
17 }

第 17 行,卸载驱动的时候使用 regmap_exit 删除掉 probe 函数中申请的 regmap。

2.3:读写设备内部寄存器

regmap 已经设置好了,接下来就是使用 regmap API 函数来读写 icm20608 内部寄存器了。 以前我们使用 spi 驱动框架编写读写函数,现在直接使用 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 : 无。
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 }

第 7~14 行,icm20608_read_onereg 函数用于读取 icm20608 内部单个寄存器,这里直接使 用 regmap_read 函数来完成寄存器读取操作。

第 24~27 行,icm20608_write_onereg 函数用于向 icm20608 指定寄存器写入数据,这里也 直接使用 regmap_write 函数来完成写操作。

第 35~49 行,icm20608_readdata 函数用于读取 icm20608 内部陀螺仪、加速度计和温度计 的数据,从 ICM20_ACCEL_XOUT_H 寄存器开始,连续读取 14 个寄存器。这里直接使用 regmap_bulk_read 函数来显示多个寄存器的读取。 对比《第四十五章 Linux SPI 驱动实验》中的 icm20608 驱动,采用 regmap API 以后驱动 程序精简了很多。具体涉及到 SPI 总线的部分全部由 regmap 来处理了,驱动编写人员不用管, 极大的方便了我们的驱动编写。而且驱动的可以执行提高了很多,即使将来更换为 IIC 接口, 也只需要更改很少的一部分即可。

3:运行测试

测试 APP 直接第四十五章编写的 icm20608App.c 即可。测试方法也和四十五章一样,输入 如下命令:

depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动模块
./icm20608App /dev/icm20608 //app 读取内部数据

如果 regmap API 工作正常,那么就会正确的初始化 icm20608,并且读出传感器数据,结果 和四十五章一样,如图 74.3.1 所示:

 IIC 总线的 regmap 框架基本和 SPI 一样,只是需要使用 regmap_init_i2c 来申请并初始化对 应的 regmap,同样都是使用 regmap_read 和 regmap_write 来读写 I2C 设备内部寄存器。这里我 们也已经将《第六十一章 Linux I2C 驱动实验》中的 ap3216c 驱动改为了 regmap API 接口的, 相应的驱动程序已经放到了开发板光盘中,路径为:本实验对应的例程路径为:开发板光盘-> 01、程序源码->02、Linux 驱动例程->26_regmap->iic。大家自行查阅,这里就不详细详解了。

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值