Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)


基于linux的I2C驱动与调试(传统ID匹配方式)

  • 1.Linux I2C驱动框架
    • 1.1. I2C驱动的主要对象
      • 1.1.1. I2C总线
      • 1.1.2. I2C设备
      • 1.1.3. I2C驱动
      • 1.1.4. I2C适配器
      • 1.1.5. 总结一下
    • 1.2. I2C框架分析
    • 1.3. I2C流程分析
  • 2.Linux I2C驱动框架源码剖析
    • 2.1.注册I2C设备
    • 2.2.注册I2C驱动
    • 2.3. I2C适配器的构建
    • 2.4. I2C数据传输
  • 3.I2C背景了解
    • 3.1.物理接口
    • 3.2通信特征
  • 4. I2C时序图
    • 4.1. I2C起始信号
    • 4.2. I2C终止信号
    • 4.3. I2C应答信号
    • 4.4. I2C写时序
    • 4.5. I2C读时序
  • 5. I2C实际操作图解
    • 5.1.写操作
    • 5.2.读操作
    • 5.3. 驱动代码
  • 6.总结

1.Linux I2C驱动框架 I2C驱动框架可以分为四部分,I2C核心、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核心中。
Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

1.1. I2C驱动的主要对象 2C总线用于管理I2C设备和I2C驱动,维护一个设备链表和驱动链表,定义了设备和驱动的匹配规则,定义了匹配成功后的行为,其在内核中的定义如下。
1.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, };

1.1.2. I2C设备
I2C设备描述了I2C设备的硬件信息,例如I2C设备的地址、I2C设备在接在哪一个I2C控制器上,其结构体定义如下
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 i2c_driver *driver; /* and our access routines */ struct device dev; /* the device structure*/ int irq; /* irq issued by device*/ struct list_head detected; };

1.1.3. I2C驱动
I2C驱动是I2C设备的驱动程序,用于匹配I2C设备,其结构体定义如下。
struct i2c_driver { ... /* 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 *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); ... struct list_head clients; };

1.1.4. I2C适配器
I2C适配器是SOC上的I2C控制器的软件抽象,可以通过其定义的算法向硬件设备传输数据,其结构体定义如下。
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; ... struct device dev; /* the adapter device */ ... };

【Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)】其中的i2c_algorithm表示算法,用于向硬件设备传输数据,其定义如下。
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 *); };

1.1.5. 总结一下
I2C驱动的主要对象是I2C总线、I2C设备、I2C驱动、I2C适配器
I2C总线用于管理I2C设备和I2C驱动
I2C设备描述了I2C设备的硬件信息
I2C驱动是I2C设备对应的驱动程序
I2C适配器是SOC上的I2C控制器,其定义了算法,可以向I2C硬件设备传输数据
其中直接面向编写I2C设备驱动的开发者的是I2C设备和I2C驱动,I2C总线和I2C适配器是幕后工作者
1.2. I2C框架分析 I2C核心维护着一条I2C总线,提供了注册I2C设备、I2C驱动、I2C适配器的接口。
I2C总线维护着一条设备链表和驱动链表,当向I2C核心层注册设备时,会将其添加到总线的设备链表中,然后遍历总线上的驱动链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
当注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,然后遍历总线的设备链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
在I2C驱动程序中,通过I2C适配器中的算法向I2C硬件设备传输数据。
1.3. I2C流程分析 Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

2.Linux I2C驱动框架源码剖析 2.1.注册I2C设备 (1)注册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; ... status = device_register(&client->dev); }

(2)看一下其中的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, };

(3)下面再来看看device_register向总线注册设备过程中会发生什么。device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数,下面看一看源码分析。
int device_register(struct device *dev) { device_initialize(dev); return device_add(dev); }

int device_add(struct device *dev) { ... error = bus_add_device(dev); ... bus_probe_device(dev); .... }

(4)其中bus_add_device函数会将设备添加到总线的设备链表中,如下。
int bus_add_device(struct device *dev) { .... klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); ... }

(5)bus_probe_device函数会遍历总线的驱动链表,如下。
void bus_probe_device(struct device *dev) { .... if (bus->p->drivers_autoprobe) { ret = device_attach(dev); WARN_ON(ret < 0); } ... }

int device_attach(struct device *dev) { ... ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); ... }

