1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新

第六十一章 Linux I2C驱动实验

I2C是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,在裸机篇已经对I.MX6U的I2C接口做了详细的讲解。本章我们来学习一下如何在Linux下开发I2C接口器件驱动,重点是学习Linux下的I2C驱动框架,按照指定的框架去编写I2C设备驱动。本章同样以I.MX6U-ALPHA开发板上的AP3216C这个三合一环境光传感器为例,通过AP3216C讲解一下如何编写Linux下的I2C设备驱动程序。

61.1 Linux I2C驱动框架简介
回想一下我们在裸机篇中是怎么编写AP3216C驱动的,我们编写了四个文件:bsp_i2c.c、bsp_i2c.h、bsp_ap3216c.c和bsp_ap3216c.h。其中前两个是I.MX6U的IIC接口驱动,后两个文件是AP3216C这个I2C设备驱动文件。相当于有两部分驱动:
①、I2C主机驱动。
②、I2C设备驱动。
对于I2C主机驱动,一旦编写完成就不需要再做修改,其他的I2C设备直接调用主机驱动提供的API函数完成读写操作即可。这个正好符合Linux的驱动分离与分层的思想,因此Linux内核也将I2C驱动分为两部分:
①、I2C总线驱动,I2C总线驱动就是SOC的I2C控制器驱动,也叫做I2C适配器驱动。
②、I2C设备驱动,I2C设备驱动就是针对具体的I2C设备而编写的驱动。
61.1.1 I2C总线驱动
首先来看一下I2C总线,在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用I2C总线即可。I2C总线驱动重点是I2C适配器(也就是SOC的I2C接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapter和i2c_algorithm,Linux内核将SOC的I2C适配器(控制器)抽象成i2c_adapter,i2c_adapter结构体定义在include/linux/i2c.h文件中,结构体内容如下:

示例代码61.1.1.1 i2c_adapter结构体
498 struct i2c_adapter {499     struct module *owner;
500     unsigned int class;       /* classes to allow probing for */
501     const struct i2c_algorithm *algo; /* 总线访问算法 */
502     void *algo_data;
503
504     /* data fields that are valid for all devices   */
505     struct rt_mutex bus_lock;
506
507     int timeout;            /* in jiffies */
508     int retries;
509     struct device dev;      /* the adapter device */
510
511     int nr;
512     char name[48];
513     struct completion dev_released;
514
515     struct mutex userspace_clients_lock;
516     struct list_head userspace_clients;
517
518     struct i2c_bus_recovery_info *bus_recovery_info;
519     const struct i2c_adapter_quirks *quirks;
520 };
第501行,i2c_algorithm类型的指针变量algo,对于一个I2C适配器,肯定要对外提供读写API函数,设备驱动程序可以使用这些API函数来完成读写操作。i2c_algorithm就是I2C适配器与IIC设备进行通信的方法。
i2c_algorithm结构体定义在include/linux/i2c.h文件中,内容如下(删除条件编译):
示例代码61.1.1.2 i2c_algorithm结构体
391 struct i2c_algorithm {......
398     int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
399                int num);
400     int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
401                unsigned short flags, char read_write,
402                u8 command, int size, union i2c_smbus_data *data);
403
404     /* To determine what the adapter supports */
405     u32 (*functionality) (struct i2c_adapter *);
......
411 };
第398行,master_xfer就是I2C适配器的传输函数,可以通过此函数来完成与IIC设备之间的通信。
第400行,smbus_xfer就是SMBUS总线的传输函数。
综上所述,I2C总线驱动,或者说I2C适配器驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数。完成以后通过i2c_add_numbered_adapter或i2c_add_adapter这两个函数向系统注册设置好的i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
这两个函数的区别在于i2c_add_adapter使用动态的总线号,而i2c_add_numbered_adapter使用静态总线号。函数参数和返回值含义如下:
adapter或adap:要添加到Linux内核中的i2c_adapter,也就是I2C适配器。
返回值:0,成功;负值,失败。
如果要删除I2C适配器的话使用i2c_del_adapter函数即可,函数原型如下:
void i2c_del_adapter(struct i2c_adapter * adap)
函数参数和返回值含义如下:
adap:要删除的I2C适配器。
返回值:无。
关于I2C的总线(控制器或适配器)驱动就讲解到这里,一般SOC的I2C总线驱动都是由半导体厂商编写的,比如I.MX6U的I2C适配器驱动NXP已经编写好了,这个不需要用户去编写。因此I2C总线驱动对我们这些SOC使用者来说是被屏蔽掉的,我们只要专注于I2C设备驱动即可。除非你是在半导体公司上班,工作内容就是写I2C适配器驱动。
61.1.2 I2C设备驱动
I2C设备驱动重点关注两个数据结构:i2c_client和i2c_driver,根据总线、设备和驱动模型,I2C总线上一小节已经讲了。还剩下设备和驱动,i2c_client就是描述设备信息的,i2c_driver描述驱动内容,类似于platform_driver。
1、i2c_client结构体
i2c_client结构体定义在include/linux/i2c.h文件中,内容如下:

示例代码61.1.2.1 i2c_client结构体
217 struct i2c_client {218     unsigned short flags;               /* 标志                       */
219     unsigned short addr;                /* 芯片地址,7位,存在低7位  */
......
222     char name[I2C_NAME_SIZE];           /* 名字                   */
223     struct i2c_adapter *adapter;    /* 对应的I2C适配器            */
224     struct device dev;                  /* 设备结构体                */
225     int irq;                            /* 中断                       */
226     struct list_head detected;
......
230 };
一个设备对应一个i2c_client,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client。
2、i2c_driver结构体
i2c_driver类似platform_driver,是我们编写I2C设备驱动重点要处理的内容,i2c_driver结构体定义在include/linux/i2c.h文件中,内容如下:

```c
示例代码61.1.2.2 i2c_driver结构体
161 struct i2c_driver {162     unsigned int class;
163
164     /* Notifies the driver that a new bus has appeared. You should
165      * avoid  using this, it will be removed in a near future.
166      */
167     int (*attach_adapter)(struct i2c_adapter *) __deprecated;
168
169     /* Standard driver model interfaces */
170     int (*probe)(struct i2c_client *, const struct i2c_device_id *);
171     int (*remove)(struct i2c_client *);
172
173     /* driver model interfaces that don't relate to enumeration  */
174     void (*shutdown)(struct i2c_client *);
175
176     /* Alert callback, for example for the SMBus alert protocol.
177      * The format and meaning of the data value depends on the
178      * protocol.For the SMBus alert protocol, there is a single bit
179      * of data passed  as the alert response's low bit ("event
180      flag"). */
181     void (*alert)(struct i2c_client *, unsigned int data);
182
183     /* a ioctl like command that can be used to perform specific
184      * functions with the device.
185      */
186     int (*command)(struct i2c_client *client, unsigned int cmd,
void *arg);
187
188     struct device_driver driver;
189     const struct i2c_device_id *id_table;
190
191     /* Device detection callback for automatic device creation */
192     int (*detect)(struct i2c_client *, struct i2c_board_info *);
193     const unsigned short *address_list;
194     struct list_head clients;
195 };
 第170行,当I2C设备和驱动匹配成功以后probe函数就会执行,和platform驱动一样。第188行,device_driver驱动结构体,如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容(compatible)属性。第189行,id_table是传统的、未使用设备树的设备匹配ID表。对于我们I2C设备驱动编写人来说,重点工作就是构建i2c_driver,构建完成以后需要向Linux内核注册这个i2c_driver。i2c_driver注册函数为int i2c_register_driver,此函数原型如下:
int i2c_register_driver(struct module       *owner, struct i2c_driver   *driver)
函数参数和返回值含义如下:owner:一般为THIS_MODULE。driver:要注册的i2c_driver。返回值:0,成功;负值,失败。另外i2c_add_driver也常常用于注册i2c_driver,i2c_add_driver是一个宏,定义如下:
```c
示例代码61.1.2.3 i2c_add_driver宏
587 #define i2c_add_driver(driver) \
588     i2c_register_driver(THIS_MODULE, driver)
i2c_add_driver就是对i2c_register_driver做了一个简单的封装,只有一个参数,就是要注册的i2c_driver。
注销I2C设备驱动的时候需要将前面注册的i2c_driver从Linux内核中注销掉,需要用到i2c_del_driver函数,此函数原型如下:

