I.MX6ULL_Linux_驱动篇(48)linux I2C驱动

news2024/11/24 2:01:54

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

目录

Linux I2C 驱动框架简介

I2C 总线驱动

I2C 设备驱动

I2C 设备和驱动匹配过程

I2C 适配器驱动分析

I2C 设备驱动编写流程

I2C 设备信息描述

I2C 设备数据收发处理流程

实验

修改设备树

驱动

应用


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 设备而编写的驱动。

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 文件中,结构体内容如下:

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 文件中,内容如下(删除条件编译):

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 适配器驱动。

I2C 设备驱动

I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver,根据总线、设备和驱动模型,I2C 总线上一小节已经讲了。还剩下设备和驱动, i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容,类似于 platform_driver。

i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:

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。

i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容, i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:

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 是一个宏,定义如下:

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 的注册示例代码如下:

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 函数里面基本就是标准的字符设备驱动那一套了。

I2C 设备和驱动匹配过程

I2C 设备和驱动的匹配过程是由 I2C 核心来完成的, drivers/i2c/i2c-core.c 就是 I2C 的核心部分, I2C 核心提供了一些与具体硬件无关的 API 函数,比如前面讲过的:

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)
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 内容如下:

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 这个函数,此函数内容如下:

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 设备和驱动匹配。

I2C 适配器驱动分析

前面我们讲解了 Linux 下的 I2C 驱动框架,重点分为 I2C 适配器驱动和 I2C 设备驱动,其中 I2C 适配器驱动就是 SOC 的 I2C 控制器驱动。 I2C 设备驱动是需要用户根据不同的 I2C 设
备去编写,而 I2C 适配器驱动一般都是 SOC 厂商去编写的,比如 NXP 就编写好了 I.MX6U 的I2C 适配器驱动。在 imx6ull.dtsi 文件中找到 I.MX6U 的 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,在此文件中有如下内容:

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

从上述代码可以看出, 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 函数内容如下所示(有省略):

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 结构体定义如下:

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 函数内容如下:

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 设备通信的, 此函数内容如下(有省略):
 

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 设备驱动编写流程

I2C 适配器驱动 SOC 厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动,本小节我们就来学习一下 I2C 设备驱动的详细编写流程。

I2C 设备信息描述

1、未使用设备树的时候
首先肯定要描述 I2C 设备节点信息,先来看一下没有使用设备树的时候是如何在 BSP 里面描述 I2C 设备信息的,在未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描
述一个具体的 I2C 设备。 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 设备
信息描述如下:

392 static struct i2c_board_info mx27_3ds_i2c_camera = {
393     I2C_BOARD_INFO("ov2640", 0x30),
394 };

上述代码中使用 I2C_BOARD_INFO 来完成 mx27_3ds_i2c_camera 的初始化工作,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 这两个成员变量,因此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 这个设备树文件,然后找到如下内容:

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 属性的设置,一个用于匹配驱动,一个用于设置器件地址。

I2C 设备数据收发处理流程

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 文件中,结构体内容如下:

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 数据收发的示例代码如下:

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 驱动。

实验

修改设备树

AP3216C 是连接到 I2C1 上的,因此需要在 i2c1 节点下添加 ap3216c 的设备子节点,在imx6ull-alientek-emmc.dts 文件中找到 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”的子目录,如图所示:

图中的“0-001e”就是 ap3216c 的设备目录,“1e”就是 ap3216c 器件地址。进入0-001e 目录,可以看到“name”文件, name 问价就保存着此设备名字,在这里就是“ap3216c”。

驱动
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"

#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"

struct ap3216c_dev {
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	int major;			/* 主设备号 */
	void *private_data;	/* 私有数据 */
	unsigned short ir, als, ps;		/* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;

/*
 * @description	: 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int ap3216c_read_regs(struct ap3216c_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]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 */
	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=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ap3216c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 读取ap3216c指定寄存器值,读取一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
	u8 data = 0;

	ap3216c_read_regs(dev, reg, &data, 1);
	return data;

#if 0
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	return i2c_smbus_read_byte_data(client, reg);
#endif
}