(6)bus_for_each_drv(dev->bus, NULL, dev, __device_attach); 会遍历总线的驱动链表的每一项,然后调用__device_attach。
static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv = data; if (!driver_match_device(drv, dev)) return 0; if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); if (!dev->driver) driver_probe_device(drv, dev); device_unlock(dev); if (dev->parent) device_unlock(dev->parent); return 0; }

driver_match_device函数会判断设备和驱动是否匹配,如果匹配就调用driver_probe_device。
(7)首先来看一看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; }

(8)发现它调用了总线的match函数,这里的总线在注册I2C设备的时候已经被设置为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, };

(9)所以这里会调用到i2c_device_match函数,i2c_device_match会通过I2C驱动的id_table中每一的name和I2C设备的name进行匹配。
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; }

(10)如果匹配成功会调用driver_probe_device,下面再来看看driver_probe_device,driver_probe_device函数最终会先调用到I2C总线的probe函数,然后再调用I2C驱动的probe函数。
int driver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0; if (!device_is_registered(dev)) return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); pm_runtime_get_noresume(dev); pm_runtime_barrier(dev); ret = really_probe(dev, drv); pm_runtime_put_sync(dev); return ret; }

static int really_probe(struct device *dev, struct device_driver *drv) { ... }

(11)总线的probe函数为i2c_device_probe,此函数会调用驱动的probe函数
static int i2c_device_probe(struct device *dev) { ... status = driver->probe(client, i2c_match_id(driver->id_table, ... }

2.2.注册I2C驱动 (1) 可以通过i2c_add_driver注册I2C驱动,该函数会指定驱动对应的总线为I2C总线,然后向总线注册驱动。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) { ... driver->driver.bus = &i2c_bus_type; ... res = driver_register(&driver->driver); ... }

(2) i2c_bus_type的定义如下。
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, };

(3) driver_register函数遍历总线的设备链表进行操作,然后将驱动添加到总线的驱动链表中。
int driver_register(struct device_driver *drv) { ... ret = bus_add_driver(drv); ... }

int bus_add_driver(struct device_driver *drv) { ... if (drv->bus->p->drivers_autoprobe) { error = driver_attach(drv); if (error) goto out_unregister; } klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); }

int driver_attach(struct device_driver *drv) { return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); }

(4)下面来看一看__driver_attach函数,此函数会判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数。
static int __driver_attach(struct device *dev, void *data) { ... if (!driver_match_device(drv, dev)) return 0; ... if (!dev->driver) driver_probe_device(drv, dev); }

2.3. I2C适配器的构建 对于三星平台,在drivers\i2c\busses\i2c-s3c2410.c文件中构建并注册了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; ... ret = i2c_add_numbered_adapter(&i2c->adap); ... }

其中的s3c24xx_i2c_algorithm中的s3c24xx_i2c_xfer就是通过操作寄存器来通过I2C控制器传输数据。
2.4. I2C数据传输 上面介绍I2C数据传输是通过I2C适配器完成的,下面来分析一下源码,在I2C驱动中,使用i2c_transfer来传输I2C数据,此函数肯定是通过I2C适配器的算法进行操作的,如下。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { .... ret = adap->algo->master_xfer(adap, msgs, num); ... }

3.I2C背景了解 3.1.物理接口
1)SCL(serial clock ):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道; 2)SDA(serial data):数据线,通信数据都通过SDA线传输。

3.2通信特征
1)I2C 属于串行通信,所有的数据以位为单位在SDA线上串行传输。2)同步通信就是通信双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线传输自己的时钟给B,B工作在A传输的时钟下,所以同步通信的显著特征就是:通信线中有CLK。3)非差分。因为I2C速率不高且通信双方距离很近,所以使用电平信号通信。4)低速率。 I2C一般是用在板子上的2个IC之间的通信,而且用来传输的数据量不大,所以本身通信速率很低(几百KHz,不同的I2C芯片的通信速率可能不同,具体在编程的时候要看自己使用的设备允许的I2C通信最高速率,不能超过这个速率)

4. I2C时序图 4.1. I2C起始信号
1) SCL为高电平的时候,SDA由高电平向低电平跳变。

Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

4.2. I2C终止信号
1) 终止信号:SCL为高电平的时候,SDA由低电平向高电平跳变。

Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

4.3. I2C应答信号
1)I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功,对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