void i2c_del_driver(struct i2c_driver *driver)
函数参数和返回值含义如下:
driver:要注销的i2c_driver。
返回值:无。
i2c_driver的注册示例代码如下:

示例代码61.1.2.4 i2c_driver注册流程
1  /* i2c驱动的probe函数 */
2  static int xxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
3  {4       /* 函数具体程序 */
5       return 0;
6  }
7
8  /* i2c驱动的remove函数 */
9  static int xxx_remove(struct i2c_client *client)
10 {11      /* 函数具体程序 */
12      return 0;
13 }
14
15 /* 传统匹配方式ID列表 */
16 static const struct i2c_device_id xxx_id[] = {17      {"xxx", 0},
18      {}
19 };
20
21 /* 设备树匹配列表 */
22 static const struct of_device_id xxx_of_match[] = {23      { .compatible = "xxx" },
24      { /* Sentinel */ }
25 };
26
27 /* i2c驱动结构体 */
28 static struct i2c_driver xxx_driver = {29      .probe = xxx_probe,
30      .remove = xxx_remove,
31      .driver = {32              .owner = THIS_MODULE,
33              .name = "xxx",
34              .of_match_table = xxx_of_match,
35          },
36          .id_table = xxx_id,
37      };
38
39 /* 驱动入口函数 */
40 static int __init xxx_init(void)
41 {42      int ret = 0;
43
44      ret = i2c_add_driver(&xxx_driver);
45      return ret;
46 }
47
48 /* 驱动出口函数 */
49 static void __exit xxx_exit(void)
50 {51      i2c_del_driver(&xxx_driver);
52 }
53
54 module_init(xxx_init);
55 module_exit(xxx_exit);
第16~19行,i2c_device_id,无设备树的时候匹配ID表。
第22~25行,of_device_id,设备树所使用的匹配表。
第28~37行,i2c_driver,当I2C设备和I2C驱动匹配成功以后probe函数就会执行,这些和platform驱动一样,probe函数里面基本就是标准的字符设备驱动那一套了。

61.1.3 I2C设备和驱动匹配过程
I2C设备和驱动的匹配过程是由I2C核心来完成的,drivers/i2c/i2c-core.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数,比如前面讲过的:
1、i2c_adapter注册/注销函数
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2、i2c_driver注册/注销函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
设备和驱动的匹配过程也是由I2C总线完成的,I2C总线的数据结构为i2c_bus_type,定义在drivers/i2c/i2c-core.c文件,i2c_bus_type内容如下:

示例代码61.1.2.5 i2c_bus_type总线
736 struct bus_type i2c_bus_type = {737     .name       = "i2c",
738     .match      = i2c_device_match,
739     .probe      = i2c_device_probe,
740     .remove     = i2c_device_remove,
741     .shutdown   = i2c_device_shutdown,
742 };
.match就是I2C总线的设备和驱动匹配函数,在这里就是i2c_device_match这个函数,此函数内容如下:
示例代码61.1.2.6 i2c_device_match函数
457 static int i2c_device_match(struct device *dev, struct device_driver *drv)
458 {459     struct i2c_client   *client = i2c_verify_client(dev);
460     struct i2c_driver   *driver;
461
462     if (!client)
463         return 0;
464
465     /* Attempt an OF style match */
466     if (of_driver_match_device(dev, drv))
467         return 1;
468
469     /* Then ACPI style match */
470     if (acpi_driver_match_device(dev, drv))
471         return 1;
472
473     driver = to_i2c_driver(drv);
474     /* match on an id table if there is one */
475     if (driver->id_table)
476         return i2c_match_id(driver->id_table, client) != NULL;
477
478     return 0;
479 }
第466行,of_driver_match_device函数用于完成设备树设备和驱动匹配。比较I2C设备节点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示I2C设备和驱动匹配。
第470行,acpi_driver_match_device函数用于ACPI形式的匹配。
第476行,i2c_match_id函数用于传统的、无设备树的I2C设备和驱动匹配过程。比较I2C设备名字和i2c_device_id的name字段是否相等,相等的话就说明I2C设备和驱动匹配。

61.2 I.MX6U的I2C适配器驱动分析
上一小节我们讲解了Linux下的I2C驱动框架,重点分为I2C适配器驱动和I2C设备驱动,其中I2C适配器驱动就是SOC的I2C控制器驱动。I2C设备驱动是需要用户根据不同的I2C设备去编写,而I2C适配器驱动一般都是SOC厂商去编写的,比如NXP就编写好了I.MX6U的I2C适配器驱动。在imx6ull.dtsi文件中找到I.MX6U的I2C1控制器节点,节点内容如下所示:

