I2C协议
I2C协议参考博文深入浅出理解I2C协议中的介绍
I2C总线设备驱简要
I2C 总线设备驱动框架包括以下几个部分:
- I2C 核心;基于总线设备驱动框架,用于管理 I2C 适配器驱动(i2c_adapter)、 I2C 设备驱动(i2c_driver)、 I2C设备(i2c_client),此部分由 linux内核提供
- I2C 适配器驱动(i2c_adapter);又叫 I2C总线控制器驱动,用于驱动 I2C控制器,此部分一般由主控芯片厂家提供
- I2C 设备(i2c_client);又叫 I2C客户端,用于描述使用I2C总线的设备的信息,一般在设备树中编写
- I2C 设备驱动 (i2c_driver):用于驱动 I2C设备,一般移植厂家驱动或自己编写
- I2C-dev;将I2C总线虚拟成字符设备,使应用层能直接访问 I2C总线,由 Linux内核提供
I2C 适配器
对象 struct i2c_adapter 表示一个 I2C 适配器,其核心成员如下:
//I2C 适配器操作函数集合,编写I2C适配器驱动时重点在此函数集合的实现
const struct i2c_algorithm *algo;
//适配器编号,使用 i2c_add_adapter 注册时由系统自动分配
int nr;
//超时时间
int timeout;
//重试次数
int retries;
//class 表示该 I2C bus 支持哪些类型的 I2C 从设备,部分驱动程序会检查该成员以确保适配器能操作设备
//具体的类型包括:
// I2C_CLASS_HWMON :硬件监控类,如lm_sensors等;
// I2C_CLASS_DDC :DDC是数字显示通道(Digital Display Channel)的意思,通常用于显示设备信息的获取;
// I2C_CLASS_SPD :存储类的模组;
// I2C_CLASS_DEPRECATED :不支持自动探测。
unsigned int class;
//适配器的名字
char name[48];
//该适配器设备对应的 device ,其中 of_node 节点作为I2C总线的节点,控制器注册过程中会解析其节点中的I2C设备(i2c_client)节点
struct device dev;
向系统添加和删除 I2C 适配器的函数:
//添加 I2C 适配器驱动,其中 i2c_add_numbered_adapter 需要指定适配器编号,在向系统添加适配器驱动的过程中会自动解析相应设备树节点下的子节点,并将其转换为 struct i2c_client 对象
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
//删除 I2C 适配器驱动
void i2c_del_adapter(struct i2c_adapter * adap)
I2C 设备
对象 struct i2c_client 表示一个 I2C 设备,它用来描述的是 I2C 设备的信息,类似于 platform 总线的 struct platform_device ,其核心成员如下:
//从机地址
unsigned short addr;
//名称
char name[I2C_NAME_SIZE];
//设备所使用的 I2C 适配器
struct i2c_adapter *adapter;
//继承的 device 对象
struct device dev;
//中断资源,iic匹配时会获取设备树描述的中断
int irq;
在驱动中通常通过设备树来描述一个 i2c_client
也可以使用下列函数创建一个 i2c_client :
struct i2c_adapter *i2c_get_adapter(int nr)
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
在驱动开发中通常使用设备树来描述一个 i2c_client,不过也可以使用下列函数创建一个 i2c_client :
//通过适配器编号获取适配器
struct i2c_adapter *i2c_get_adapter(int nr)
//在此适配器下创建 i2c_client(创建完成后自动注册到I2C总线)
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
同样页可以通过下列函数注销一个 i2c_client :
//注销i2c_client(注销过程中会自动进行删除)
void i2c_unregister_device(struct i2c_client *client)
//释放控制器
void i2c_put_adapter(struct i2c_adapter *adap)
I2C 驱动
对象 struct i2c_driver 表示一个 I2C 设备驱动,它用来描述的是 I2C 设备的驱动信息,类似于 platform 总线的 struct platform_driver ,其核心成员如下:
//设备和驱动匹配成功执行函数
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
//设备或驱动匹配卸载执行函数
int (*remove)(struct i2c_client *);
//继承 device_driver,设备树匹配便是依据 driver 中的参数完成
struct device_driver driver;
//传统的 ID 匹配表,即使使用设备树后此参数也不能为NULL
const struct i2c_device_id *id_table;
如下是向系统添加和删除 I2C 设备驱动的函数:
//注册 I2C 设备驱动
int i2c_add_driver(struct i2c_driver *driver)
//注销 I2C 设备驱动
void i2c_del_driver(struct i2c_driver *driver)
I2C传输数据
I2C 数据包
在 I2C 设备框架中使用 struct i2c_msg 数组来描述 I2C 适配器要传输的数据,其主要成员如下:
//从机地址
__u16 addr;
//传输标志,有如下标志:
// I2C_M_RD :从从机读数据
// I2C_M_TEN :地址长度为10位
// I2C_M_DMA_SAFE :缓冲区支持 DMA
// I2C_M_RECV_LEN :接收的第一个 byte 表示长度
// I2C_M_NO_RD_ACK :不产生 ACK
// I2C_M_IGNORE_NAK :忽略 NAK
// I2C_M_NOSTART :不产生开始信号
// I2C_M_STOP :产生停止信号
_u16 flags;
//消息长度
__u16 len;
//消息缓冲区
__u8 *buf;
I2C传输数据
I2C 设备可以使用i2c_transfer函数传输数据包,另外内核中页提供了其他的数据传输函数,不过基本都是基于i2c_transfer进行封装的。
/**
* adap I2C 适配器
* msgs 描述需要传输的数据,通常是一个数组
* num msg 数量,或者说msgs数组的元素个数
* 返回负值失败,返回其他非负值,发送的 msgs 数量
* 此函数在连续发送多个数据包时如果设置了 I2C_M_NOSTART ,在发送数据包的最后 1byte 时不会等待 ACK 信号,
* 可能会导致发送失败
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
I2C适配器驱动注册过程
下面以i2c_add_adapter函数的流程简述I2C适配器驱动的注册过程
int i2c_add_adapter(struct i2c_adapter *adapter)
//通过I2C控制器的别名获取I2C适配器的编号,获取成功后调用__i2c_add_numbered_adapter注册I2C适配器驱动
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
//动态分配一个I2C适配器编号
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
adapter->nr = id;
//调用i2c_register_adapter注册I2C适配器驱动
return i2c_register_adapter(adapter);
//设置适配器设备名称、所属总线、设备类型,这里依旧将I2C适配器挂接在I2C总线上,只不过设备类型为i2c_adapter_type,在设备驱动匹配过程中会自动忽略I2C总线上设备类型为i2c_adapter_type的设备
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
//通过adap->dev.of_node设备树节点的子节点内容创建i2c_client
of_i2c_register_devices(adap);
//便利adap->dev.of_node节点或adap->dev.of_node下的i2c-bus节点
bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
if (!bus)
bus = of_node_get(adap->dev.of_node);
for_each_available_child_of_node(bus, node) {
//解析一个设备树节点,创建i2c_client
of_i2c_register_device(adap, node);
//将设备树描述的内容转换为i2c_board_info
of_i2c_get_board_info(&adap->dev, node, &info);
//利用设备树转换出来的i2c_board_info创建i2c_client
i2c_new_device(adap, &info);
}
I2C设备驱动匹配过程
i2c_client注册过程
注册i2c_client的流程如下:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
i2c_new_client_device(adap, info);
//分配内存i2c_client
client = kzalloc(sizeof *client, GFP_KERNEL);
//初始化i2c_client
client->adapter = adap;
client->dev.platform_data = info->platform_data;
client->flags = info->flags;
client->addr = info->addr;
client->init_irq = info->irq;
if (!client->init_irq)
client->init_irq = i2c_dev_irq_from_resources(info->resources,
info->num_resources);
strlcpy(client->name, info->type, sizeof(client->name));
//验证地址是否合法
status = i2c_check_addr_validity(client->addr, client->flags);
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
//检查地址是否与总线上已经存在的地址冲突
status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));
if (status)
goto out_err;
//注册device到I2C总线
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = of_node_get(info->of_node);
client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client, info);
status = device_register(&client->dev);
if (status)
goto out_free_props;
i2c_driver注册过程
i2c_driver的注册过程如下:
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
//注册driverI2C总线
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
INIT_LIST_HEAD(&driver->clients);
res = driver_register(&driver->driver);
i2c_client和i2c_driver匹配过程
注册 i2c_client或i2c_driver的最后一步都是向I2C总线添加设备或驱动,在向I2C总线添加设备或驱动的过程中便会执行I2C总线的设备驱动匹配函数,其函数的内容如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
//验证dev是否是i2c_client,如果是则进行类型转换并赋给client,否则client为NULL
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
//通过设备树进行设备驱动匹配,若client为NULL会匹配失败
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
//通过id_table进行设备驱动匹配,若client为NULL会匹配失败
driver = to_i2c_driver(drv);
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client)
{
if (!(id && client))
return NULL;
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
在设备树中描述 i2c_client
在那条 I2C 总线下挂载设备就在那条总线的设备树节点下添加对应设备的子节点,节点命名规则“ [标签:]名称[@地址]”,节点内容必须包含 reg 属性和 compatible 属性, reg 属性用于描述设备地址, compatible 属性用于设备和驱动的匹配,如下是在 i2c5 中添加一个 ap3216c 设备节点的示例。
&i2c5 {
//描述I2C控制器引脚,并使能I2C控制器
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c5_pins_a>;
pinctrl-1 = <&i2c5_pins_sleep_a>;
status = "okay";
//描述ap3216c设备
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};