I.MX6U嵌入式Linux Platform设备驱动开发(6)I2C驱动

下如何在 Linux 下开发 I2C 接口器件驱动,重点是学习 Linux 下的 I2C 驱动框架,按照指定的框架去编写 I2C 设备驱动。

以 AP3216C 这个三合一环境光传感器为例。

1、Linux I2C驱动框架简介

裸机驱动AP3216C:
需要写2部分的驱动:
(1)I2C主机驱动:bsp_i2c.c 和 bsp_i2c.h -------> I.MX6U的接口驱动
(2)I2C设备驱动:bsp_ap3216c.c 和 bsp_ap3216c.h-------> AP3216C 这个 I2C 设备驱动文件。

这个正好符合 Linux 的驱动分离与分层的思想,因此 Linux内核也将 I2C 驱动分为两部分:
(1)I2C总线驱动:就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动;
(2)I2C设备驱动:就是针对具体的 I2C 设备而编写的驱动;

1.1、I2C总线驱动

I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,用到2个重要的数据结构:i2c_adapter 和 i2c_algorithm。

SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,结构体定义在include/linux/i2c.h文件中。在这个结构体中有一个结构体变量:

struct i2c_adapter {
	...
	const struct i2c_algorithm *algo;//总线访问算法
	...
};

对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。i2c_algorithm 结构体定义在include/linux/i2c.h文件中。

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)//使用静态总线号

一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,不需要用户去编写。因此 I2C 总线驱动对我们这些 SOC 使用者来说是被屏蔽掉的,我们只要专注于 I2C 设备驱
动即可。

1.2、I2C设备驱动

重点两个数据结构体:

i2c_client:描述设备信息的。一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

i2c_driver:描述驱动内容,类似于 platform_driver。重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。i2c_driver 注册函数为 int i2c_register_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);
		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);

当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些和 platform 驱动一样,probe 函数里面基本就是标准的字符设备驱动那一套了。

1.3、I2C设备和驱动匹配过程

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

设备和驱动的匹配过程也是由 I2C 总线完成的,I2C 总线的数据结构为 i2c_bus_type。

2、Linux I2C设备驱动编写流程

我们只需要编写具体的设备驱动。

2.1 I2C 设备信息描述

2.1.1 未使用设备树的时候

在未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描述一个具体的 I2C 设备。 i2c_board_info 结构体中的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 是一个宏

#define I2C_BOARD_INFO(dev_type, dev_addr) .type = dev_type, .addr = (dev_addr)

I2C_BOARD_INFO 宏其实就是设置 i2c_board_info 的 type 和 addr 这两个成员变量。I2C 设备名字为 ov2640,ov2640 的器件地址为 0X30。

2.1.2 使用设备树的时候

使用设备树的时候 I2C 设备信息通过创建相应的节点就行了,在 i2c1 节点下创建 mag3110 子节点,然后在这个子节点内描述 mag3110 这个芯片的相关信息。

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

        codec: wm8960@1a {
                compatible = "wlf,wm8960";
                reg = <0x1a>;
                clocks = <&clks IMX6UL_CLK_SAI2>;
                clock-names = "mclk";
                wlf,shared-lrclk;
        };

	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
		status = "disabled";
	};
......
};

向 i2c1 添加 mag3110 子节点,“mag3110@0e”是子节点名字,“@”后面的“0e”就是 mag3110 的 I2C 器件地址。 compatible 属性值为“fsl,mag3110”。 reg 属性也是设置 mag3110 的器件地址的,因此值为 0x0e。I2C 设备节点的创建重点是 compatible 属性和 reg 属性的设置,一个用于匹配驱动,一个用于设置器件地址。

2.1 I2C 设备数据收发处理流程

I2C 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核注册。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行,probe 函数里面所做的就是字符设备驱动那一套了。

2.3 程序编写

2.3.1 修改设备树

(1)IO修改或者添加
AP3216C用到了I2C1接口,开发板上的I2C1接口使用到了UART4_TXD和UART4_RXD,如果用到了中断,还需要设置AP_INT 对应的 GIO1_IO01 这个 IO。

打开imx6ull-alientek-emmc.dts

&iomuxc {
	...
	pinctrl_i2c1: i2c1grp {
		fsl,pins = <
			MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
			MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
		>;
	};
	...
};

pinctrl_i2c1 就是 I2C1 的 IO 节点.

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

1 &i2c1 {
2 		clock-frequency = <100000>;//I2C 频率,这里设置为 100KHz
3	 	pinctrl-names = "default";
4 		pinctrl-0 = <&pinctrl_i2c1>;// I2C所使用的IO,pinctrl_i2c1 子节
5 		status = "okay";
6 
7 		mag3110@0e {//NXP官方的EVK开发板上接了mag3110,正点原子没有接,需要删除 
8 			compatible = "fsl,mag3110";
9 			reg = <0x0e>;
10 			position = <2>;
11 		};
12
13 		fxls8471@1e {//NXP官方EVK开发板也接了一个fxls8471,正点原子没有接,需要删除
14 			compatible = "fsl,fxls8471";
15 			reg = <0x1e>;
16 			position = <0>;
17 			interrupt-parent = <&gpio5>;
18 			interrupts = <0 8>;
19 		};
		
		ap3216c@1e {
			compatible = "alientek,ap3216c";
			reg = <0x1e>;
		};
20 };

ap3216c 子节点,@后面的“1e”是 ap3216c 的器件地址。
设置 compatible 值为“alientek,ap3216c”。
reg 属性也是设置 ap3216c 器件地址的,因此 reg 设置为 0x1e。

设备树修改完成以后使用“make dtbs”重新编译一下,然后使用新的设备树启动 Linux 内核。/sys/bus/i2c/devices 目录下存放着所有 I2C 设备,如果设备树修改正确的话,会在/sys/bus/i2c/devices 目录下看到一个名为“0-001e”的子目录。

2.3.2 编写驱动

工程创建好以后新建 ap3216c.c 和 ap3216creg.h 这两个文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值