示例代码61.2.1 I2C1控制器节点
1 i2c1: i2c@021a0000 {2   #address-cells = <1>;
3   #size-cells = <0>;
4   compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
5   reg = <0x021a0000 0x4000>;
6   interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
7   clocks = <&clks IMX6UL_CLK_I2C1>;
8   status = "disabled";
9 };
重点关注i2c1节点的compatible属性值,因为通过compatible属性值可以在Linux源码里面找到对应的驱动文件。这里i2c1节点的compatible属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21-i2c”,在Linux源码中搜索这两个字符串即可找到对应的驱动文件。I.MX6U的I2C适配器驱动驱动文件为drivers/i2c/busses/i2c-imx.c,在此文件中有如下内容:
示例代码61.2.2 i2c-imx.c文件代码段
244 static struct platform_device_id imx_i2c_devtype[] = {245     {246         .name = "imx1-i2c",
247         .driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,
248     }, {249         .name = "imx21-i2c",
250         .driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,
251     }, {252         /* sentinel */
253     }
254 };
255 MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);
256
257 static const struct of_device_id i2c_imx_dt_ids[] = {258     { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
259     { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
260     { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
261     { /* sentinel */ }
262 };
263 MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);
......
1119 static struct platform_driver i2c_imx_driver = {1120    .probe = i2c_imx_probe,
1121    .remove = i2c_imx_remove,
1122    .driver = {1123        .name = DRIVER_NAME,
1124        .owner = THIS_MODULE,
1125        .of_match_table = i2c_imx_dt_ids,
1126        .pm = IMX_I2C_PM,
1127    },
1128    .id_table   = imx_i2c_devtype,
1129 };
1130
1131 static int __init i2c_adap_imx_init(void)
1132 {1133    return platform_driver_register(&i2c_imx_driver);
1134 }
1135 subsys_initcall(i2c_adap_imx_init);
1136
1137 static void __exit i2c_adap_imx_exit(void)
1138 {1139    platform_driver_unregister(&i2c_imx_driver);
1140 }
1141 module_exit(i2c_adap_imx_exit);
从示例代码61.2.2可以看出,I.MX6U的I2C适配器驱动是个标准的platform驱动,由此可以看出,虽然I2C总线为别的设备提供了一种总线驱动框架,但是I2C适配器却是platform驱动。就像你的部门老大是你的领导,你是他的下属,但是放到整个公司,你的部门老大却也是老板的下属。
第259行,“fsl,imx21-i2c”属性值,设备树中 i2c1节点的compatible属性值就是与此匹配上的。因此i2c-imx.c文件就是I.MX6U的I2C适配器驱动文件。
第1120行,当设备和驱动匹配成功以后i2c_imx_probe函数就会执行,i2c_imx_probe函数就会完成I2C适配器初始化工作。
i2c_imx_probe函数内容如下所示(有省略):
示例代码61.2.3 i2c_imx_probe函数代码段
971  static int i2c_imx_probe(struct platform_device *pdev)
972  {973     const struct of_device_id *of_id =
974                    of_match_device(i2c_imx_dt_ids, &pdev->dev);
975     struct imx_i2c_struct *i2c_imx;
976     struct resource *res;
977     struct imxi2c_platform_data *pdata =
dev_get_platdata(&pdev->dev);
978     void __iomem *base;
979     int irq, ret;
980     dma_addr_t phy_addr;
981
982     dev_dbg(&pdev->dev, "<%s>\n", __func__);
983
984     irq = platform_get_irq(pdev, 0);
......
990     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
991     base = devm_ioremap_resource(&pdev->dev, res);
992     if (IS_ERR(base))
993         return PTR_ERR(base);
994
995     phy_addr = (dma_addr_t)res->start;
996     i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx),
GFP_KERNEL);
997     if (!i2c_imx)
998         return -ENOMEM;
999
1000    if (of_id)
1001        i2c_imx->hwdata = of_id->data;
1002    else
1003        i2c_imx->hwdata = (struct imx_i2c_hwdata *)
1004                platform_get_device_id(pdev)->driver_data;
1005
1006    /* Setup i2c_imx driver structure */
1007    strlcpy(i2c_imx->adapter.name, pdev->name,
sizeof(i2c_imx->adapter.name));
1008    i2c_imx->adapter.owner      = THIS_MODULE;
1009    i2c_imx->adapter.algo       = &i2c_imx_algo;
1010    i2c_imx->adapter.dev.parent = &pdev->dev;
1011    i2c_imx->adapter.nr     = pdev->id;
1012    i2c_imx->adapter.dev.of_node    = pdev->dev.of_node;
1013    i2c_imx->base           = base;
1014
1015    /* Get I2C clock */
1016    i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
......
1022    ret = clk_prepare_enable(i2c_imx->clk);
......
1027    /* Request IRQ */
1028    ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
1029                   IRQF_NO_SUSPEND, pdev->name, i2c_imx);
......
1035    /* Init queue */
1036    init_waitqueue_head(&i2c_imx->queue);
1037
1038    /* Set up adapter data */
1039    i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
1040
1041    /* Set up clock divider */
1042    i2c_imx->bitrate = IMX_I2C_BIT_RATE;
1043    ret = of_property_read_u32(pdev->dev.of_node,
1044                   "clock-frequency", &i2c_imx->bitrate);
1045    if (ret < 0 && pdata && pdata->bitrate)
1046        i2c_imx->bitrate = pdata->bitrate;
1047
1048    /* Set up chip registers to defaults */
1049    imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
1050            i2c_imx, IMX_I2C_I2CR);
1051    imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,
IMX_I2C_I2SR);
1052
1053    /* Add I2C adapter */
1054    ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
1055    if (ret < 0) {1056        dev_err(&pdev->dev, "registration failed\n");
1057        goto clk_disable;
1058    }
1059
1060    /* Set up platform driver data */
1061    platform_set_drvdata(pdev, i2c_imx);
1062    clk_disable_unprepare(i2c_imx->clk);
......
1070    /* Init DMA config if supported */
1071    i2c_imx_dma_request(i2c_imx, phy_addr);
1072
1073    return 0;   /* Return OK */
1074
1075 clk_disable:
1076    clk_disable_unprepare(i2c_imx->clk);
1077    return ret;
1078 }
第984行,调用platform_get_irq函数获取中断号。
第990~991行,调用platform_get_resource函数从设备树中获取I2C1控制器寄存器物理基地址,也就是0X021A0000。获取到寄存器基地址以后使用devm_ioremap_resource函数对其进行内存映射,得到可以在Linux内核中使用的虚拟地址。
第996行,NXP使用imx_i2c_struct结构体来表示I.MX系列SOC的I2C控制器,这里使用devm_kzalloc函数来申请内存。
第1008~1013行,imx_i2c_struct结构体要有个叫做adapter的成员变量,adapter就是i2c_adapter,这里初始化i2c_adapter。第1009行设置i2c_adapter的algo成员变量为i2c_imx_algo,也就是设置i2c_algorithm。
第1028~1029行,注册I2C控制器中断,中断服务函数为i2c_imx_isr。
第1042~1044行,设置I2C频率默认为IMX_I2C_BIT_RATE=100KHz,如果设备树节点设置了“clock-frequency”属性的话I2C频率就使用clock-frequency属性值。
第1049~1051行,设置I2C1控制的I2CR和I2SR寄存器。
第1054行,调用i2c_add_numbered_adapter函数向Linux内核注册i2c_adapter。
第1071行,申请DMA,看来I.MX的I2C适配器驱动采用了DMA方式。
i2c_imx_probe函数主要的工作就是一下两点:
①、初始化i2c_adapter,设置i2c_algorithm为i2c_imx_algo,最后向Linux内核注册i2c_adapter。
②、初始化I2C1控制器的相关寄存器。
i2c_imx_algo包含I2C1适配器与I2C设备的通信函数master_xfer,i2c_imx_algo结构体定义如下:
示例代码61.2.4 i2c_imx_algo结构体
966 static struct i2c_algorithm i2c_imx_algo = {967     .master_xfer    = i2c_imx_xfer,
968     .functionality  = i2c_imx_func,
969 };
我们先来看一下. functionality,functionality用于返回此I2C适配器支持什么样的通信协议,在这里functionality就是i2c_imx_func函数,i2c_imx_func函数内容如下:
示例代码61.2.5 i2c_imx_func函数
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL| I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}
重点来看一下i2c_imx_xfer函数,因为最终就是通过此函数来完成与I2C设备通信的,此函数内容如下(有省略):
示例代码61.2.6 i2c_imx_xfer函数
888 static int i2c_imx_xfer(struct i2c_adapter *adapter,
889                         struct i2c_msg *msgs, int num)
890 {891     unsigned int i, temp;
892     int result;
893     bool is_lastmsg = false;
894     struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
895
896     dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
897
898     /* Start I2C transfer */
899     result = i2c_imx_start(i2c_imx);
900     if (result)
901         goto fail0;
902
903     /* read/write data */
904     for (i = 0; i < num; i++) {905         if (i == num - 1)
906             is_lastmsg = true;
907
908         if (i) {909             dev_dbg(&i2c_imx->adapter.dev,
910                 "<%s> repeated start\n", __func__);
911             temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
912             temp |= I2CR_RSTA;
913             imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
914             result =  i2c_imx_bus_busy(i2c_imx, 1);
915             if (result)
916                 goto fail0;
917         }
918         dev_dbg(&i2c_imx->adapter.dev,
919             "<%s> transfer message: %d\n", __func__, i);
920         /* write/read data */
......
938         if (msgs[i].flags & I2C_M_RD)
939             result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
940         else {941             if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
942                 result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
943             else
944                 result = i2c_imx_write(i2c_imx, &msgs[i]);
945         }
946         if (result)
947             goto fail0;
948     }
949
950 fail0:
951     /* Stop I2C transfer */
952     i2c_imx_stop(i2c_imx);
953
954     dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
955         (result < 0) ? "error" : "success msg",
956             (result < 0) ? result : num);
957     return (result < 0) ? result : num;
958 }
第899行,调用i2c_imx_start函数开启I2C通信。
第939行,如果是从I2C设备读数据的话就调用i2c_imx_read函数。
第941~945行,向I2C设备写数据,如果要用DMA的话就使用i2c_imx_dma_write函数来完成写数据。如果不使用DMA的话就使用i2c_imx_write函数完成写数据。
第952行,I2C通信完成以后调用i2c_imx_stop函数停止I2C通信。
i2c_imx_start、i2c_imx_read、i2c_imx_write和i2c_imx_stop这些函数就是I2C寄存器的具体操作函数,函数内容基本和我们裸机篇中讲的I2C驱动一样,这里我们就不详细的分析了,大家可以对照着第二十六章实验自行分析。