/*
 * @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ap3216c_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。
 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
	unsigned char i =0;
    unsigned char buf[6];
	
	/* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)	
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);	
    }

    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据   		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &ap3216cdev;

	/* 初始化AP3216C */
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	short data[3];
	long err = 0;

	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
	
	ap3216c_readdata(dev);

	data[0] = dev->ir;
	data[1] = dev->als;
	data[2] = dev->ps;
	err = copy_to_user(buf, data, sizeof(data));
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,
};

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 1、构建设备号 */
	if (ap3216cdev.major) {
		ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
		register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
	} else {
		alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
		ap3216cdev.major = MAJOR(ap3216cdev.devid);
	}

	/* 2、注册设备 */
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3、创建类 */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);
	}

	/* 4、创建设备 */
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		return PTR_ERR(ap3216cdev.device);
	}

	ap3216cdev.private_data = client;

	return 0;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ap3216c_remove(struct i2c_client *client)
{
	/* 删除设备 */
	cdev_del(&ap3216cdev.cdev);
	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);

	/* 注销掉类和设备 */
	device_destroy(ap3216cdev.class, ap3216cdev.devid);
	class_destroy(ap3216cdev.class);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "ap3216c",
		   	.of_match_table = ap3216c_of_match, 
		   },
	.id_table = ap3216c_id,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ap3216c_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ap3216c_driver);
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}