4.4. I2C写时序
1) 开始信号:主机+从设备地址+写命令,从机应答,应答成功,表示有这个设备,然后主机+设备内部寄存器地址,此时不用再加写命令控制字,从机应答,应答成功,表示设备内有这个地址,主机写入数据,从机应答,是否继续发送,不发送的话,发送停止信号P。

Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

4.5. I2C读时序
1) 首先要知道将要所读取设备的地址告诉从设备,从设备才能将数据放到(发送)SDA上使主设备读取,从设备将数据放入SDA上的过程,由硬件主动完成,不用人为的写入。所以首先先写入从机地址,然后+写控制命令,从机应答,应答成功,表示有这个设备,然后写入内部寄存器地址,此时不用再加写命令控制字,从机应答,应答成功,表示设备内有这个地址。然后主机继续发出:写入从机地址,然后+读命令,从机应答,应答成功,此时便可以读取数据了,从设备已经将数据放入到SDA上了。

Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

5. I2C实际操作图解
1)首先本次操作是通过I2C读写CPLD的一个实验。第一,我们进行一个写数据操作,I2C从设备地址位0x16,CPLD寄存器地址为0x13,写入CPLD的数据为0x2;然后在读取写入CPLD中寄存器的值。

5.1.写操作 Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

由上图所示,我可以得到从设备的地址为0x16,为写操作写入的高八位数据为0x0。
Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

由上图所示,我可以得到写操作写入的低八位数据为0x13,接着写入的高八位数据为0x00。
Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

由上图所示,我们可以得到写入的低八位数据为0x2,然后终止传输信号。
5.2.读操作 Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

由上图所示,我可以得到从设备的地址为0x16,为写操作写入的高八位数据为0x0。
Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

由上图所示,我可以得到写操作写入的低八位数据为0x13,然后重新发送起始型号,发送从设备地址0x16,然后读取数据。
Linux_drivers|基于linux的I2C驱动与调试(传统ID匹配方式)
文章图片