61.3 I2C设备驱动编写流程
I2C适配器驱动SOC厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动,本小节我们就来学习一下I2C设备驱动的详细编写流程。
61.3.1 I2C设备信息描述
1、未使用设备树的时候
首先肯定要描述I2C设备节点信息,先来看一下没有使用设备树的时候是如何在BSP里面描述I2C设备信息的,在未使用设备树的时候需要在BSP里面使用i2c_board_info结构体来描述一个具体的I2C设备。i2c_board_info结构体如下:

示例代码61.3.1.1 i2c_board_info结构体
295 struct i2c_board_info {296     char        type[I2C_NAME_SIZE];    /* I2C设备名字  */
297     unsigned short  flags;                  /* 标志           */
298     unsigned short  addr;                   /* I2C器件地址  */
299     void        *platform_data;
300     struct dev_archdata *archdata;
301     struct device_node *of_node;
302     struct fwnode_handle *fwnode;
303     int     irq;
304 };
type和addr这两个成员变量是必须要设置的,一个是I2C设备的名字,一个是I2C设备的器件地址。打开arch/arm/mach-imx/mach-mx27_3ds.c文件,此文件中关于OV2640的I2C设备信息描述如下:

示例代码61.3.1.2 OV2640的I2C设备信息
392 static struct i2c_board_info mx27_3ds_i2c_camera = {
393 I2C_BOARD_INFO(“ov2640”, 0x30),
394 };
示例代码61.3.1.2中使用I2C_BOARD_INFO来完成mx27_3ds_i2c_camera的初始化工作,I2C_BOARD_INFO是一个宏,定义如下:

示例代码61.3.1.3 I2C_BOARD_INFO宏
316 #define I2C_BOARD_INFO(dev_type, dev_addr) \
317     .type = dev_type, .addr = (dev_addr)
可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的type和addr这两个成员变量,因此示例代码61.3.1.2的主要工作就是设置I2C设备名字为ov2640,ov2640的器件地址为0X30。
大家可以在Linux源码里面全局搜索i2c_board_info,会找到大量以i2c_board_info定义的I2C设备信息,这些就是未使用设备树的时候I2C设备的描述方式,当采用了设备树以后就不会再使用i2c_board_info来描述I2C设备了。
2、使用设备树的时候
使用设备树的时候I2C设备信息通过创建相应的节点就行了,比如NXP官方的EVK开发板在I2C1上接了mag3110这个磁力计芯片,因此必须在i2c1节点下创建mag3110子节点,然后在这个子节点内描述mag3110这个芯片的相关信息。打开imx6ull-14x14-evk.dts这个设备树文件,然后找到如下内容:
示例代码61.3.1.4 mag3110子节点
1  &i2c1 {2      clock-frequency = <100000>;
3      pinctrl-names = "default";
4      pinctrl-0 = <&pinctrl_i2c1>;
5      status = "okay";
6
7      mag3110@0e {8          compatible = "fsl,mag3110";
9          reg = <0x0e>;
10         position = <2>;
11     };
......
20 };
第7~11行,向i2c1添加mag3110子节点,第7行“mag3110@0e”是子节点名字,“@”后面的“0e”就是mag3110的I2C器件地址。第8行设置compatible属性值为“fsl,mag3110”。第9行的reg属性也是设置mag3110的器件地址的,因此值为0x0e。I2C设备节点的创建重点是compatible属性和reg属性的设置,一个用于匹配驱动,一个用于设置器件地址。

61.3.2 I2C设备数据收发处理流程
在61.1.2小节已经说过了,I2C设备驱动首先要做的就是初始化i2c_driver并向Linux内核注册。当设备和驱动匹配以后i2c_driver里面的probe函数就会执行,probe函数里面所做的就是字符设备驱动那一套了。一般需要在probe函数里面初始化I2C设备,要初始化I2C设备就必须能够对I2C设备寄存器进行读写操作,这里就要用到i2c_transfer函数了。i2c_transfer函数最终会调用I2C适配器中i2c_algorithm里面的master_xfer函数,对于I.MX6U而言就是i2c_imx_xfer这个函数。i2c_transfer函数原型如下:
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)
函数参数和返回值含义如下:
adap:所使用的I2C适配器,i2c_client会保存其对应的i2c_adapter。
msgs:I2C要发送的一个或多个消息。
num:消息数量,也就是msgs的数量。
返回值:负值,失败,其他非负值,发送的msgs数量。
我们重点来看一下msgs这个参数,这是一个i2c_msg类型的指针参数,I2C进行数据收发说白了就是消息的传递,Linux内核使用i2c_msg结构体来描述一个消息。i2c_msg结构体定义在include/uapi/linux/i2c.h文件中,结构体内容如下:

