I2C 协议是嵌入式系统中广泛使用的一类通信协议,主要用于 CPU 和各种外设之间的低速数据通信。Linux kernel 使用 I2C framework 抽象、管理相应的资源,并以各种形式,向各类使用者提供 API。Linux的 I2C 体系结构分为 3 个组成部分,分别是 I2C 核心、I2C 总线驱动和 I2C 设备驱动,其中 I2C 核心与 I2C 总线驱动由芯片厂商完成,我们主要是编写 I2C 设备驱动以及对应的应用 APP。
本驱动包含的驱动框架类型有 MISC 驱动 和 I2C 驱动,MISC 驱动主要为了使用 misc 驱动接口函数(open,read),与应用层对接进行数据传输。I2C 驱动借助 I2C 总线,对 isl29023 设备进行读写寄存器操作。misc 驱动的 read 函数里面调用了对 isl29023 设备的 I2C 读写函数,用户层调用 read 函数时,kernel 层会把从 isl29023 设备读取的亮度值传到用户层。用户层再根据需求进行运算。
在应用层 APP 部分,操作 isl29023 驱动生成在 /dev 目录下的设备节点,使用 open 和 read 等函数与 driver 进行交互,获取 isl29023 亮度传感器的亮度值,并进行计算,把结果显示在终端上,软件框图如下所示:
i.MX8QM kernel 关于 isl29023 部分的 I2C 驱动框架分为三大部分,分别是 dma_subsys、i2c0 和 isl29023,I2C 框架在设备树的根节点是 dma_subsys,I2C0 节点是 dma_subsys 的子节点,主要作用是初始化 I2C0 的总线, 为总线驱动,而 isl29023 是 I2C0 的子节点,表示 isl29023 是挂载在 I2C0 总线下面的一个子设备,可以使用 I2C 总线提供的 API 函数与 isl29023 进行通信,框架图如下所示:
isl29023 接线原理图: 图片来源:取自 NXP 官网 imx8qm 原理图
isl29023 与 i.MX8QM 的连接图:
图片来源:取自 NXP 官网 imx8qm 原理图
isl29023 驱动编写
1、模块入口 / 出口
如下代码, module_init(iic_misc_init) 指示驱动入口函数为 iic_misc_init函数。module_exit(iic_misc_exit) 指示驱动出口函数为 iic_misc_exit 函数。__init 是告知编译器,将变量或函数放在一个特殊的区域,这个区域定义在 vmlinux.lds 中。__init 将函数放在代码段的一个子段“.init.text”(初始化代码段)中,_exit 标记的函数只有对模块才起作用,是指明函数是放在代码段的“.exit.text”中。
在入口函数中使用 i2c_add_driver 进行 i2c 驱动注册,在出口函数中使用 i2c_del_driver 函数进行 i2c 驱动注销。MODULE_LICENSE 声明模块许可证,通常使用 GPL 即可。 文件路径为 drivers/iio/light/i2c_train/i2c_train.c。
static int __init iic_misc_init(void) {
return i2c_add_driver(&misciic_driver); } static void __exit iic_misc_exit(void) {
i2c_del_driver(&misciic_driver); } module_init(iic_misc_init); module_exit(iic_misc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Andrew"); |
2、i2c 驱动框架
2.1 添加/删除 iic 驱动函数
i2c_add_driver(&misciic_driver); //添加驱动 i2c_del_driver(&misciic_driver); //删除驱动 |
2.2 i2c_driver 结构体
添加 I2C 驱动时,当结构体成员 .of_match_table 的 compatible 属性与设备树节点的 compatible 匹配上时,执行 probe 函数,.id_table 未没有设备树时的传统 id 匹配表,现在一般不用。内容如下:
static const struct of_device_id misciic_of_match[] = { {.compatible = "misciic_train"}, {} };
static struct i2c_driver misciic_driver = { .probe = misciic_probe, .remove = misciic_remove, .driver = { .owner = THIS_MODULE, .name = "misciic_train", .of_match_table = misciic_of_match, }, .id_table = misciic_id, }; |
2.3 probe 函数
驱动 probe 函数名为 misciic_probe,主要用于注册 misc 设备和初始化亮度传感器并获取亮度值。根据芯片手册编写 isl29023_write_reg 函数用于配置亮度传感器,编写 isl29023_read_regs 函数用于读取亮度值,函数 misc_register 用于注册 misc 设备,下文具体说明,probe 代码如下所示:
static int misciic_probe(struct i2c_client *client, const struct i2c_device_id *id) { u8 datalsb = 0; u8 datamsb = 0; uint16_t data =0; uint16_t da = 0; misc_register(&iic_miscdev); misciicdev.private_data = client; isl29023_write_reg(&misciicdev); msleep(100); isl29023_read_regs(&misciicdev, ISL29023_DATA_LSB, &datalsb, 1); printk("datalsb = %d\r\n",datalsb); printk("datalsb = 0x%x\r\n",datalsb); isl29023_read_regs(&misciicdev, ISL29023_DATA_MSB, &datamsb, 1); printk("datamsb = %d\r\n",datamsb); printk("datamsb = 0x%x\r\n",datamsb); printk("isl29023 probe is ok\r\n");
data|=datamsb; data<<=8; data &=0xff00; data|=datalsb; da= (1000*data)/65535; printk("da = %d\r\n",da); printk("da = 0x%x\r\n",da);
return 0; } |
2.4 remove 函数
驱动 remove 函数名为 misciic_remove 函数,用于 misc 设备注销,代码如下:
static int misciic_remove(struct i2c_client *client ) { misc_deregister(&iic_miscdev);
return 0; } |
2.5 isl29023_write_reg 函数
调用 i2c_transfer 函数向 I2C 设备发送数组 b 数据。内容如下:
static s32 isl29023_write_reg(struct misciic_dev *dev) { u8 b[2]; struct i2c_msg msg; struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = ISL29018_REG_ADD_COMMAND1; b[1] = 32; msg.addr = client->addr; msg.flags = 0; //0 发送 msg.buf = b; msg.len = 2; // 长度 return i2c_transfer(client->adapter, &msg , 1); } |
2.6 isl29023_read_reg 函数
调用 i2c_transfer 函数向 I2C 设备寄存器地址为reg 读取 val 数据。内容如下:
static int isl29023_read_regs(struct misciic_dev *dev, u8 reg, void *val, int len) { int ret; struct i2c_msg msg[2]; struct i2c_client *client = (struct i2c_client *)dev->private_data;
msg[0].addr = client->addr; msg[0].flags = 0; msg[0].buf = ® //寄存器 msg[0].len = 1; msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; //读数据 msg[1].buf = val; //数据存放 msg[1].len = len; ret = i2c_transfer(client->adapter, msg, 2); if(ret==2){ ret=0; }else{ printk("i2c rd failed=%d reg=%d led=%d\n",ret,reg,len); ret = -EREMOTEIO; } return ret; } |
3、MISC 驱动框架
注册 MISC 驱动主要为了使用 misc 驱动接口函数,与应用层对接进行数据传输。
3.1 驱动出入口
module_init(iic_misc_init); module_exit(iic_misc_exit); |
3.2 注册、注销函数
misc_register(&iic_miscdev); //注册函数 misc_deregister(&iic_miscdev); //注销函数 |
3.3 miscdevice 结构体
static const struct file_operations misciic_fops = { .owner = THIS_MODULE, .open = misciic_open, .write = misciic_write, .read = misciic_read, };
static struct miscdevice iic_miscdev ={ .minor = 144, .name = "misciicdev", .fops = &misciic_fops, }; |
misc 驱动主设备号为 10,次设备号 minor 为 114,设备名为 misciicdev,设备驱动函数在成员变量 .fops 中定义。
4.1 Kconfig
- 增加 Kconfig 文件:
menuconfig I2C_TRAIN bool "I2C Train support" depends on I2C
if I2C_TRAIN config I2C_TRAIN_ISL29023 tristate "I2C_TRAIN_ISL29023 Train block support" default n help This is enable I2C Train support for the leds class endif |
增加上一级 Kconfig 包含,文件路径为 drivers/iio/light/Kconfig,内容如下:
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index a62c7b4..945e38e 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -231,6 +231,9 @@ config SENSORS_ISL29018 in lux, proximity infrared sensing and normal infrared sensing. Data from sensor is accessible via sysfs. + +source "drivers/iio/light/i2c_train/Kconfig" + config SENSORS_ISL29028 tristate "Intersil ISL29028 Concurrent Light and Proximity Sensor" depends on I2C |
4.2 Makefile
- 增加 Makefile 文件
obj-$(CONFIG_I2C_TRAIN_ISL29023) += i2c_train.o |
增加上一级 Makefile 包含,文件路径为 drivers/iio/light/Makefile,内容如下:
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index d10912f..b8ae845 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o obj-$(CONFIG_IQS621_ALS) += iqs621-als.o obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o +obj-$(CONFIG_I2C_TRAIN) += i2c_train/ obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o obj-$(CONFIG_ISL29125) += isl29125.o obj-$(CONFIG_JSA1212) += jsa1212.o |
4.3 设备树
申请名为 iic29023@44 的设备节点,包含 pincrtl 子系统的 IO 配置,配置为 I2C 模式,I2C 从器件地址为 0x44,文件路径为 arch/arm64/boot/dts/freescale/imx8qm-mek.dts,
如下所示:
iic29023@44 { pinctrl-mane = "default"; pinctrl-0 = <&pinctrl_isl29023>; compatible = "misciic_train"; reg = <0x44>; status = "okay"; };
pinctrl_isl29023: isl29023grp { fsl,pins = < IMX8QM_USDHC2_WP_LSIO_GPIO4_IO11 0x00000021 >; }; pinctrl_i2c0: i2c0grp { fsl,pins = < IMX8QM_HDMI_TX0_TS_SCL_DMA_I2C0_SCL 0x06000021 IMX8QM_HDMI_TX0_TS_SDA_DMA_I2C0_SDA 0x06000021 >; }; |
5、APP 编写
使用 open 函数打开设备,然后每隔 1 秒使用 read 函数读取亮度值,分为高八位和第八位,对数据进行处理并打印出来,内容如下所示:
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include "sys/time.h"
int main(int argc, char *argv[]) { char *filename; int fd, ret; unsigned char databuf[2]; unsigned char datalsb, datamsb; unsigned int data, da; filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("file %s open failed! \r\n",argv[1]); return -1; }
while (1) { ret = read(fd,databuf, sizeof(databuf)); if(ret ==0) { datalsb = databuf[0]; datamsb = databuf[1]; data &=0x0000; data |= datamsb; data <<=8; data &=0xff00; data |= datalsb; da = (data*1000)/65535; printf("da =%d\r\n",da); } sleep(1); } close(fd); return 0; } |
6、测试
启动板子,在终端输入命令运行 APP,结果如下所示:
root@imx8qmmek:/i2c_train# ./i2capp /dev/misciicdev
[ 93.679105] datalsb = 0x3f
[ 93.682351] datamsb = 0x55
da =332
[ 94.790936] datalsb = 0x3f
[ 94.794172] datamsb = 0x55
da =332
[ 95.902933] datalsb = 0x37
[ 95.906165] datamsb = 0x55
da =332
[ 97.014950] datalsb = 0x3b
[ 97.018181] datamsb = 0x55
da =332
[ 98.126925] datalsb = 0x3c
[ 98.130170] datamsb = 0x55
da =332
评论