I2C驱动框架

Linux的I2C体系结构分为3个组成部分,分别是I2C核心、I2C总线驱动和I2C设备驱动。
I2C控制器的驱动程序称为总线驱动(包含两部分,硬件相关、硬件无关),将I2C设备的驱动程序称为设备驱动(包含两部分,I2C设备匹配相关、I2C设备操作方法集)。
在这里插入图片描述

I2C驱动设计过程简述:

I2C设备注册:
首先要向I2C核心层注册一个I2C设备,I2C总线会将其添加到总线的设备链表中,然后遍历总线上的驱动链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
I2C驱动注册:
注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,然后遍历总线的设备链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
I2C适配器注册:
I2C适配器也是以设备的形式挂在总线上,通过操作寄存器来传输数据。

I2C总线基础详解链接

具体实现程序详解

1.1 I2C总线
I2C总线用于管理I2C设备和I2C驱动,维护一个设备链表和驱动链表,定义了设备和驱动的匹配规则,定义了匹配成功后的行为,其在内核中的定义如下:

struct bus_type i2c_bus_type = {
    .name                = "i2c",    //总线的名称
    .match                = i2c_device_match,    /* i2c设备与驱动注册时候会调用这个函数进行匹配操作,里面有匹配规则,目前是通过IIC设备i2c_client结构体中名字与驱动i2c_driver结构体中id_table匹配,注意:并不会匹配驱动名字! */
    .probe                = i2c_device_probe,    //匹配成功后会调用总线的probe函数,在里面进一步调用iic驱动的prob函数(也就是自己写的)
    .remove                = i2c_device_remove,    //与上面一样
    .shutdown        = i2c_device_shutdown,
    .pm                = &i2c_device_pm_ops,    //电源管理
};

1.2 I2C设备
I2C设备描述了I2C设备的硬件信息,例如I2C设备的地址、I2C设备在接在哪一个I2C控制器上,其结构体定义如下:

struct i2c_client {
    unsigned short flags;                //I2C_CLIENT_TEN表示设备使用10bit从地址,I2C_CLIENT_PEC表示设备使用SMBus检错
    unsigned short addr;                /*I2C设备基地址,7bit。这里说一下为什么是7位,因为最后以为0表示写,1表示读,通过对这个7bit地址移位处理即可。addr<<1 & 0x0即写,addr<<1 | 0x01即读*/
    char name[I2C_NAME_SIZE];      //i2c设备名字,用于匹配iic驱动
    struct i2c_adapter *adapter;        /*iic设备是挂在哪个适配器(总线)上        */
    struct device dev;                /* 表明这是一个设备,挂在I2C总线上        */
    int irq;                        /* 中断号,用于申请中断,一般是在自己实现的i2c驱动的probe函数中使用*/
    struct list_head detected;    //是i2c_adapter结构体或i2c_driver结构体中链表的节点
};

i2c_client结构体成员的参数是通过i2c_board_info中的成员进行赋值,用于I2C设备的注册,具体内容如下:

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];  //设备名,最长20个字符,最终安装到client的name上
    unsigned short    flags;  //最终安装到client.flags
    unsigned short    addr;  //设备从地址slave address,最终安装到client.addr上
    void        *platform_data;  //设备数据,最终存储到i2c_client.dev.platform_data上
    struct dev_archdata    *archdata;
    struct device_node *of_node;  //OpenFirmware设备节点指针
    struct acpi_dev_node acpi_node;
    int        irq;  //设备采用的中断号,最终存储到i2c_client.irq上
};

1.3 I2C驱动
I2C驱动是I2C设备的驱动程序,用于匹配I2C设备,其结构体定义如下:

struct i2c_driver {
    unsigned int class;
    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 *);    //当前移除设备或驱动时执行的移除函数,一般是释放资源
    int (*probe_new)(struct i2c_client *);    //未来匹配成功后的执行函数
    void (*shutdown)(struct i2c_client *);    //关闭设备
    void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsigned int data);
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    struct device_driver driver;    //表明这是一个驱动,驱动模型用来挂在I2C总线上
    const struct i2c_device_id *id_table;    //设备匹配列表,非常重要,IIC设备的名字与这个列表匹配
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;    //该驱动所支持的所有(次设备)的地址数组,用于注册驱动时自动去匹配
    struct list_head clients;    //用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
    bool disable_i2c_core_irq_mapping;
};

1.4 I2C适配器
I2C适配器是SOC上的I2C控制器的软件抽象,可以通过其定义的算法向硬件设备传输数据,其结构体定义如下:

struct i2c_adapter {
    struct module *owner;//所有者
    unsigned int class;                  /*适配器支持的从设备类型 */
    const struct i2c_algorithm *algo; /* 适配器的通信方法,也就是上面的i2c_algorithm */
    void *algo_data;    //algo私有数据
    int timeout;                        /* 超时时间 */
    int retries;                               //重传次数,在通信方法中使用
    struct device dev;                /* 表明这是一个设备,挂载在I2C总线上 */
    int nr;                                          //适配器(总线)的编号
    char name[48];                        //适配器名称
    struct list_head userspace_clients;     //IIC设备的client依附链表头,只有在用户层echo name addr > /sys/bus/i2c/devices/i2c-x/new_device时候创建的client才会依附在此链表//省略部分无关成员
};