示例代码61.3.2.1 i2c_msg结构体
68 struct i2c_msg {69      __u16 addr;                     /* 从机地址             */
70      __u16 flags;                    /* 标志           */
71      #define I2C_M_TEN               0x0010
72      #define I2C_M_RD                0x0001
73      #define I2C_M_STOP              0x8000
74      #define I2C_M_NOSTART           0x4000
75      #define I2C_M_REV_DIR_ADDR  0x2000
76      #define I2C_M_IGNORE_NAK    0x1000
77      #define I2C_M_NO_RD_ACK     0x0800
78      #define I2C_M_RECV_LEN      0x0400
79      __u16 len;                      /* 消息(本msg)长度   */
80      __u8 *buf;                      /* 消息数据             */
81 };
使用i2c_transfer函数发送数据之前要先构建好i2c_msg,使用i2c_transfer进行I2C数据收发的示例代码如下:
示例代码61.3.2.2 I2C设备多寄存器数据读写
1  /* 设备结构体 */
2  struct xxx_dev {3       ......
4       void *private_data; /* 私有数据,一般会设置为i2c_client */
5  };
6
7  /*
8   * @description : 读取I2C设备多个寄存器数据
9   * @param – dev : I2C设备
10  * @param – reg : 要读取的寄存器首地址
11  * @param – val : 读取到的数据
12  * @param – len : 要读取的数据长度
13  * @return          : 操作结果
14  */
15 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
int len)
16 {17      int ret;
18      struct i2c_msg msg[2];
19      struct i2c_client *client = (struct i2c_client *)
dev->private_data;
20
21      /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
22      msg[0].addr = client->addr;             /* I2C器件地址      */
23      msg[0].flags = 0;                      /* 标记为发送数据  */
24      msg[0].buf = &reg;                     /* 读取的首地址       */
25      msg[0].len = 1;                        /* reg长度            */
26
27      /* msg[1],第二条读消息,读取寄存器数据 */
28      msg[1].addr = client->addr;             /* I2C器件地址      */
29      msg[1].flags = I2C_M_RD;               /* 标记为读取数据  */
30      msg[1].buf = val;                      /* 读取数据缓冲区  */
31      msg[1].len = len;                      /* 要读取的数据长度 */
32
33      ret = i2c_transfer(client->adapter, msg, 2);
34      if(ret == 2) {35          ret = 0;
36      } else {37          ret = -EREMOTEIO;
38      }
39      return ret;
40 }
41
42 /*
43  * @description : 向I2C设备多个寄存器写入数据
44  * @param – dev : 要写入的设备结构体
45  * @param – reg : 要写入的寄存器首地址
46  * @param – buf : 要写入的数据缓冲区
47  * @param – len : 要写入的数据长度
48  * @return          : 操作结果
49  */
50 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,
u8 len)
51 {52      u8 b[256];
53      struct i2c_msg msg;
54      struct i2c_client *client = (struct i2c_client *)
dev->private_data;
55
56      b[0] = reg;                    /* 寄存器首地址                       */
57      memcpy(&b[1],buf,len);          /* 将要发送的数据拷贝到数组b里面  */
58
59      msg.addr = client->addr;        /* I2C器件地址                      */
60      msg.flags = 0;                 /* 标记为写数据                       */
61
62      msg.buf = b;                   /* 要发送的数据缓冲区                */
63      msg.len = len + 1;            /* 要发送的数据长度                     */
64
65      return i2c_transfer(client->adapter, &msg, 1);
66 }
第2~5行,设备结构体,在设备结构体里面添加一个执行void的指针成员变量private_data,此成员变量用于保存设备的私有数据。在I2C设备驱动中我们一般将其指向I2C设备对应的i2c_client。
第15~40行,xxx_read_regs函数用于读取I2C设备多个寄存器数据。第18行定义了一个i2c_msg数组,2个数组元素,因为I2C读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个i2c_msg。一个用于发送寄存器地址,一个用于读取寄存器值。对于msg[0],将flags设置为0,表示写数据。msg[0]的addr是I2C设备的器件地址,msg[0]的buf成员变量就是要读取的寄存器地址。对于msg[1],将flags设置为I2C_M_RD,表示读取数据。msg[1]的buf成员变量用于保存读取到的数据,len成员变量就是要读取的数据长度。调用i2c_transfer函数完成I2C数据读操作。
第50~66行,xxx_write_regs函数用于向I2C设备多个寄存器写数据,I2C写操作要比读操作简单一点,因此一个i2c_msg即可。数组b用于存放寄存器首地址和要发送的数据,第59行设置msg的addr为I2C器件地址。第60行设置msg的flags为0,也就是写数据。第62行设置要发送的数据,也就是数组b。第63行设置msg的len为len+1,因为要加上一个字节的寄存器地址。最后通过i2c_transfer函数完成向I2C设备的写操作。
另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。首先来看一下I2C数据发送函数i2c_master_send,函数原型如下:

int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count)
函数参数和返回值含义如下:
client:I2C设备对应的i2c_client。
buf:要发送的数据。
count:要发送的数据字节数,要小于64KB,以为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
I2C数据接收函数为i2c_master_recv,函数原型如下:
int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count)
函数参数和返回值含义如下:
client:I2C设备对应的i2c_client。
buf:要接收的数据。
count:要接收的数据字节数,要小于64KB,以为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
关于Linux下I2C设备驱动的编写流程就讲解到这里,重点就是i2c_msg的构建和i2c_transfer函数的调用,接下来我们就编写AP3216C这个I2C设备的Linux驱动。
61.4 硬件原理图分析
本章实验硬件原理图参考26.2小节即可。
61.5 实验程序编写
本实验对应的例程路径为:开发板光盘-> 2、Linux驱动例程-> 21_iic。
61.5.1 修改设备树
1、IO修改或添加
首先肯定是要修改IO,AP3216C用到了I2C1接口,I.MX6U-ALPHA开发板上的I2C1接口使用到了UART4_TXD和UART4_RXD,因此肯定要在设备树里面设置这两个IO。如果要用到AP3216C的中断功能的话还需要初始化AP_INT对应的GIO1_IO01这个IO,本章实验我们不使用中断功能。因此只需要设置UART4_TXD和UART4_RXD这两个IO,NXP其实已经将他这两个IO设置好了,打开imx6ull-alientek-emmc.dts,然后找到如下内容:

示例代码61.5.1.1 pinctrl_i2c1子节点
1 pinctrl_i2c1: i2c1grp {2      fsl,pins = <
3          MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
4          MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
5      >;
6  };
pinctrl_i2c1就是I2C1的IO节点,这里将UART4_TXD和UART4_RXD这两个IO分别复用为I2C1_SCL和I2C1_SDA,电气属性都设置为0x4001b8b0。
2、在i2c1节点追加ap3216c子节点

AP3216C是连接到I2C1上的,因此需要在i2c1节点下添加ap3216c的设备子节点,在imx6ull-alientek-emmc.dts文件中找到i2c1节点,此节点默认内容如下:

示例代码61.5.1.2 i2c1子节点默认内容
1  &i2c1 {2      clock-frequency = <100000>;
3      pinctrl-names = "default";
4      pinctrl-0 = <&pinctrl_i2c1>;
5      status = "okay";
6
7      mag3110@0e {8          compatible = "fsl,mag3110";
9          reg = <0x0e>;
10         position = <2>;
11     };
12
13     fxls8471@1e {14         compatible = "fsl,fxls8471";
15         reg = <0x1e>;
16         position = <0>;
17         interrupt-parent = <&gpio5>;
18         interrupts = <0 8>;
19     };
20 };
第2行,clock-frequency属性为I2C频率,这里设置为100KHz。
第4行,pinctrl-0属性指定I2C所使用的IO为示例代码61.5.1.1中的pinctrl_i2c1子节点。
第7~11行,mag3110是个磁力计,NXP官方的EVK开发板上接了mag3110,因此NXP在i2c1节点下添加了mag3110这个子节点。正点原子的I.MX6U-ALPHA开发板上没有用到mag3110,因此需要将此节点删除掉。
第13~19行,NXP官方EVK开发板也接了一个fxls8471,正点原子的I.MX6U-ALPHA开发板同样没有此器件,所以也要将其删除掉。
将i2c1节点里面原有的mag3110和fxls8471这两个I2C子节点删除,然后添加ap3216c子节点信息,完成以后的i2c1节点内容如下所示:
示例代码61.5.1.3 添加ap3216c子节点以后的i2c1节点
1  &i2c1 {2      clock-frequency = <100000>;
3      pinctrl-names = "default";
4      pinctrl-0 = <&pinctrl_i2c1>;
5      status = "okay";
6
7      ap3216c@1e {8          compatible = "alientek,ap3216c";
9          reg = <0x1e>;
10     };
11 };
第7行,ap3216c子节点,@后面的“1e”是ap3216c的器件地址。
第8行,设置compatible值为“alientek,ap3216c”。
第9行,reg属性也是设置ap3216c器件地址的,因此reg设置为0x1e。
设备树修改完成以后使用“make dtbs”重新编译一下,然后使用新的设备树启动Linux内核。/sys/bus/i2c/devices目录下存放着所有I2C设备,如果设备树修改正确的话,会在/sys/bus/i2c/devices目录下看到一个名为“0-001e”的子目录,如图61.5.1.1所示:

