1. Linux IIC驱动框架
Linux驱动和设备隔离,驱动分层,IIC驱动框架分为两部分
- IIC 总线驱动,也就是SOC的IIC控制器驱动,也就适配器驱动
- IIC 设备驱动,指具体的IIC设备驱动
1.1 IIC总线驱动
platform是虚拟总线,针对没有总线的设备实现,总线、设备、驱动框架,IIC不用虚拟,可直接用总线bus。IIC总线驱动有两个重要数据结构:i2c_adapter, i2c_algorithm。i2c_adapter结构体定义如下:include/linux/i2c.h
struct i2c_adapter {
。。。
const struct i2c_algortithm *algo; /*总线访问算法*/
。。。
}
通过iic适配器操作设备的API函数都在i2c_algortithm
结构体中,i2c_algortithm
定义在 include/linux/i2c.h
struct i2c_algorithm {
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适配器的数据传输函数,完成和i2c设备的数据通信- smbus_xfer 是smbus的数据传输函数
I2C总线驱动,主要就是初始化i2c_adapter结构体变量,然后设置master_xfer数据传输函数,完成后通过向系统注册i2c_adapter,注册函数如下:
int i2c_add_adapter(struct i2c_adapter *adapter) //使用动态总线号
int i2c_add_number_adapter(struct i2c_adapter *adap) //使用静态总线号
- 参数 adapter/adap 表示要注册到系统中的适配器
- 返回值 0 成功 负值 失败
删除IIC适配器函数:
void i2c_del_adapter(struct i2c_adapter *adap);
1.2 IIC设备驱动
重点就是两个结构体,i2c_client和i2c_driver。
- i2c_client 主要描述设备信息,inlcude/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
};
每检测到IIC设备就会分配一个i2c_client,这个结构体和设备一一对应
- 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;
};
probe: IIC设备和驱动匹配成功以后执行probe函数
device_driver :驱动结构体,如果用设备树,设置of_match_table成员,表示兼容属性
id_table:没有使用设备树,表示设备匹配列表
- i2c_driver结构体构建完成后,需要向linux内核注册,注册函数:
int i2c_register_driver(struct module *owner,struct i2c_driver *driver)
参数 owner 一般为 THIS_MODULE
driver:需要注册的i2c_driver结构体
返回值 0 成功 负值 失败
- 注销IIC设备驱动,函数
void i2c_del_driver(struct i2c_driver *driver)
1.3 设备和驱动的匹配过程
IIC设备和驱动的匹配是有IIC核心完成的,drivers/i2c/i2c-core.c,提供了一些和具体硬件无关的API函数,i2c_adapter注册和注销函数,i2c_driver注册和注销函数。IIC设备和驱动的匹配是由I2C总线完成的,结构体i2c_bus_type,定义在drivers/i2c/i2c-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_device_match 是IIC设备和驱动的匹配函数,定义如下
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
of_driver_match_device : 设备树设备和驱动的匹配,比较I2C设备节点的compatible属性和 of_device_id中的compatible是否相等,相等则匹配
i2c_match_id : 用于传统无设备树的匹配,比较设备名字和i2c_device_id的name字段是否相等,相等则匹配
2. I2C适配器驱动分析
I2C总线驱动其实就是SOC的I2C控制器驱动,一般SOC厂商已经写好。在imx6ull.dtsi文件中找到I.MX6U的I2C1控制节点,
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
通过compatible属性可以再linux源码中找到对应的驱动文件,I.MX6U的I2C适配器驱动文件为drivers/i2c/busser/i2c-imx.c
static struct platform_device_id imx_i2c_devtype[] = {
{
.name = "imx1-i2c",
.driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,
}, {
.name = "imx21-i2c",
.driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);
static const struct of_device_id i2c_imx_dt_ids[] = {
{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);
。。。
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = i2c_imx_dt_ids,
.pm = IMX_I2C_PM,
},
.id_table = imx_i2c_devtype,
};
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);
static void __exit i2c_adap_imx_exit(void)
{
platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Darius Augulis");
MODULE_DESCRIPTION("I2C adapter driver for IMX I2C bus");
MODULE_ALIAS("platform:" DRIVER_NAME);
从以上可以看出I2C适配器驱动是个标准的platform驱动,当设备和驱动匹配完成后i2c_imx_probe函数就会执行,主要是完成I2C适配器的初始化。
i2c_imx_probe 函数主要的工作就是一下两点:
①、初始化 i2c_adapter,设置 i2c_algorithm 为 i2c_imx_algo,最后向 Linux 内核注册
i2c_adapter。
②、初始化 I2C1 控制器的相关寄存器。
3. I2C设备驱动编写流程
3.1 未使用设备树,I2C设备信息描述
未使用设备树的时候用i2c_board_info 结构体描述具体的I2C设备,定义如下
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
int irq;
};
type : I2C设备名字
addr:I2C器件地址
3.2 使用设备树,I2C设备信息描述
在使用设备树的情况下,I2C设备信息通过创建相应的节点添加,在i2c1节点下创建子节点,描述具体i2c设备的有关信息