由上图所示,我可以读取到高八位数据:0x0,接着为读取到的第八位数据:0x2,然后终止传输信号。
5.3. 驱动代码
  1. 以下代码为nst175温度传感器的代码.
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #define I2C_MASTER_RD 0x0001 //read data, from slave to master #define DEVICE_NAME "sensor"//设备名:srnsor #define DEVICE_MINOR_NUM 1//次设备号数struct i2c_nst175_command { u16address; u16data_buff; }; struct nst175_device{ dev_t devid; //设备ID struct cdev nst175_cdev; //字符设备结构体 struct class *nst175_class; //类 struct device *device; //创建设备 int major; //主设备 int minor; //从设备 int private_data[2]; //私有数据,用于存放从设备地址 }; static struct i2c_client *this_client; static struct nst175_device nst175dev; //------------------------I2C读取NST175数据------------------------ static int i2c_nst175_rxdata(int saddr,char *regaddr,int regaddr_len,char *rxdata, int data_len) { int ret; struct i2c_msg msgs[] = { { .addr= saddr, .flags = 0, .len= regaddr_len, .buf= regaddr, }, { .addr= saddr, .flags = I2C_MASTER_RD, .len= data_len, .buf= rxdata, }, }; ret = i2c_transfer( this_client->adapter, msgs, 2); if( ret < 0) printk( "read error:%d.\n", ret); return ret; }//---------------------------打开操作--------------------------- static int nst175_open(struct inode *inode, struct file *filp) { return 0; }//---------------------------控制操作--------------------------- static long nst175_ioctl( struct file *file, unsigned int cmd, unsigned long arg) { struct i2c_nst175_command cmdframe; int ret = 0; switch( cmd) { //-----------------------------nst175_1(板子标识号u8)----------------------------- case 0: copy_from_user((void *)&cmdframe, (void __user *)arg, sizeof( cmdframe)); ret = i2c_nst175_rxdata(nst175dev.private_data[0],(u8 *)&cmdframe.address,1,(u8 *)&cmdframe.data_buff,2); copy_to_user( (void __user *)arg, (void *)&cmdframe, sizeof( cmdframe)); break; //-----------------------------nst175_2(板子标识号u9)----------------------------- case 1: copy_from_user((void *)&cmdframe, (void __user *)arg, sizeof( cmdframe)); ret = i2c_nst175_rxdata(nst175dev.private_data[1],(u8 *)&cmdframe.address,1,(u8 *)&cmdframe.data_buff,2); copy_to_user( (void __user *)arg, (void *)&cmdframe, sizeof( cmdframe)); break; default: printk( "invalid cmd %#x\n", cmd); return -EINVAL; }return ret; }//---------------------------设备操作--------------------------- static struct file_operations nst175_fops = { .owner= THIS_MODULE, .open= nst175_open, .unlocked_ioctl = nst175_ioctl, }; //---------------------------探针函数---------------------------- static int sensor_nst175_probe(struct i2c_client *client, const struct i2c_device_id *id) { int i,err; int devno; //-------------------------注册字符设备驱动--------------------------------- if (nst175dev.major) //创建设备号 { //静态注册设备号 nst175dev.devid = MKDEV(nst175dev.major, 0); register_chrdev_region(nst175dev.devid, DEVICE_MINOR_NUM,id->name); } else { //动态注册设备号 alloc_chrdev_region(&nst175dev.devid, 0, DEVICE_MINOR_NUM,id->name); /* 申请设备号 */ nst175dev.major = MAJOR(nst175dev.devid); /* 获取主设备号 */ nst175dev.minor = MINOR(nst175dev.devid); /* 获取次设备号 */ } //创建类 nst175dev.nst175_class = class_create(THIS_MODULE,id->name); if (IS_ERR(nst175dev.nst175_class)) { return PTR_ERR(nst175dev.nst175_class); } //设备初始化{ //数据初始化 nst175dev.nst175_cdev.owner = THIS_MODULE; cdev_init(&nst175dev.nst175_cdev,&nst175_fops); devno = MKDEV(nst175dev.major,nst175dev.minor+id->driver_data); //注册到系统 err = cdev_add(&nst175dev.nst175_cdev,devno, 1); if(err){ printk(KERN_EMERG "cdev_add is fail! %d\n",err); } else{ printk(KERN_EMERG "cdev_add %d is success!\n",nst175dev.minor+id->driver_data); } //创建设备节点 device_create(nst175dev.nst175_class,NULL,devno,NULL,"%s",id->name); this_client = client; return 0; }//--------------------------移除字符函数--------------------------- static int sensor_nst175_remove(struct i2c_client *client) { cdev_del(&nst175dev.nst175_cdev); //删除字符设备 unregister_chrdev_region(nst175dev.devid, 1); //注销字符设备 //注销掉类和设备 device_destroy(nst175dev.nst175_class, nst175dev.devid); class_destroy(nst175dev.nst175_class); return 0; }//-----------------------传统匹配方式ID列表------------------------ static const struct i2c_device_id sensor_nst175_id[] = { {"nst175_1", 0}, {"nst175_2", 1}, {/* END OF LIST */} }; //-------------------------I2C板载设备信息------------------------- static struct i2c_board_info sensor_nst175_i2c_board_info[] = { {I2C_BOARD_INFO("nst175_1", 0x49),}, {I2C_BOARD_INFO("nst175_2", 0x4a),},}; //--------------------------i2c驱动结构体-------------------------- static struct i2c_driver sensor_nst175_driver = { .probe = sensor_nst175_probe, .remove = sensor_nst175_remove, .id_table = sensor_nst175_id, .driver = { .owner = THIS_MODULE, .name = "sensor_nst175", }, }; //----------------------------加载函数----------------------------- static int __init sensor_nst175_init(void) { int ret = 0,i; struct i2c_adapter *i2c_adap = NULL; struct i2c_client *client = NULL; //获取adapter总线上的相应的I2C设备:I2C(0) i2c_adap = i2c_get_adapter(0); if(i2c_adap < 0) { printk("i2c_get_adapter() failed...\n"); } //添加新的I2C设备 for(i=0; iaddr; } //添加I2C驱动 ret = i2c_add_driver(&sensor_nst175_driver); if(ret) { printk("i2c_add_driver() failed...\n"); } return ret; }//-----------------------------卸载函数----------------------------- static void __exit sensor_nst175_exit(void) { printk(KERN_EMERG "sensor_nst175_exit!\n"); i2c_del_driver(&sensor_nst175_driver); }module_init(sensor_nst175_init); //入口函数 module_exit(sensor_nst175_exit); //出口函数MODULE_LICENSE("GPL"); MODULE_AUTHOR("author:shzn");

6.总结 最后关于I2C的调试过程讲解就到这了,主要是想讲述一下调试中的过程,同时附带了驱动代码,欢迎大家一起来学习。。。

    推荐阅读