图61.5.1.1 当前系统I2C设备
图61.5.1.1中的“0-001e”就是ap3216c的设备目录,“1e”就是ap3216c器件地址。进入0-001e目录,可以看到“name”文件,name问价就保存着此设备名字,在这里就是“ap3216c”,如图61.5.1.2所示:

图61.5.1.2 ap3216c器件名字
61.5.2 AP3216C驱动编写
新建名为“21_iic”的文件夹,然后在21_iic文件夹里面创建vscode工程,工作区命名为“iic”。工程创建好以后新建ap3216c.c和ap3216creg.h这两个文件,ap3216c.c为AP3216C的驱动代码,ap3216creg.h是AP3216C寄存器头文件。先在ap3216creg.h中定义好AP3216C的寄存器,输入如下内容,

示例代码61.5.2.1 ap3216creg.h文件代码段
1  #ifndef AP3216C_H
2  #define AP3216C_H
3  /***************************************************************
4  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
5  文件名          : ap3216creg.h
6  作者           : 左忠凯
7  版本           : V1.0
8  描述           : AP3216C寄存器地址描述头文件
9  其他           : 无
10 论坛           : www.openedv.com
11 日志           : 初版V1.0 2019/9/2 左忠凯创建
12 ***************************************************************/
13 /* AP3316C寄存器 */
14 #define AP3216C_SYSTEMCONG   0x00    /* 配置寄存器        */
15 #define AP3216C_INTSTATUS    0X01    /* 中断状态寄存器  */
16 #define AP3216C_INTCLEAR     0X02    /* 中断清除寄存器      */
17 #define AP3216C_IRDATALOW    0x0A    /* IR数据低字节      */
18 #define AP3216C_IRDATAHIGH   0x0B    /* IR数据高字节      */
19 #define AP3216C_ALSDATALOW   0x0C    /* ALS数据低字节     */
20 #define AP3216C_ALSDATAHIGH  0X0D    /* ALS数据高字节     */
21 #define AP3216C_PSDATALOW    0X0E    /* PS数据低字节      */
22 #define AP3216C_PSDATAHIGH   0X0F    /* PS数据高字节      */
23
24 #endif
ap3216creg.h没什么好讲的,就是一些寄存器宏定义。然后在ap3216c.c输入如下内容:
示例代码61.5.2.2 ap3216c.c文件代码段
1   #include <linux/types.h>
2   #include <linux/kernel.h>
3   #include <linux/delay.h>
4   #include <linux/ide.h>
5   #include <linux/init.h>
6   #include <linux/module.h>
7   #include <linux/errno.h>
8   #include <linux/gpio.h>
9   #include <linux/cdev.h>
10  #include <linux/device.h>
11  #include <linux/of_gpio.h>
12  #include <linux/semaphore.h>
13  #include <linux/timer.h>
14  #include <linux/i2c.h>
15  #include <asm/mach/map.h>
16  #include <asm/uaccess.h>
17  #include <asm/io.h>
18  #include "ap3216creg.h"
19  /***************************************************************
20  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
21  文件名     : ap3216c.c
22  作者          : 左忠凯
23  版本          : V1.0
24  描述          : AP3216C驱动程序
25  其他          : 无
26  论坛          : www.openedv.com
27  日志          : 初版V1.0 2019/9/2 左忠凯创建
28  ***************************************************************/
29  #define AP3216C_CNT         1
30  #define AP3216C_NAME        "ap3216c"
31
32  struct ap3216c_dev {33      dev_t devid;                    /* 设备号          */
34      struct cdev cdev;               /* cdev             */
35      struct class *class;            /* 类                */
36      struct device *device;          /* 设备               */
37      struct device_node  *nd;        /* 设备节点             */
38      int major;                      /* 主设备号             */
39      void *private_data;             /* 私有数据             */
40      unsigned short ir, als, ps; /* 三个光传感器数据     */
41  };
42
43  static struct ap3216c_dev ap3216cdev;
44
45  /*
46   * @description    : 从ap3216c读取多个寄存器数据
47   * @param – dev    :  ap3216c设备
48   * @param – reg    :  要读取的寄存器首地址
49   * @param – val    :  读取到的数据
50   * @param – len    :  要读取的数据长度
51   * @return         : 操作结果
52   */
53  static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg,
void *val, int len)
54  {55      int ret;
56      struct i2c_msg msg[2];
57      struct i2c_client *client = (struct i2c_client *)
dev->private_data;
58
59      /* msg[0]为发送要读取的首地址 */
60      msg[0].addr = client->addr;     /* ap3216c地址    */
61      msg[0].flags = 0;                  /* 标记为发送数据  */
62      msg[0].buf = &reg;                 /* 读取的首地址       */
63      msg[0].len = 1;                    /* reg长度            */
64
65      /* msg[1]读取数据 */
66      msg[1].addr = client->addr;     /* ap3216c地址    */
67      msg[1].flags = I2C_M_RD;           /* 标记为读取数据  */
68      msg[1].buf = val;                  /* 读取数据缓冲区  */
69      msg[1].len = len;                  /* 要读取的数据长度 */
70
71      ret = i2c_transfer(client->adapter, msg, 2);
72      if(ret == 2) {73          ret = 0;
74      } else {75          printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
76          ret = -EREMOTEIO;
77      }
78      return ret;
79  }
80
81  /*
82   * @description    : 向ap3216c多个寄存器写入数据
83   * @param – dev    :  ap3216c设备
84   * @param – reg    :  要写入的寄存器首地址
85   * @param – val    :  要写入的数据缓冲区
86   * @param – len    :  要写入的数据长度
87   * @return         :   操作结果
88   */
89  static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg,
u8 *buf, u8 len)
90  {91      u8 b[256];
92      struct i2c_msg msg;
93      struct i2c_client *client = (struct i2c_client *)
dev->private_data;
94
95      b[0] = reg;                    /* 寄存器首地址                       */
96      memcpy(&b[1],buf,len);          /* 将要写入的数据拷贝到数组b里面  */
97
98      msg.addr = client->addr;        /* ap3216c地址                    */
99      msg.flags = 0;                 /* 标记为写数据                       */
100
101     msg.buf = b;                   /* 要写入的数据缓冲区                */
102     msg.len = len + 1;            /* 要写入的数据长度                     */
103
104     return i2c_transfer(client->adapter, &msg, 1);
105 }
106
107 /*
108  * @description    : 读取ap3216c指定寄存器值,读取一个寄存器
109  * @param – dev    :  ap3216c设备
110  * @param – reg    :  要读取的寄存器
111  * @return         :   读取到的寄存器值
112  */
113 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev,
u8 reg)
114 {115     u8 data = 0;
116
117     ap3216c_read_regs(dev, reg, &data, 1);
118     return data;
119
120 #if 0
121     struct i2c_client *client = (struct i2c_client *)
dev->private_data;
122     return i2c_smbus_read_byte_data(client, reg);
123 #endif
124 }
125
126 /*
127  * @description    : 向ap3216c指定寄存器写入指定的值,写一个寄存器
128  * @param – dev    :  ap3216c设备
129  * @param – reg    :  要写的寄存器
130  * @param – data   : 要写入的值
131  * @return         :    无
132  */
133 static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg,
u8 data)
134 {135     u8 buf = 0;
136     buf = data;
137     ap3216c_write_regs(dev, reg, &buf, 1);
138 }
139
140 /*
141  * @description    : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR,
142  *                      :同时打开ALS,IR+PS的话两次数据读取的间隔要大于112.5ms
143  * @param - ir     : ir数据
144  * @param - ps     : ps数据
145  * @param - ps     : als数据
146  * @return         : 无。
147  */
148 void ap3216c_readdata(struct ap3216c_dev *dev)
149 {150     unsigned char i =0;
151     unsigned char buf[6];
152
153     /* 循环读取所有传感器数据 */
154     for(i = 0; i < 6; i++)
155     {156         buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
157     }
158
159     if(buf[0] & 0X80)       /* IR_OF位为1,则数据无效       */
160         dev->ir = 0;
161     else                    /* 读取IR传感器的数据           */
162         dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
163
164     dev->als = ((unsigned short)buf[3] << 8) | buf[2];/* ALS数据 */
165
166     if(buf[4] & 0x40)       /* IR_OF位为1,则数据无效       */
167         dev->ps = 0;
168     else                    /* 读取PS传感器的数据           */
169         dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) |(buf[4] & 0X0F);
170 }
171
172 /*
173  * @description    : 打开设备
174  * @param – inode  : 传递给驱动的inode
175  * @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量
176  *                    一般在open的时候将private_data指向设备结构体。
177  * @return         : 0 成功;其他 失败
178  */
179 static int ap3216c_open(struct inode *inode, struct file *filp)
180 {181     filp->private_data = &ap3216cdev;
182
183     /* 初始化AP3216C */
184     ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
185     mdelay(50);     /* AP3216C复位最少10ms  */
186     ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);
187     return 0;
188 }
189
190 /*
191  * @description    : 从设备读取数据
192  * @param – filp   : 要打开的设备文件(文件描述符)
193  * @param - buf    : 返回给用户空间的数据缓冲区
194  * @param - cnt    : 要读取的数据长度
195  * @param – offt   : 相对于文件首地址的偏移
196  * @return         : 读取的字节数,如果为负值,表示读取失败
197  */
198 static ssize_t ap3216c_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *off)
199 {200     short data[3];
201     long err = 0;
202
203     struct ap3216c_dev *dev = (struct ap3216c_dev *)
filp->private_data;
204
205     ap3216c_readdata(dev);
206
207     data[0] = dev->ir;
208     data[1] = dev->als;
209     data[2] = dev->ps;
210     err = copy_to_user(buf, data, sizeof(data));
211     return 0;
212 }
213
214 /*
215  * @description    : 关闭/释放设备
216  * @param - filp   : 要关闭的设备文件(文件描述符)
217  * @return         : 0 成功;其他 失败
218  */
219 static int ap3216c_release(struct inode *inode, struct file *filp)
220 {221     return 0;
222 }
223
224 /* AP3216C操作函数 */
225 static const struct file_operations ap3216c_ops = {226     .owner = THIS_MODULE,
227     .open = ap3216c_open,
228     .read = ap3216c_read,
229     .release = ap3216c_release,
230 };
231
232  /*
233   * @description       : i2c驱动的probe函数,当驱动与
234   *                       设备匹配以后此函数就会执行
235   * @param - client    : i2c设备
236   * @param - id        : i2c设备ID
237   * @return            : 0,成功;其他负值,失败
238   */
239 static int ap3216c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
240 {241     /* 1、构建设备号 */
242     if (ap3216cdev.major) {243         ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
244         register_chrdev_region(ap3216cdev.devid, AP3216C_CNT,
AP3216C_NAME);
245     } else {246         alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT,
AP3216C_NAME);
247         ap3216cdev.major = MAJOR(ap3216cdev.devid);
248     }
249
250     /* 2、注册设备 */
251     cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
252     cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
253
254     /* 3、创建类 */
255     ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
256     if (IS_ERR(ap3216cdev.class)) {257         return PTR_ERR(ap3216cdev.class);
258     }
259
260     /* 4、创建设备 */
261     ap3216cdev.device = device_create(ap3216cdev.class, NULL,
ap3216cdev.devid, NULL, AP3216C_NAME);
262     if (IS_ERR(ap3216cdev.device)) {263         return PTR_ERR(ap3216cdev.device);
264     }
265
266     ap3216cdev.private_data = client;
267
268     return 0;
269 }
270
271 /*
272  * @description    : i2c驱动的remove函数,移除i2c驱动此函数会执行
273  * @param – client : i2c设备
274  * @return             : 0,成功;其他负值,失败
275  */
276 static int ap3216c_remove(struct i2c_client *client)
277 {278     /* 删除设备 */
279     cdev_del(&ap3216cdev.cdev);
280     unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
281
282     /* 注销掉类和设备 */
283     device_destroy(ap3216cdev.class, ap3216cdev.devid);
284     class_destroy(ap3216cdev.class);
285     return 0;
286 }
287
288 /* 传统匹配方式ID列表 */
289 static const struct i2c_device_id ap3216c_id[] = {290     {"alientek,ap3216c", 0},
291     {}
292 };
293
294 /* 设备树匹配列表 */
295 static const struct of_device_id ap3216c_of_match[] = {296     { .compatible = "alientek,ap3216c" },
297     { /* Sentinel */ }
298 };
299
300 /* i2c驱动结构体 */
301 static struct i2c_driver ap3216c_driver = {302     .probe = ap3216c_probe,
303     .remove = ap3216c_remove,
304     .driver = {305             .owner = THIS_MODULE,
306             .name = "ap3216c",
307             .of_match_table = ap3216c_of_match,
308            },
309     .id_table = ap3216c_id,
310 };
311
312 /*
313  * @description    : 驱动入口函数
314  * @param          : 无
315  * @return         : 无
316  */
317 static int __init ap3216c_init(void)
318 {319     int ret = 0;
320
321     ret = i2c_add_driver(&ap3216c_driver);
322     return ret;
323 }
324
325 /*
326  * @description    : 驱动出口函数
327  * @param          : 无
328  * @return         : 无
329  */
330 static void __exit ap3216c_exit(void)
331 {332     i2c_del_driver(&ap3216c_driver);
333 }
334
335 /* module_i2c_driver(ap3216c_driver) */
336
337 module_init(ap3216c_init);
338 module_exit(ap3216c_exit);
339 MODULE_LICENSE("GPL");
340 MODULE_AUTHOR("zuozhongkai");
第32~41行,ap3216c设备结构体,第39行的private_data成员变量用于存放ap3216c对应的i2c_client。第40行的ir、als和ps分别存储AP3216C的IR、ALS和PS数据。
第43行,定义一个ap3216c_dev类型的设备结构体变量ap3216cdev。
第53~79行,ap3216c_read_regs函数实现多字节读取,但是AP3216C好像不支持连续多字节读取,此函数在测试其他I2C设备的时候可以实现多给字节连续读取,但是在AP3216C上不能连续读取多个字节。不过读取一个字节没有问题的。
第89~105行,ap3216c_write_regs函数实现连续多字节写操作。
第113~124行,ap3216c_read_reg函数用于读取AP3216C的指定寄存器数据,用于一个寄存器的数据读取。
第133~138行,ap3216c_write_reg函数用于向AP3216C的指定寄存器写入数据,用于一个寄存器的数据写操作。
第148~170行,读取AP3216C的PS、ALS和IR等传感器原始数据值。
第179~230行,标准的字符设备驱动框架。
第239~269行,ap3216c_probe函数,当I2C设备和驱动匹配成功以后此函数就会执行,和platform驱动框架一样。此函数前面都是标准的字符设备注册代码,最后面会将此函数的第一个参数client传递给ap3216cdev的private_data成员变量。
第289~292行,ap3216c_id匹配表,i2c_device_id类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。
第295~298行,ap3216c_of_match匹配表,of_device_id类型,用于设备树设备和驱动匹配。这里只写了一个compatible属性,值为“alientek,ap3216c”。
第301~310行,ap3216c_driver结构体变量,i2c_driver类型。
第317~323行,驱动入口函数ap3216c_init,此函数通过调用i2c_add_driver来向Linux内核注册i2c_driver,也就是ap3216c_driver。
第330~333行,驱动出口函数ap3216c_exit,此函数通过调用i2c_del_driver来注销掉前面注册的ap3216c_driver。

