I2C驱动框架+I2C设备驱动编写方法

I2C驱动框架

1、主要对象

1. I2C总线

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match, //匹配规则
	.probe		= i2c_device_probe, //匹配成功后的行为
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
复制代码

I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操做,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,若是匹配就调用i2c_device_prob函数,进而调用I2C驱动的probe函数
特别提示:i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关。node

2. I2C驱动

struct i2c_driver {
	int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
	struct device_driver driver; //代表这是一个驱动
	const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
	int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
	const unsigned short *address_list; //设备地址
	struct list_head clients; //设备链表
};
复制代码

3. I2C设备

struct i2c_client {
	unsigned short addr; //设备地址
	char name[I2C_NAME_SIZE]; //设备名称
	struct i2c_adapter *adapter; //适配器,I2C控制器。
	struct i2c_driver *driver; //设备对应的驱动
	struct device dev; //代表这是一个设备
	int irq; //中断号
	struct list_head detected; //节点
};
复制代码

4. I2C适配器

I2C适配器是什么?算法

通过上面的介绍,知道有I2C驱动和I2C设备,咱们须要经过I2C驱动去和I2C设备通信,这其中就须要一个I2C适配器,I2C适配器对应的就是SOC上的I2C控制器。数组

struct i2c_adapter {    //适配器
	unsigned int id; //适配器的编号
	const struct i2c_algorithm *algo; //算法,发送时序
	struct device dev; //代表这是一个设备
};
复制代码

5. I2C算法

I2C算法对应的就是如何发送I2C时序数据结构

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);
};
复制代码

小结

I2C驱动有4个重要的东西,I2C总线、I2C驱动、I2C设备、I2C适配器框架

  • I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等
  • I2C驱动:对应的就是I2C设备的驱动程序
  • I2C设备:是具体硬件设备的一个抽象
  • I2C适配器:用于I2C驱动和I2C设备间的通信,是SOC上I2C控制器的一个抽象

I2C总线的运行机制 函数

I2C驱动框架能够分为四部分,I2C核心、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核心中。

  • I2C核心维护着一条I2C总线,提供了注册I2C设备、I2C驱动、I2C适配器的接口
  • I2C总线维护着一条设备链表和驱动链表,当向I2C核心层注册设备时,会将其添加到总线的设备链表中,而后遍历总线上的驱动链表,查看两者是否匹配,若是匹配就调用驱动的probe函数。
  • 当注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,而后遍历总线的设备链表,查看两者是否匹配,若是匹配就调用驱动的probe函数。
  • 在I2C驱动程序中,经过I2C适配器中的算法向I2C硬件设备传输数据。

2、内核源码分析

1. 注册I2C设备

注册I2C设备能够经过i2c_new_device,此函数会生成一个i2c_client,指定对应的总线为I2C总线,而后向总线注册设备。源码分析

struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) {
    struct i2c_client *client;
    client = kzalloc(sizeof *client, GFP_KERNEL);
        
    client->dev.bus = &i2c_bus_type; //指定I2C总线
    
    device_register(&client->dev); //向总线注册设备
    return client;
}
复制代码

device_register首先会将设备添加到总线的设备链表中,而后遍历总线的驱动链表,判断设备和驱动是否匹配,若是匹配就调用驱动的probe函数:ui

//第一层
int device_register(struct device *dev) {
    device_add(dev);
}
//第二层
int device_add(struct device *dev) {
    /* 添加设备到总线的设备链表中 */
    bus_add_device(dev); 
    /* 遍历总线的驱动进行操做 */
    bus_probe_device(dev); 
}
//第三层
int bus_add_device(struct device *dev) {
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
void bus_probe_device(struct device *dev) {
    device_attach(dev); 
}
//第四层
int device_attach(struct device *dev) {
    /* 遍历总线的驱动链表每一项,而后调用__device_attach */
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
//第五层
static int __device_attach(struct device_driver *drv, void *data)
{
    /* 判断设备和驱动是否匹配 */
	if (!driver_match_device(drv, dev)) 
		return 0;
    /* 匹配成功 */
	return driver_probe_device(drv, dev);
}
//第六层
static inline int driver_match_device(struct device_driver *drv, struct device *dev) {
    /* 调用了总线的match函数,这里的总线在注册i2c设备时以及设置为I2C总线了 */
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

int driver_probe_device(struct device_driver *drv, struct device *dev) {
    really_probe(dev, drv);
}

//第七层
struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match, //匹配规则
	.probe		= i2c_device_probe, //匹配后的行为
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
/* 这里调用了i2c_device_match函数 * i2c_device_match会经过I2C驱动的id_table中每一的name和I2C设备的name进行匹配 */
 static int i2c_device_match(struct device *dev, struct device_driver *drv) {
    i2c_match_id(driver->id_table, client);
}

static int really_probe(struct device *dev, struct device_driver *drv) {
    i2c_device_probe(dev)
}
//第八层
 static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client) {
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0) //字符串匹配
			return id;
		id++;
	}
	return NULL;
}

static int i2c_device_probe(struct device *dev) {
    /* 调用驱动的probe函数 */
    driver->probe(client, i2c_match_id(driver->id_table, client));
}
复制代码

2. 注册I2C驱动

与注册设备驱动过程基本一致spa