/* module_i2c_driver(ap3216c_driver) */

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
应用
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	char *filename;
	unsigned short databuf[3];
	unsigned short ir, als, ps;
	int ret = 0;

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if(fd < 0) {
		printf("can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, databuf, sizeof(databuf));
		if(ret == 0) { 			/* 数据读取成功 */
			ir =  databuf[0]; 	/* ir传感器数据 */
			als = databuf[1]; 	/* als传感器数据 */
			ps =  databuf[2]; 	/* ps传感器数据 */
			printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
		}
		usleep(200000); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1327208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一些错误的集合

目录 第一章、1.1&#xff09;前端找不到图片1.2&#xff09;1.3&#xff09;1.4&#xff09; 第二章、2.1&#xff09;2.2&#xff09;2.3&#xff09; 第三章、3.1&#xff09;3.2&#xff09;3.3&#xff09; 第四章、4.1&#xff09;4.2&#xff09;4.3&#xff09; 友情提…

ZKP Pasta Curves

Mina book[https://o1-labs.github.io/proof-systems/specs/pasta.html?highlightpasta#pasta-curves]学习笔记 Pasta Curves Pasta Curves is a fascinating innovation in cryptography designed by Zcash. What are the Pasta Curves The Pasta Curves are a pair of e…

走过的2023:在挑战中领悟,在仿徨中成长

转眼间就到了2023年的最后一个月&#xff0c;回顾这短暂而又有意义的一年&#xff0c;可以用12个字总结&#xff1a;在挑战中领悟&#xff0c;在仿徨中成长。这篇文章我会从技术成长、职场生活、读书感悟和个人生活等几个方面&#xff0c;总结一下过去的这一年&#xff0c;梳理…

视觉设计必备!这6款工具让你事半功倍!

视觉设计师是做什么的 不同类型的行业将聘请视觉设计师&#xff0c;工作内容可能涉及设计网站、游戏、电影和其他数字设计项目。通过以下关于视觉设计师的工作描述和薪酬描述&#xff0c;我们可以进一步了解视觉设计师。 视觉设计师职位描述&#xff1a; 遵循品牌指南&#…

WEB 3D技术 three.js rotation元素旋转控制

我们在官网中搜索 Euler 循环用的 就不是三维向量了 而是欧拉角对象 但欧拉角也是绕着某个轴进行旋转 我们有两个这样的元素 官网中的 order 比较特殊 它是先旋转完 x轴 然后旋转 y轴 最后旋转z轴 order也是它默认的值 一般来讲 我们用 就改 x y z就够了 order 一般不需要 …

基于Java+Vue+uniapp微信小程序大学生心理健康服务设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

DOM是什么?

1、概述 &#xff08;1&#xff09;DOM代表文档对象模型&#xff0c;是 HTML 和 XML 文档的接口&#xff08;API&#xff09; &#xff08;2&#xff09;当浏览器第一次读取&#xff08;解析&#xff09;HTML文档时&#xff0c;会创建一个基于 HTML 文档的大对象&#xff0c;…

js中的Array.from()和Array.of()方法的用法详情

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

获取请求体中json数据并解析到实体对象

目录 相关依赖 前端代码 后端代码 测试结果 相关依赖 <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version> </dependency> <dependency><groupId>comm…

[软件] Image2LCD v4.0

介绍 通过打开图片, 可以提取图片的像素特征, 生成.c文件, 或者二进制文件等, 提供人们根据需要选择. 16位真彩色 每一个像素点需要用16位来表示, 分别是RGB, R: 5位 G: 6位, B: 5位, 共两个字节. 配置 tftLCD180显示屏, 官方给的参考代码, 需要如下所示设置.

Python 输入输出, 标识符, import(保留字关键字) ,注释 , 缩进

1 Python简介输入print()标识符import关键字保留字(关键字)注释缩进 Python简介 python是一门解释性语言 解释性语言的特点:它不像java c一样先编译后执行,它是直接每一行的去执行,所以遇见错误时,它会把错误之前的执行完 输入print() print() 是一个让计算机在屏幕上进行输…

FPGA模块——以太网(1)MDIO读写

FPGA模块——以太网MDIO读写 MDIO接口介绍MDIO接口代码&#xff08;1&#xff09;MDIO接口驱动代码&#xff08;2&#xff09;使用MDIO驱动的代码 MDIO接口介绍 MDIO是串行管理接口。MAC 和 PHY 芯片有一个配置接口&#xff0c;即 MDIO 接口&#xff0c;可以配置 PHY 芯片的工…

前端手动部署与自动化部署

连接服务器 先购买服务器 安装vscode插件 连接服务器 连接成功 手动部署 安装nginx 启动nginx systemctl start nginx systemctl status nginx systemctl enable nginx启动 检查状态 开机就启动nginx 开始手动部署 配置nginx 成功

flutter自定义地图Marker完美展示图片

世人都说雪景美 寒风冻脚无人疼 只道是一身正气 结论 参考Flutter集成高德地图并添加自定义Maker先实现自定义Marker。如果自定义Marker中用到了图片&#xff0c;那么会碰到图片没有被绘制到Marker的问题&#xff0c;此时需要通过precacheImage来预加载图片&#xff0c;从而解…

DC-磁盘配额

2023年全国网络系统管理赛项真题 模块B-Windows解析 题目 在DC2驱动器C:\上设置磁盘配额&#xff0c;限制磁盘空间为5G&#xff0c;警告等级为3G&#xff0c;超出配额限制时记录事件&#xff0c;超出警告等级时记录事件。 配置步骤 验证 查看DC2驱动器C:\的磁盘配额&#xf…

WPF组合控件TreeView+DataGrid之TreeView封装-粉丝专栏

wpf的功能非常强大&#xff0c;很多控件都是原生的&#xff0c;但是要使用TreeViewDataGrid的组合&#xff0c;就需要我们自己去封装实现。 我们需要的效果如图所示&#xff1a; 这2个图都是第三方控件自带的&#xff0c;并且都是收费使用。 现在我们就用原生的控件进行封装一…

APP应用加固指南:如何有效辨别,网络上伪造的地理位置?

目录 地理位置数据的来源 伪造地理位置数据的危害 如何有效辨别模拟器作弊行为&#xff1f; App加固有效防控地理伪造 在数字互联时代&#xff0c;已经离不开地理位置数据。地理位置数据不仅仅是一个简单的坐标&#xff0c;更是一种数字足迹&#xff0c;描绘了人们在数字世界中…

隧道LED照明技术见证大国交通发展,三思陶瓷散热技术强势突围

隧道灯&#xff0c;顾名思义&#xff0c;就是用于公路隧道中照明的灯。随着我们社会经济的不断进步&#xff0c;公路交通网越来越发达&#xff0c;隧道越来越多&#xff0c;隧道中的照明也越来越受到重视。 很多人会想当然地认为&#xff0c;隧道照明和公路、街道照明应该差不…

QpushButton菜单设置 和 右键菜单

一、前言 ​ 在PyQt中&#xff0c;可以通过QMenu和QAction类来创建和管理菜单。QMenu用于定义具体的菜单&#xff0c;而QAction则代表菜单中的具体操作项。 二、点击QPushButton&#xff0c;显示菜单 API QPushButton操作菜单的相关Api QMenu自身相关Api 代码演示 代码&…

你以为出现NoClassDefFoundError错误会是什么原因?

你以为出现NoClassDefFoundError错误会是什么原因&#xff1f; 1、概述2、事情经过3、总结 1、概述 大家好&#xff0c;我是欧阳方超&#xff0c;可以关注我的公众号“欧阳方超”&#xff0c;后续内容将在公众号首发。 同样的错误&#xff0c;非一样的解决方式。NoClassDefFou…