61.5.3 编写测试APP
新建ap3216cApp.c文件,然后在里面输入如下所示内容:

示例代码61.5.3.1 ap3216cApp.c文件代码段
1  #include "stdio.h"
2  #include "unistd.h"
3  #include "sys/types.h"
4  #include "sys/stat.h"
5  #include "sys/ioctl.h"
6  #include "fcntl.h"
7  #include "stdlib.h"
8  #include "string.h"
9  #include <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 /***************************************************************
15 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
16 文件名      : ap3216cApp.c
17 作者           : 左忠凯
18 版本           : V1.0
19 描述           : ap3216c设备测试APP。
20 其他           : 无
21 使用方法         :./ap3216cApp /dev/ap3216c
22 论坛           : www.openedv.com
23 日志           : 初版V1.0 2019/9/20 左忠凯创建
24 ***************************************************************/
25
26 /*
27  * @description     : main主程序
28  * @param - argc    : argv数组元素个数
29  * @param - argv    : 具体参数
30  * @return          : 0 成功;其他 失败
31  */
32 int main(int argc, char *argv[])
33 {34      int fd;
35      char *filename;
36      unsigned short databuf[3];
37      unsigned short ir, als, ps;
38      int ret = 0;
39
40      if (argc != 2) {41          printf("Error Usage!\r\n");
42          return -1;
43      }
44
45      filename = argv[1];
46      fd = open(filename, O_RDWR);
47      if(fd < 0) {48          printf("can't open file %s\r\n", filename);
49          return -1;
50      }
51
52      while (1) {53          ret = read(fd, databuf, sizeof(databuf));
54          if(ret == 0) {            /* 数据读取成功       */
55                  ir =  databuf[0];      /* ir传感器数据  */
56                  als = databuf[1];      /* als传感器数据     */
57                  ps =  databuf[2];      /* ps传感器数据  */
58                  printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
59          }
60          usleep(200000);             /* 200ms            */
61      }
62      close(fd);                      /* 关闭文件             */
63      return 0;
64 }
ap3216cApp.c文件内容很简单,就是在while循环中不断的读取AP3216C的设备文件,从而得到ir、als和ps这三个数据值,然后将其输出到终端上。