//第一层
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) {
    driver->driver.bus = &i2c_bus_type; //指定I2C总线
    
    driver_register(&driver->driver); //向总线注册驱动
}
//第二层
int driver_register(struct device_driver *drv) {
    bus_add_driver(drv);
}
//第三层
int bus_add_driver(struct device_driver *drv) {
    driver_attach(drv); //此函数会遍历总线设备链表进行操做
    
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 添加进bus的driver链表中
}
//第四层
int driver_attach(struct device_driver *drv) {
    /* 遍历总线的设备链表,调用__driver_attach */
    bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//第五层
static int __driver_attach(struct device *dev, void *data)
{
	if (!driver_match_device(drv, dev))
		return 0;
    
    driver_probe_device(drv, dev);
}
复制代码

3. I2C适配器构建及其驱动介绍

I2C适配器驱动就是SOC的I2C控制器驱动,主要是由SOC厂商去编写,咱们不用过度注意细节。内部两个重要的数据结构i2c_adapter和 i2c_algorithmcode

//第一层
struct i2c_adapter{
    const struct i2c_algorithm *algo; /* 总线访问算法 */
}
/* i2c_algorithm 就是I2C适配器与IIC设备进行通讯的方法。*/
//第二层
struct i2c_algorithm {
    ......
    /* I2C适配器的传输函数,此函数完成与IIC设备的通讯 */
    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);
    
    /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);
    ......
};
/* 实例-构建适配器 */
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};

static int s3c24xx_i2c_probe(struct platform_device *pdev) {
    i2c->adap.algo    = &s3c24xx_i2c_algorithm; //构建了算法
    i2c_add_numbered_adapter(&i2c->adap); //注册了适配器
}
复制代码

4. I2C数据传输

上面介绍I2C数据传输是经过I2C适配器完成的,下面来分析一下源码在I2C驱动中,使用i2c_transfer来传输I2C数据,此函数确定是经过I2C适配器的算法进行操做的,以下

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) {
    adap->algo->master_xfer(adap, msgs, num); //调用适配器的算法
}

复制代码

设备驱动编写方法

i2c设备驱动重点关注两个数据结构i2c_client 和 i2c_driver,前者是描述设备信息的,后者是描述驱动的。

1、注册设备

1. 设置I2C设备驱动信息

  • 匹配ID方式
static const struct i2c_device_id my_i2c_dev_id[] = {
	{ "my_i2c_dev", 0},  /* 设备名字 */
	{ }
};
 
static struct i2c_driver my_i2c_drv = {
	.driver = {
		.name	= "no", /* 这个名字不重要 */
        .owner = THIS_MODULE,
	},
	.probe		= my_i2c_drv_probe, /* 当匹配到i2c设备时调用 */
	.remove		= my_i2c_drv_remove, /* 当卸载i2c设备或驱动时调用 */
	.id_table	= my_i2c_dev_id, /* 这个结构体中的名字很重要 */
};
复制代码

其中my_i2c_dev很是的重要,由于这个名字就是用来和设备进行匹配的名字。

  • 设备树匹配方式
/* 设备树匹配列表 */
static const struct of_device_id my_i2c_dev_of_match[] = {
	{ .compatible = "my_i2c_dev, 0" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver my_i2c_drv = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "no",
		   	.of_match_table = my_i2c_dev_of_match,
		   },
};

复制代码

2. 注册i2c设备驱动

static int __init my_i2c_drv_init(void) {
 
	i2c_add_driver(&my_i2c_drv);
 
	return 0;
}
复制代码

3.注册i2c设备

  • 匹配id方式
    /* 静态注册-只能在内核启动时就进行i2c设备注册 */
  1. 定义一个i2c_board_info对象
static struct i2c_board_info my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
复制代码
  1. 经过i2c_register_board_info注册
/* * busnum:哪一条总线,也就是选择哪个i2c控制器 * info:i2c设备信息数组 * n:数组有几项 */
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n);
i2c_register_board_info(0, my_i2c_dev_info, ARRAY_SIZE(my_i2c_dev_info));
复制代码

/* 动态注册能够在内核运行期间注册,也就是能够应用 在加载驱动模块中 */

  1. 定义一个i2c_board_info对象
static struct i2c_board_info my_i2c_dev_info = {
	I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
复制代码
  1. 经过i2c_new_device来动态注册
/* * adap:指定i2c设备器,之后访问设备的时候,使用过哪个设备器(i2c主机控制器)去访问 * info:指定i2c设备信息 */
struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

复制代码
  • 使用设备树时
/* 在i2c节点下添加设备信息 */
&i2c1 {
    my_i2c_dev@20 {
        compatible = "my_i2c_dev,0"
    } 
}
复制代码

2、数据传输函数介绍

1. 传输函数

/* * adap:i2c适配器 * msgs:消息数据 * num:数组的个数 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 复制代码

2. 传输报文msg的组成

struct i2c_msg {
	__u16 addr;	//从设备地址
	__u16 flags; //读或写
	__u16 len;	//消息的长度
	__u8 *buf;	//消息
};
复制代码
  • 例子

/* 定义 i2c_msg 结构体 */
struct i2c_msg msg[2]; 
 
char val[10]
 
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 这个client在probe函数中获得的 */
msg[0].flags = 0; /* 0表示写,1表示读 */
msg[0].buf = 0x80; /* 写:要发送的数据地址,读:读取到的数据存放的地址 */
msg[0].len = 1; /* 想要传输的字节数 */
 
/* 填充msg */
msg[1].addr = my_i2c_client->addr; /* 这个client在probe函数中获得的 */
msg[1].flags = 1; /* 1表示读 */
msg[1].buf = val; /* 读到的数据存在这里 */
msg[1].len = 4; /* 想要读取的字节数 */
 
 
/* 传输数据 */
i2c_transfer(my_i2c_client->adapter, msg, 2); /* 有两个msg */

复制代码
相关文章
相关标签/搜索