电子说
Linux内核将 I2C 驱动分为两部分:
I2C 总线驱动, I2C总线驱动就是SOC的 I2C控制器驱动,也叫做 I2C适配器驱动。
I2C 设备驱动, I2C设备驱动就是针对具体的 I2C设备而编写的驱动。
I2C框架下的几个重要成员
1. I2C总线
I2C总线结构体在driversi2ci2c-core.c中定义如下:
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, };
I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动的probe函数。
形如:
i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关。
2. I2C驱动
i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容, i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:
struct i2c_driver { unsigned int class; /* Notifies the driver that a new bus has appeared. You should avoid * using this, it will be removed in a near future. */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; /* Standard driver model interfaces */ int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); /* driver model interfaces that don't relate to enumeration */ void (*shutdown)(struct i2c_client *); /* Alert callback, for example for the SMBus alert protocol. * The format and meaning of the data value depends on the protocol. * For the SMBus alert protocol, there is a single bit of data passed * as the alert response's low bit ("event flag"). */ void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions * with the device. */ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver; const struct i2c_device_id *id_table; /* Device detection callback for automatic device creation */ int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients; };
重点成员如下:
int (*probe)(struct i2c_client *, const struct i2c_device_id *)
当 I2C设备和驱动匹配成功以后 probe函数就会执行。
struct device_driver driver device_driver
驱动结构体,如果使用设备树的话,需要设置device_driver的of_match_table成员变量,也就是驱动的兼容(compatible)属性。
const struct i2c_device_id *id_table
id_table 是传统的、未使用设备树的设备匹配 ID表
3. I2C设备
I2C设备结构体i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:
struct i2c_client { unsigned short flags; /* div., see below */ unsigned short addr; /* chip address - NOTE: 7bit */ /* addresses are stored in the _LOWER_ 7 bits */ char name[I2C_NAME_SIZE]; struct i2c_adapter *adapter; /* the adapter we sit on */ struct device dev; /* the device structure */ int irq; /* irq issued by device */ struct list_head detected; #if IS_ENABLED(CONFIG_I2C_SLAVE) i2c_slave_cb_t slave_cb; /* callback for slave mode */ #endif };
重点成员如下:
flags:标志
addr:芯片地址,7 位,存在低 7 位
flagsname[I2C_NAME_SIZE]:名字
adapter:对应的 I2C 适配器
dev:设备结构体
irq:中断
一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。
4. I2C适配器
经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C设配器,I2C设配器对应的就是SOC上的I2C控制器。
Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter, i2c_adapter 结构体定义在 include/linux/i2c.h 文件中,结构体内容如下:
/* * i2c_adapter is the structure used to identify a physical i2c bus along * with the access algorithms necessary to access it. */ struct i2c_adapter { struct module *owner; unsigned int class; /* classes to allow probing for */ const struct i2c_algorithm *algo; /* the algorithm to access the bus *//* 总线访问算法 */ void *algo_data; /* data fields that are valid for all devices */ struct rt_mutex bus_lock; int timeout; /* in jiffies */ int retries; struct device dev; /* the adapter device */ int nr; char name[48]; struct completion dev_released; struct mutex userspace_clients_lock; struct list_head userspace_clients; struct i2c_bus_recovery_info *bus_recovery_info; const struct i2c_adapter_quirks *quirks; };
重点成员如下:
const struct i2c_algorithm *algo
I2C 适配器与 IIC 设备进行通信的方法。
i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中,内容如下:
struct i2c_algorithm { /* If an adapter algorithm can't do I2C-level access, set master_xfer to NULL. If an adapter algorithm can do SMBus access, set smbus_xfer. If set to NULL, the SMBus protocol is simulated using common I2C messages */ /* master_xfer should return the number of messages successfully processed, or a negative value on error */ int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); /* To determine what the adapter supports */ u32 (*functionality) (struct i2c_adapter *); #if IS_ENABLED(CONFIG_I2C_SLAVE) int (*reg_slave)(struct i2c_client *client); int (*unreg_slave)(struct i2c_client *client); #endif };
重点成员如下:
master_xfer:I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。
smbus_xfer:SMBUS 总线的传输函数
I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm中的master_xfer函数。完成以后通过 i2c_add_numbered_adapter或 i2c_add_adapter这两个函数向系统注册设置好的 i2c_adapter。
这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。
5. 小结
I2C驱动有4个重要的东西:I2C总线、I2C驱动、I2C设备、I2C设备器。
I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等。
I2C驱动:对应的就是I2C设备的驱动程序。
I2C设备:是具体硬件设备的一个抽象。
I2C适配器:用于I2C驱动和I2C设备间的通用,是SOC上I2C控制器的一个抽象。
Linux I2C总线的运行机制:
注册I2C驱动
将I2C驱动添加到I2C总线的驱动链表中
遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数
i2c_device_probe函数会调用I2C驱动的probe函数
I2C驱动简单编写流程
一般 SOC 的 I2C总线驱动都是由半导体厂商编写的,这个不需要用户去编写。因此 I2C 总线驱动对于 SOC使用者来说是被屏蔽掉的,我们只要专注于 I2C 设备驱动即可。除非你是在半导体公司上班,工作内容就是写 I2C 适配器驱动。
i2c_driver类似platform_driver,是我们编写I2C设备驱动重点要处理的内容,i2c_driver在上面已经介绍了其结构体的具体内容。
对于我们 I2C 设备驱动编写人来说,重点工作就是构建i2c_driver,构建完成以后需要向Linux内核注册这个i2c_driver。
那么如何注册呢?
使用下面的这个函数:
int i2c_register_driver(struct module *owner,struct i2c_driver *driver)函数参数和返回值含义如下:
owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
返回值:0,成功;负值,失败。
另外 i2c_add_driver 也常常用于注册 i2c_driver, i2c_add_driver 是一个宏,定义如下:
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的 i2c_driver。
设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:
void i2c_del_driver(struct i2c_driver *driver);函数参数和返回值含义如下:
driver:要注销的 i2c_driver。
返回值:无。
例程框架:
/* i2c 驱动的 probe 函数 */ static int xxx_probe(struct i2c_client *client, { /* 函数具体程序 */ return 0; } /* i2c 驱动的 remove 函数 */ static int xxx_remove(struct i2c_client *client) { /* 函数具体程序 */ return 0; } /* 传统匹配方式 ID 列表 */ static const struct i2c_device_id xxx_id[] = { {"xxx", 0}, {} }; /* 设备树匹配列表 */ static const struct of_device_id xxx_of_match[] = { { .compatible = "xxx" }, { /* Sentinel */ } }; /* i2c 驱动结构体 */ static struct i2c_driver xxx_driver = { .probe = xxx_probe, .remove = xxx_remove, .driver = { .owner = THIS_MODULE, .name = "xxx", .of_match_table = xxx_of_match, }, .id_table = xxx_id, }; /* 驱动入口函数 */ static int __init xxx_init(void) { int ret = 0; ret = i2c_add_driver(&xxx_driver); return ret; } /* 驱动出口函数 */ static void __exit xxx_exit(void) { i2c_del_driver(&xxx_driver); } module_init(xxx_init); module_exit(xxx_exit);
当I2C设备和I2C驱动匹配成功以后probe函数就会执行,这些和platform驱动一样,probe函数里面基本就是标准的字符设备驱动那一套了。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !