文章目录
- 一、I2C体系架构
- 二、主要的结构体
- 1. i2c_adapter
- 2. i2c_algorithm
- 3. i2c_driver
- 4. i2c_client
- 4.1 方式一:通过I2C bus number静态方式来创建
- 4.2 方式二:通过Device Tree来创建
- 4.3 方式三:直接通过i2c_new_device来创建
- 4.3 方式四:通过用户空间(user-space)手动来创建
- 5. i2c_msg
- 三、参考资料
- 四、验证测试时使用
一、I2C体系架构
主要由三部分组成:
- I2C核心层
提供I2C Adapter、I2C设备驱动的注册和注销方法,I2C Algorithm通信方法,与适配器无关的代码以及探测设备等。 - I2C Adapter控制器驱动
它一般是由芯片原厂来完成编写,根据Device Tree里面的I2C硬件资源来配置新的I2C Adapter,然后将它注册到I2C核心层。 - I2C设备驱动
I2C设备驱动它包含:I2C Driver和I2C Client。i2c_driver会提供该类I2C硬件的驱动方法,I2C Client会提供当前真实I2C物理设备信息,如:I2C设备地址、挂载的哪条I2C总线(I2C Adapter)等。
二、主要的结构体
1. i2c_adapter
一般i2c adapter的注册都是由芯片原厂来完成,它一般存放在kernel\drivers\i2c\busses\
目录下。==i2c_adapter==的核心是i2c_algorithm,主要是它提供了I2C read/write收发数据的时方法。
struct i2c_adapter {
const struct i2c_algorithm *algo; /* 存放I2C传输数据的方法 */
...
int nr; // I2C adapter的编号,默认从0开始。
char name[48]; // adapter的name,如:i2c@21a4000
...
};
下面以kernel\drivers\i2c\busses\i2c-imx.c
注册一个I2C Adapter的为例,简答分析它如何注册的一个adapter的。
设备树里面的compatible
和驱动里面的of_device_id.compatible
匹配成功后,就调用驱动里面的probe函数。
probe函数主要完成如下几个步骤:
- 给i2c adapter申请空间
- 初始化i2c adapter
- 将i2c adapter注册到i2c核心层
struct imx_i2c_struct {
struct i2c_adapter adapter;
...
};
static int i2c_imx_probe(struct platform_device *pdev)
{
...
/* 1.给i2c adapter申请空间 */
i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
/* 2.初始化i2c adapter */
strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
i2c_imx->adapter.owner = THIS_MODULE;
i2c_imx->adapter.algo = &i2c_imx_algo; // 这里就是imx实现的I2C传输数据方法
i2c_imx->adapter.dev.parent = &pdev->dev;
i2c_imx->adapter.nr = pdev->id; // 配置adaper的编号
i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
...
/* 3.将i2c adapter注册到i2c核心层 */
ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
...
}
再进一步分析i2c_add_numbered_adapter
,它是如何注册一个adapter的?
它们的调用关系大致如下:
-
idr_alloc函数:
新的adapter将根据总线number添加到I2C Core的全局变量i2c_adapter_idr中,为以后get对应的adapter做准备。
后面的所有的adapter的注册都会挂载在i2c_adapter_idr
上,最终的结果大致如下:
-
device_register函数:
这里主要是在I2C总线下,注册一个i2c adapter类型的设备。部分代码如下:
device_register
函数注册成功后,会在/sys/bus/i2c/devices
目录下创建对应的文件夹。如下:
device_register
函数非常的关键,在adapter被添加时会导致i2c-dev.c
中的i2cdev_notifier_call
被调用。device_register -->device_add -->blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev);
因为在
i2c_dev_init
有注册一个notifier:static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { struct device *dev = data; switch (action) { case BUS_NOTIFY_ADD_DEVICE: return i2cdev_attach_adapter(dev, NULL); case BUS_NOTIFY_DEL_DEVICE: return i2cdev_detach_adapter(dev, NULL); } return 0; } static struct notifier_block i2cdev_notifier = { .notifier_call = i2cdev_notifier_call, }; static int __init i2c_dev_init(void) { ... res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); ... }
接收到通知后,
i2cdev_attach_adapter
就会被调用,这里创建/dev/i2c-*
字符设备。代码如下:
所以,当成功添加一个i2c adapter后,都会在/dev/
目录下创建i2c-*
设备。结果如下:
-
of_i2c_register_devices函数:
of_i2c_get_board_info
:它会解析dts里面I2C子节点数据。&i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; mag3110@e { compatible = "fsl,mag3110"; reg = <0x0e>; position = <2>; };
i2c_new_device
:它创建一个i2c_client设备,实际对应外围I2C设备。/* 下面是i2c_new_device的调用关系 */ i2c_new_device -->i2c_new_client_device -->i2c_dev_set_name -->device_register static void i2c_dev_set_name(struct i2c_adapter *adap, struct i2c_client *client, struct i2c_board_info const *info) { ... dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), i2c_encode_flags_to_addr(client)); }
i2c_dev_set_name
设置I2C设备的名字,例如:0-000e,0:代表i2c adapter0 (i2c1),000e:代表I2C设备从机地址。device_register
函数会创建该设备,然后我们就可以在/sys/bus/i2c/devices
目录创建对应的目录。
-
i2c_scan_static_board_info函数:
__i2c_board_list
保存了通过i2c_register_board_info
函数静态注册设备信息,然后根据board info信息通过i2c_new_device
来创建一个i2c_client设备。
文件路径:kernel\drivers\i2c\i2c-boardinfo.c
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) { ... list_add_tail(&devinfo->list, &__i2c_board_list); ... }
2. i2c_algorithm
i2c_algorithm
是根据Soc的I2C硬件寄存器来实现read、write通信的方法,适配器需要通过i2c_algorithm提供的通信函数来产生对应的访问时序。所以i2c_adapter中包含i2c_algorithm的指针。
i2c_algorithm
使用master_xfer()
来产生I2C时序,以i2c_msg
为单位,i2c_msg
代表一次传输的数据。
//I2C传输方法
struct i2c_algorithm {
//i2c传输函数指针
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
//smbus传输函数指针
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
...
};
???问题:收发数据的函数master_xfer是如何被调用的???
我们可以通过i2c_master_send
和i2c_master_recv
函数来收发I2C设备数据,从而分析它的调用关系如下:
3. i2c_driver
i2c_dirver
就是i2c标准总线设备驱动模型中的驱动部分,它主要是现实该I2C设备具体初始化、操作函数接口等的实现,等待app程序的对该I2C设备实现业务上面的逻辑。i2c_dirver
这部分的就是需要我们自己的来实现的。
// I2C设备驱动
struct i2c_driver {
...
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
struct device_driver driver; // 驱动的name和匹配device tree
const struct i2c_device_id *id_table; //该设备所支持的设备ID表
};
下面是举例的一个模板代码:
static const struct of_device_id of_match_ids_xxxx[] = {
{ .compatible = "xxxx,xxxx", .data = NULL },
{ /* END OF LIST */ },
};
static const struct i2c_device_id xxxx_ids[] = {
{ "xxxx", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int ap3216c_remove(struct i2c_client *client)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct i2c_driver i2c_xxxx_driver = {
.driver = {
.name = "xxxx",
.of_match_table = of_match_ids_xxxx,
},
.probe = xxxx_probe,
.remove = xxxx_remove,
.id_table = xxxx_ids,
};
static int __init i2c_driver_xxxx_init(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return i2c_add_driver(&i2c_xxxx_driver);
}
static void __exit i2c_driver_xxxx_exit(void)
{
i2c_del_driver(&i2c_xxxx_driver);
}
module_init(i2c_driver_xxxx_init);
module_exit(i2c_driver_xxxx_exit);
MODULE_LICENSE("GPL");
4. i2c_client
i2c_clent
对应真实的物理设备,每个i2c设备都需要一个i2c_client
来描述。i2c_driver
与i2c_client
是一对多的关系,一个i2c_driver
上可以支持多个同类型的i2c_client
。
struct i2c_client {
unsigned short flags; /* 标记,标记相关属性 */
unsigned short addr; /* 芯片地址,7bit地址 */
char name[I2C_NAME_SIZE]; /* i2c设备的名称 */
struct i2c_adapter *adapter; /* 依附的i2c_adapter */
struct device dev; /* 该设备相关属性配置 */
int irq; /* 设备使用的中断号 */
...
};
创建i2c_clent
有四种方式,具体如下:
4.1 方式一:通过I2C bus number静态方式来创建
i2c_client
可以通过i2c_register_board_info
函数来创建,并且会注册到I2C总线。重要提示:这个方式最大的缺陷是就是一定要编译进内核,并且该驱动一定要在i2c adapter驱动之前就要加载,否则会导致该设备未注册从而导致对应的驱动不会被加载。 解决方法:可以使用arch_initcall
将该模块提前被加载。
static int __init i2c_client_ap3216c_init(void)
{
static struct i2c_board_info board_info[] = {
{I2C_BOARD_INFO("ap3216c", 0x1e),},
};
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* register I2C device, 0:代表i2c-0总线 */
i2c_register_board_info(0, board_info, ARRAY_SIZE(board_info));
return 0;
}
arch_initcall(i2c_client_ap3216c_init);
i2c_register_board_info
其实就是添加board info到__i2c_board_list
全局链接,然后在添加i2c adapter时,遍历该链接将挂载当前adapter的设备进行创建。(备注:上面在介绍adapter的时候已经分析过这块,如需要可以向上回看。)
4.2 方式二:通过Device Tree来创建
这种方式是目前使用最多的方式,将需要的I2C设备信息添加到对应的I2C总线下。一般只需要包括2个单元:
compatible
:该name将被用来匹配i2c driver。reg
:挂载的I2C Slave设备地址,这里是7Bit地址,不包括读写位。
(备注:上面在介绍adapter的时候已经分析过这块,如需要可以向上回看。)
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
};
4.3 方式三:直接通过i2c_new_device来创建
其实其它方式都会最终会调用i2c_new_device
来创建I2C Client设备,我们可以通过它来直接创建。使用该函数需要知道i2c_adapter
,我们可以通过i2c_get_adapter
函数来获取。具体参考代码如下:
static struct i2c_client *ap3216c_client;
static int __init i2c_client_ap3216c_init(void)
{
struct i2c_adapter *adapter;
static struct i2c_board_info board_info = {
I2C_BOARD_INFO("ap3216c", 0x1e),
};
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* register I2C device, 0:代表i2c-0总线 */
adapter = i2c_get_adapter(0);
ap3216c_client = i2c_new_device(adapter, &board_info);
i2c_put_adapter(adapter);
return 0;
}
static void __exit i2c_client_ap3216c_exit(void)
{
i2c_unregister_device(ap3216c_client);
}
module_init(i2c_client_ap3216c_init);
module_exit(i2c_client_ap3216c_exit);
MODULE_LICENSE("GPL");
4.3 方式四:通过用户空间(user-space)手动来创建
调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。
#创建一个i2c_client, .name = "ap3216c", .addr=0x1e, .adapter是i2c-0
echo ap3216c 0x1e > /sys/bus/i2c/devices/i2c-0/new_device
#删除一个i2c_client
echo 0x1e > /sys/bus/i2c/devices/i2c-0/delete_device
这里所使用的new_device
和delete_device
是在添加i2c adapter时创建的,i2c_adapter_type
包含了该设备的相关属性。
下面代码可以看到i2c_adapter_type
就包含了new_device
和delete_device
相关属性,如果使用文件系统操作new_device
节点,那么就会调用i2c_sysfs_new_device
->i2c_new_client_device
函数来创建i2c_client设备。
static ssize_t
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client;
...
client = i2c_new_client_device(adap, &info);
...
}
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
static struct attribute *i2c_adapter_attrs[] = {
&dev_attr_name.attr,
&dev_attr_new_device.attr,
&dev_attr_delete_device.attr,
NULL
};
ATTRIBUTE_GROUPS(i2c_adapter);
struct device_type i2c_adapter_type = {
.groups = i2c_adapter_groups,
.release = i2c_adapter_dev_release,
};
EXPORT_SYMBOL_GPL(i2c_adapter_type);
下面是i2c adapter创建成功后,进入文件系统后我们可以看到的相关属性的节点。
5. i2c_msg
在Linux驱动里面使用i2c收发函数最终都会调用到i2c_transfer
,它传输数据的单元基本都是i2c_msg
。
//I2C传输数据结构体,代表一个消息数据
struct i2c_msg {
__u16 addr; //设备地址
__u16 flags; //标志
__u16 len; //消息长度
__u8 *buf; //消息数据
};
下面是是直接使用i2c_msg
来收发数据的伪代码如下:
struct i2c_client *xxx_client= NULL;
static int i2c_read_bytes(unsigned char *reg, char *data, int data_len)
{
struct i2c_msg msgs[] = {
{
.addr = xxx_client->addr,
.flags = 0,
.len = 1,
.buf = reg,
},{
.addr = dev_addr,
.flags = I2C_M_RD,
.len = data_len,
.buf = data,
}
};
if (i2c_transfer(xxx_client->adapter, msgs, 2) < 0)
return -1;
else
return 0;
}
static int i2c_write__bytes(unsigned char *buf, int len)
{
struct i2c_msg msg[] = {
{
.addr = xxx_client->addr,
.flags = 0,
.len = len,
.buf = buf,
}
};
if (i2c_transfer(xxx_client->adapter, msg, 1) < 0)
return -1;
else
return 0;
}
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
xxx_client = client;
return 0;
}
三、参考资料
1:Linux驱动之I2C驱动架构
https://blog.51cto.com/u_11866419/4935228
2:Linux i2c 学习 -2 i2c adapter 注册
https://blog.csdn.net/Royal2012/article/details/116081632
四、验证测试时使用
测试验证四种创建i2c client的方法代码:
参考代码下载地址:
正在上传…