一、Linux I2C 驱动框架简介
Linux 内核将 I2C 驱动分为两部分:
①、I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
②、I2C 设备驱动,I2C 设备驱动就是针对具体的 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, int num);
400 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
401 unsigned short flags, char read_write, 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设备驱动
I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver,根据总线、设备和驱动模型,
I2C 总线上一小节已经讲了。还剩下设备和驱动,i2c_client 就是描述设备信息的,i2c_driver 描
述驱动内容,类似于 platform_driver。
1、i2c_client 结构体
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。
2、i2c_driver 结构体
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
3、I2c注册实例代码
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 函数里面基本就是标准的字符设备驱动那一套了。
4、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 内容如下:
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 设备和驱动匹配。
四、I.MX6U 的 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);
从示例代码 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 函数内容如下所示(有省略):
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 设备驱动编写流程
1、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 };
示例代码 61.3.1.2 中使用 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 这两个成员
变量,因此示例代码 61.3.1.2 的主要工作就是设置 I2C 设备名字为 ov2640,ov2640 的器件地
址为 0X30。
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 属性的设置,一个用于匹配驱动,一个用于设置器件地址。
2、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 = ® /* 读取的首地址 */
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 驱动。
六、实验
1、修改设备树
1、IO 修改或添加
打开 imx6ull-alientek-emmc.dts,然后找到如下内容:
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 节点,此节点默认内容如下:
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 节点内容如下所示:
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。
3、编译设备树重启系统
设备树修改完成以后使用“make dtbs”重新编译一下。
然后使用新的设备树启动 Linux 内 核。/sys/bus/i2c/devices 目录下存放着所有 I2C 设备,如果设备树修改正确的话,会在 /sys/bus/i2c/devices 目录下看到一个名为“0-001e”的子目录,如图 61.5.1.1 所示:
4、AP3216C 驱动编写
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
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 = ® /* 读取的首地址 */
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("zuipeng");
5、编写测试 APP
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 }
6、编译驱动程序和测试 APP
1 KERNELDIR := /home/zipeng/linux/myKernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
......
4 obj-m := ap3216c.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
输入如下命令编译出驱动模块文件:
make -j4
编译成功以后就会生成一个名为“ap3216c.ko”的驱动模块文件
输入如下命令编译 ap3216cApp.c 这个测试程序:
arm-linux-gnueabihf-gcc ap3216cApp.c -o ap3216cApp
编译成功以后就会生成 ap3216cApp 这个应用程序。

7、运行测试
将上一小节编译出来 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 中读取数据,然后输出到终端上

rmmod ap3216c.ko

1483

被折叠的 条评论
为什么被折叠?