其中的i2c_algorithm表示算法,用于向硬件设备传输数据,其定义如下:

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,   int num);    //I2C传输函数指针,i2c_transfer函数的底层调用
 
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,   unsigned short flags, char read_write,   u8 command, int size, union i2c_smbus_data *data);    //smbus传输函数指针,SMBUS协议发送函数,i2c_smbus_xxx函数的底层调用
 
	u32 (*functionality) (struct i2c_adapter *);    /* 这个IIC适配器支持什么样的功能,比如支持SMBUS字节发送或读取操作,标志位为I2C_FUNC_SMBUS_BYTE_DATA */
};

总结:
I2C驱动设计的的主要对象是I2C总线、I2C设备、I2C驱动、I2C适配器
I2C总线用于管理I2C设备和I2C驱动
I2C设备描述了I2C设备的硬件信息
I2C驱动是I2C设备对应的驱动程序
I2C适配器是SOC上的I2C控制器,其定义了算法,可以向I2C硬件设备传输数据

2.1 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);//GFP_KERNEL是无内存可用时可引起休眠
        
    client->dev.bus = &i2c_bus_type; //指定I2C总线
    
    device_register(&client->dev); //向总线注册设备
}

看一下其中的i2c_bus_type对象,其表示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,
};

device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数,下面看一看源码分析:

int device_register(struct device *dev)
{
    device_add(dev);
}
int device_add(struct device *dev)
{
    bus_add_device(dev); //添加设备到总线的设备链表中
    bus_probe_device(dev); //遍历总线的驱动
}

其中bus_add_device函数会将设备添加到总线的设备链表中,如下

int bus_add_device(struct device *dev)
{
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}

bus_probe_device函数会遍历总线的驱动链表,如下:

void bus_probe_device(struct device *dev)
{
    device_attach(dev);
}
int device_attach(struct device *dev)
{
    /* 遍历总线的驱动链表 */
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}

bus_for_each_drv(dev->bus, NULL, dev, __device_attach);会遍历总线的驱动链表的每一项,然后调用__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);
}

driver_match_device函数会判断设备和驱动是否匹配,如果匹配就调用driver_probe_device
首先来看一看driver_match_device函数的定义:

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

这里调用的就是最开始i2c_bus_type成员中设置的i2c_device_match函数,然后我们再分析i2c_device_match:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    i2c_match_id(driver->id_table, client);
}
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;
}

这里就是通过i2c_match_id函数去将驱动和设备进行匹配
再回到__device_attach函数,当完成匹配后就会执行driver_probe_device,继续分析该函数:

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    really_probe(dev, drv);
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
  i2c_device_probe(dev);  
}
static int i2c_device_probe(struct device *dev)
{
    /* 调用驱动的probe函数 */
    driver->probe(client, i2c_match_id(driver->id_table, client));
}

函数最后执行的是驱动的probe函数,也就是最开始我们再驱动注册时设置的驱动成员函数:

struct i2c_driver {
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);    //当前匹配成功后执行函数,一般是申请资源,注册字符驱动
};

2.2 注册I2C驱动过程源码详解:

通过i2c_add_driver注册I2C驱动,该函数会指定驱动对应的总线为I2C总线,然后向总线注册驱动,基本的过程与I2C设备的注册一致,所以要先搞懂设备注册,驱动注册流程也就水到渠成,需要注意的下面第一段代码,即驱动结构体成员指定的各个函数,因为通过上面的源码分析我们知道驱动注册、注销最终会运行驱动的probe、remove相关函数:

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

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    driver->driver.bus = &i2c_bus_type; //指定I2C总线
    
    driver_register(&driver->driver); //向总线注册驱动
}

driver_register函数遍历总线的设备链表进行操作,然后将驱动添加到总线的驱动链表中:

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);
}

__driver_attach函数,此函数会判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数:

static int __driver_attach(struct device *dev, void *data)
{
	if (!driver_match_device(drv, dev))
		return 0;
    
    driver_probe_device(drv, dev);
}

2.3 I2C适配器的构建和数据传输:
以三星平台I2C控制器的驱动部分源码分析:

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); //注册了适配器
}

在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); //调用适配器的算法
}

其中msgs结构体内容如下:

struct i2c_msg {
    __u16 addr;                 /* IIC设备的基地址,7位 */
    __u16 flags;               /*操作标志位*/
    __u16 len;                /* 读写数据的长度 */
    __u8 *buf;               /* 装有数据的缓冲区 */
    #define I2C_M_RD                0x0001        /* 设置了这个标志位表示本次通信i2c控制器是处于接收方,否则就是发送方 */
    #define I2C_M_TEN                0x0010        /* 设置了这个标志位表示从设备的地址是10bit */
    #define I2C_M_DMA_SAFE                0x0200        /* the buffer of this message is DMA safe */
    /* makes only sense in kernelspace */
    /* userspace buffers are copied anyway */
    #define I2C_M_RECV_LEN                0x0400        /* length will be first received byte */
    #define I2C_M_NO_RD_ACK                0x0800        /* 设置这个标志位表示在读操作中主机不用ACK */
    #define I2C_M_IGNORE_NAK        0x1000        /* 设置这个标志意味当前i2c_msg忽略I2C器件的ack和nack信号 */
    #define I2C_M_REV_DIR_ADDR        0x2000        /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NOSTART                0x4000        /* if I2C_FUNC_NOSTART */
    #define I2C_M_STOP                0x8000        /* if I2C_FUNC_PROTOCOL_MANGLING */
};

其他知识分享:
I2C总线基础详解链接

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