第三十节、i2c总线实现client设备和驱动
本节用于介绍i2c总线实现client设备和驱动
一、Linuxl2C驱动框架简介
Linux中的l2C也是按照平台总线模型设计的,既然也是按照平台总线模型设计的,是不是也分为一个device 和一个driver呢?但是I2C这里的device 不叫 device,而是叫client。在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于l2C而言,不需要虚拟出一条总线,直接使用l2C总线即可。
同样,我们也是先从非设备树开始,先来看一下,在没有设备树之前我们是怎么实现的l2C的device部分,也就是client部分。然后在学习有了设备树之后,我们的client是怎么编写的。按照Linux的发展路径来学习。
在没有使用设备树之前,我们使用的是i2c_board_info这个结构体来描述一个I2C设备的,i2c_board_info这个结构体如下:
文件位置路径:include/uapi/linux/i2c-dev.h
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //I2C设备的名字
unsigned short flags; //标志
unsigned short addr; //I2C器件地址
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
const struct property_entry *properties;
const struct resource *resources;
unsigned int num_resources;
int irq;
};
在这个结构体里面,type和addr这两个成员变量是必须要设置的,一个是l2C设备的名字,,这个名字就是用来进行匹配用的,一个是l2C设备的器件地址。也可以使用宏:
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的type和addr这两个成员变量。
I2C设备和驱动的匹配过程是由I2C核心来完成的,在Linux源码的 drivers/i2c/i2c-core-base.c就是l2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数,如下:
1、i2c_get_adapter函数
作用:获得一个l2C适配器
文件位置路径:include/linux/i2c.h
实际函数位置路径:drivers/i2c/i2c-core-base.c
struct i2c_adapter *i2c_get_adapter(int nr)
{
struct i2c_adapter *adapter;
mutex_lock(&core_lock);
adapter = idr_find(&i2c_adapter_idr, nr);
if (!adapter)
goto exit;
if (try_module_get(adapter->owner))
get_device(&adapter->dev);
else
adapter = NULL;
exit:
mutex_unlock(&core_lock);
return adapter;
}
//参数:
//nr:要获得的哪个I2C适配器的编号
//返回值:
//失败返回NULL
2、i2c_new_device函数
作用:把I2C适配器和I2C器件关联起来。
文件位置路径:include/linux/i2c.h
实际函数位置路径:drivers/i2c/i2c-core-base.c
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
//参数:
//adap:I2C适配器
//info:i2c_board_info结构体的指针
//返回值:
//失败返回NULL
3、i2c_put_adapter函数
作用:释放l2C适配器
文件位置路径:include/linux/i2c.h
实际函数位置路径:drivers/i2c/i2c-core-base.c
void i2c_put_adapter(struct i2c_adapter *adap)
{
if (!adap)
return;
put_device(&adap->dev);
module_put(adap->owner);
}
//参数:
//adap:要释放的I2C适配器
4、i2c_unregister_device函数
作用:注销一个client
文件位置路径:include/linux/i2c.h
实际函数位置路径:drivers/i2c/i2c-core-base.c
void i2c_unregister_device(struct i2c_client *client)
//参数:
//client:i2c_client的指针
在使用了设备树以后,就不用这么复杂了,使用设备树的时候只要在对应的I2C节点下创建相应设备的节点即可,比如我想添加一个RTC的设备,我就可以在对应的I2C的节点下这样写,如下图所示:
&i2c0 {
status = "okay";
rtc1:rtc@32 {
compatible = "rx8010";
reg = <0x32>;
status = "okay";
};
};
这些设备我们可以在文件系统的/sys/bus/i2c/devices/路径下看到我们所有的I2C设备的节点
root@localhost:~# ls /sys/bus/i2c/devices/
0-0052 0-0053 1-0032 i2c-0 i2c-1 i2c-2
然后我们再来看driver部分。不管是使用设备树还是非设备树,driver部分就比较复杂了。和注册一个杂项设备或者是字符设备的套路一样,我们也是要先定一个一个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 *);
/* New driver model interface to aid the seamless removal of the
* current probe()'s, more commonly unused than used second parameter.
*/
int (*probe_new)(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").
* For the SMBus Host Notify protocol, the data corresponds to the
* 16-bit payload data reported by the slave device acting as master.
*/
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
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;
bool disable_i2c_core_irq_mapping;
};
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)
在驱动注册之前i2c_driver结构体需要被正确地初始化,有4个成员要求必须被初始化,其中id_table不管用不用设备树都要被初始化,否则不能匹配成功:
static struct i2c_driver xxx_driver = {
.driver = {
.name ="xxx",
},
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_table,
};
初始化完成以后就是把i2c_driver注册进内核,注册进内核我们使用是的是i2c_add_driver
1、i2c_add_driver函数
作用:注册一个i2c驱动
函数原型:
文件位置路径:include/linux/i2c.h
实际函数位置路径:drivers/i2c/i2c-core-base.c
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
//参数:
//driver:struct i2c_driver的指针
//返回值:
//失败返回负值
2、i2c_del_driver函数
作用:删除一个i2c驱动
函数原型:
文件位置路径:include/linux/i2c.h
实际函数位置路径:drivers/i2c/i2c-core-base.c
void i2c_del_driver(struct i2c_driver *driver)
{
i2c_for_each_dev(driver, __process_removed_driver);
driver_unregister(&driver->driver);
pr_debug("driver [%s] unregistered\n", driver->driver.name);
}
EXPORT_SYMBOL(i2c_del_driver);
//参数:
//driver:struct i2c_driver的指针
//返回值:
//失败返回负值