体系概述
I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在芯片之间进行通信。Linux内核支持I2C总线,可以通过I2C总线访问连接到系统的各种设备,如温度传感器、加速度计、数字电压计等。
在Linux内核中,I2C体系由以下几个部分组成:
-
I2C核心代码:I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等
-
I2C总线驱动程序:I2C总线驱动程序负责管理I2C总线的物理层和数据链路层,实现I2C总线的读写操作。I2C总线驱动主要包含I2C适配器数据结构i2c_adapter、I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。
-
I2C设备驱动程序:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
核心层
在Linux 2.6内核中,所有的I2C设备都在sysfs文件系统中显示,存于/sys/bus/i2c/目录下,以适配器地址和芯片地址的形式列出,例如:
在Linux内核源代码中的drivers目录下有一个i2c目录,而在i2c目录下又包含如下文件和文件夹。
-
i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
-
i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过“i2c-%d”(i2c-0,i2c-1,…,i2c-10,…)文件名并使用文件操作接口open()、write()、read()、ioctl()和close()等来访问这个设备。i2c-dev.c并不是针对特定的设备而设计的,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
-
busses文件夹这个文件包含了一些I2C主机控制器的驱动,如i2c-tegra.c、i2c-omap.c、i2c-versatile.c、i2c-s3c2410.c等。
-
algos文件夹实现了一些I2C总线适配器的通信方法。
内核中的include/linux/i2c.h头文件对i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构进行了定义。
1 struct i2c_adapter {
2 struct module *owner;
3 unsigned int class; /* classes to allow probing for */
4 const struct i2c_algorithm *algo; /* the algorithm to access the bus */
5 void *algo_data;
6
7 /* data fields that are valid for all devices */
8 struct rt_mutex bus_lock;
9
10 int timeout; /* in jiffies */
11 int retries;
12 struct device dev; /* the adapter device */
13
14 int nr;
15 char name[48];
16 struct completion dev_released;
17
18 struct mutex userspace_clients_lock;
19 struct list_head userspace_clients;
20
21 struct i2c_bus_recovery_info *bus_recovery_info;
22 };
1 struct i2c_algorithm {
2 /* If an adapter algorithm can't do I2C-level access, set master_xfer
3 to NULL. If an adapter algorithm can do SMBus access, set
4 smbus_xfer. If set to NULL, the SMBus protocol is simulated
5 using common I2C messages */
6 /* master_xfer should return the number of messages successfully
7 processed, or a negative value on error */
8 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
9 int num);
10 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
11 unsigned short flags, char read_write,
12 u8 command, int size, union i2c_smbus_data *data);
13
14 /* To determine what the adapter supports */
15 u32 (*functionality) (struct i2c_adapter *);
16 };
1 struct i2c_driver {
2 unsigned int class;
3
4 /* Notifies the driver that a new bus has appeared. You should avoid
5 * using this, it will be removed in a near future.
6 */
7 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
8
9 /* Standard driver model interfaces */
10 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
11 int (*remove)(struct i2c_client *);
12
13 /* driver model interfaces that don't relate to enumeration */
14 void (*shutdown)(struct i2c_client *);
15 int (*suspend)(struct i2c_client *, pm_message_t mesg);
16 int (*resume)(struct i2c_client *);
17
18 /* Alert callback, for example for the SMBus alert protocol.
19 * The format and meaning of the data value depends on the protocol.
20 * For the SMBus alert protocol, there is a single bit of data passed
21 * as the alert response's low bit ("event flag").
22 */
23 void (*alert)(struct i2c_client *, unsigned int data);
24
25 /* a ioctl like command that can be used to perform specific functions
26 * with the device.
27 */
28 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
29
30 struct device_driver driver;
31 const struct i2c_device_id *id_table;
32
33 /* Device detection callback for automatic device creation */
34 int (*detect)(struct i2c_client *, struct i2c_board_info *);
35 const unsigned short *address_list;
36 struct list_head clients;
37 };
1 struct i2c_client {
2 unsigned short flags; /* div., see below */
3 unsigned short addr; /* chip address - NOTE: 7bit */
4 /* addresses are stored in the */
5 /* _LOWER_ 7 bits */
6 char name[I2C_NAME_SIZE];
7 struct i2c_adapter *adapter; /* the adapter we sit on */
8 struct device dev; /* the device structure */
9 int irq; /* irq issued by device */
10 struct list_head detected;
11 };
下面分析i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构的作用及其盘根错节的关系。
-
下面分析i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构的作用及其盘根错节的关系。
i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm提供的通信函数来控制适配器产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含所使用的i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位。i2c_msg结构体也是非常重要的,它定义于include/uapi/linux/i2c.h(在uapi目录下,证明用户空间的应用也可能使用这个结构体)中,代码清单15.5给出了它的定义,其中的成员表明了I2C的传输地址、方向、缓冲区、缓冲区长度等信息。
1 struct i2c_msg { 2 __u16 addr; /* slave address */ 3 __u16 flags; 4 #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ 5 #define I2C_M_RD 0x0001 /* read data, from slave to master */ 6 #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 7 #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ 8 #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 9 #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 10 #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ 11 #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ 12 __u16 len; /* msg length */ 13 __u8 *buf; /* pointer to msg data */ 14 };
-
i2c_driver与i2c_client
i2c_driver对应于一套驱动方法,其主要成员函数是probe()、remove()、suspend()、resume()等,另外,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_driver可以支持多个同类型的i2c_client。
-
i2c_adpater与i2c_client
i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。
假设I2C总线适配器xxx上有两个使用相同驱动程序的yyy I2C设备,在打开该I2C总线的设备节点后,相关数据结构之间的逻辑组织关系将如下所示。
当工程师拿到实际的电路板时,面对复杂的Linux I2C子系统,应该如何下手写驱动呢?
一方面,适配器驱动可能是Linux内核本身还不包含的;另一方面,挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的。因此,工程师要实现的主要工作如下。
-
提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。
-
提供I2C适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。
-
实现I2C设备驱动中的i2c_driver接口,用具体设备yyy的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、suspend、resume和id_table指针。
-
实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等;如果是声卡,就实现ALSA驱动。
上述工作中前两个属于I2C总线驱动,后两个属于I2C设备驱动。实际上前两个工作已经由芯片厂商做好了,我们主要做后两个工作
设备驱动
以正点原子的ap3216c为例
修改设备树
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 子节点
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 节点,这里将 UART4_TXD 和 UART4_RXD 这两个 IO 分别 复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0。
在 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 开发板同样没有此器件,所以也要将其删除掉
diff --git a/arch/arm/boot/dts/imx6ull-alientek-emmc.dts b/arch/arm/boot/dts/imx6ull-alientek-emmc.dts
index de41a93d..6cabc4b4 100644
--- a/arch/arm/boot/dts/imx6ull-alientek-emmc.dts
+++ b/arch/arm/boot/dts/imx6ull-alientek-emmc.dts
@@ -297,18 +297,9 @@
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
- mag3110@0e {
- compatible = "fsl,mag3110";
- reg = <0x0e>;
- position = <2>;
- };
-
- fxls8471@1e {
- compatible = "fsl,fxls8471";
+ ap3216c@1e {
+ compatible = "alientek,ap3216c";
reg = <0x1e>;
- position = <0>;
- interrupt-parent = <&gpio5>;
- interrupts = <0 8>;
};
};
设备驱动代码
#include "ap3216c_reg.h"
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/types.h>
#define AP3216C_NAME "ap3216c"
struct ap3216c_dev {
struct miscdevice miscdev;
void *private_data;
unsigned short ir, als, ps;
};
static struct ap3216c_dev ap3216cdev;
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char buf[6];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
i2c_smbus_read_i2c_block_data(client, AP3216C_IRDATALOW, 6, buf);
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);
}
static int ap3216c_open(struct inode *inode, struct file *filp)
{
struct i2c_client *client = NULL;
filp->private_data = &ap3216cdev;
client = (struct i2c_client *)ap3216cdev.private_data;
/* 初始化AP3216C */
i2c_smbus_write_byte_data(client, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */
mdelay(50); /* AP3216C复位最少10ms */
i2c_smbus_write_byte_data(client, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
return 0;
}
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;
}
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,
};
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
printk("%s(): probe enter\n", __func__);
ap3216cdev.miscdev.minor = MISC_DYNAMIC_MINOR;
ap3216cdev.miscdev.name = AP3216C_NAME;
ap3216cdev.miscdev.fops = &ap3216c_ops;
ret = misc_register(&ap3216cdev.miscdev);
ap3216cdev.private_data = client;
i2c_set_clientdata(client, &ap3216cdev);
printk("%s(): probe exit\n", __func__);
return ret;
}
static int ap3216c_remove(struct i2c_client *client)
{
struct ap3216c_dev *dev = i2c_get_clientdata(client);
misc_deregister(&dev->miscdev);
return 0;
}
// clang-format off
static const struct i2c_device_id ap3216c_id[] = {
{ "ap3216c", 0 },
{ /* Sentinel */ }
};
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = AP3216C_NAME,
.of_match_table = ap3216c_of_match,
},
.id_table = ap3216c_id,
};
// clang-format on
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wtp");