i.MX8QM I2C 驱动编写(isl29023)

目标: 调试 NXP i.MX8QM I2C 通信功能,实现对 I2C 器件 isl29023 亮度传感器亮度值的转换与读取,并通过 APP 每隔一秒更新亮度数据在终端显示。

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 = &reg;       //寄存器

    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、Kernel 配置

4.1 Kconfig
  • 增加 Kconfig 文件:
新文件,驱动源码目录下 Kconfig,文件路径为 drivers/iio/light/i2c_train/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 文件
增加新文件,驱动源码目录下 Makefile,文件路径为 drivers/iio/light/i2c_train/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

 

以上便完成了,i.MX8QM isl29023 驱动功能的实现。

参考文献:
《iMX8QM_RM_Rev_E.pdf》
《REN_isl29023_DST_20100127.pdf》                                                                                                         

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论