61.6 运行测试
61.6.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“ap3216c.o”,Makefile内容如下所示:
示例代码61.6.1.1 Makefile文件
1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

4 obj-m := ap3216c.o

11 clean:
12 $(MAKE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (KERNELDIR)M=(CURRENT_PATH) clean
第4行,设置obj-m变量的值为“ap3216c.o”。
输入如下命令编译出驱动模块文件:
make -j32
编译成功以后就会生成一个名为“ap3216c.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译ap3216cApp.c这个测试程序:
arm-linux-gnueabihf-gcc ap3216cApp.c -o ap3216cApp
编译成功以后就会生成ap3216cApp这个应用程序。
61.6.2 运行测试
将上一小节编译出来ap3216c.ko和ap3216cApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中。输入如下命令加载ap3216c.ko这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe ap3216c.ko //加载驱动模块
当驱动模块加载成功以后使用ap3216cApp来测试,输入如下命令:
./ap3216cApp /dev/ap3216c
测试APP会不断的从AP3216C中读取数据,然后输出到终端上,如图61.6.2.1所示:

图61.6.2.1 获取到的AP3216C数据
大家可以用手电筒照一下AP3216C,或者手指靠近AP3216C来观察传感器数据有没有变化。

【正点原子Linux连载】第六十一章 Linux I2C驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0相关推荐

  1. 【正点原子FPGA连载】第二十一章 HDMI彩条显示实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  2. 正点原子linux串口驱动下载,【正点原子Linux连载】第二十一章UART串口通信实验-摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南 (amobbs.com 阿莫电子论坛)...

    示例代码21.3.1 bsp_uart.h文件代码 1  #ifndef _BSP_UART_H 2  #define _BSP_UART_H 3  #include "imx6ul.h&q ...

  3. 【正点原子FPGA连载】 第十七章 RS485串口通信实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  4. 【正点原子FPGA连载】第二十七章DS18B20数字温度传感器实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  5. 【正点原子STM32连载】 第二十七章 RTC实时时钟实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  6. 【正点原子FPGA连载】 第十七章 HDMI彩条显示实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

    1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: h ...

  7. 【正点原子Linux连载】第六十四章 Linux 多点电容触摸屏实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. 【正点原子Linux连载】第六十七章 Linux USB驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. 【正点原子Linux连载】第七十一章 Linux 4G通信实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

最新文章

  1. C++中实现链表的删除和颠倒
  2. react 动态添加组件属性_这么高质量React面试题(含答案),看到就是赚到了!...
  3. Centos环境下mysql源码编译安装
  4. RDC TERM TABLE
  5. Qt Creator在多个平台上运行
  6. java重写6,java重写equals()方法和hashCode()方法
  7. java list 获取索引_java – 获取arrayList中元素的索引
  8. python 将列表值赋予函数_python把空列表作为函数默认参数,可是有坑的
  9. centos将某一目录权限给用户_CenTOS7使用ACL控制目录权限,只给某个用户访问特定目录...
  10. 为何大富连续三天彻夜未眠!
  11. hadoop组件中的hive安装
  12. 技术是如何推动内容产业的。
  13. wagtail 实现中英文
  14. python 自动发邮件模块抄送_Python实现自动发送邮件
  15. mysql 删除时间一个星期_我用财务管理系统花费一天时间做账,别人用一个星期也追赶不上...
  16. qq对计算机程序的更改,解决电脑总弹出“是否允许程序对计算机进行更改”
  17. 记war exploded部署不成功
  18. 基于开源软件构建高性能集群NAS系统
  19. 【网络协议从入门到底层原理】【06】数据链路层
  20. hdu 6194string string string 后缀自动机

热门文章

  1. python函数声明和调用定义及原理详解
  2. Linux下wlan配置程序详解,linux 命令行配置wlan无线网卡
  3. OpenWrt软路由空间扩容
  4. python中集合的常用方法
  5. 如何保证微服务下的数据一致性?
  6. npm install up to date, audited 733 packages in 13s
  7. 【在线教育】- 讲师模块功能实现
  8. 马云的五新其实没有哪一个是新的
  9. UNIX环境高级编程——系统数据文件和信息
  10. JAVA字符流InputStreamReader读取文本