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

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

2023年6月26日发(作者:)

基于linux的I2C驱动与调试(传统ID匹配⽅式)基于linux的I2C驱动与调试(传统ID匹配⽅式) I2C驱动框架I2C驱动框架可以分为四部分,I2C核⼼、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核⼼中。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 */ ...};其中的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流程分析 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-> = &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 %sn", 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-> = &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适配器的构建对于三星平台,在driversi2cbussesi2c-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-> = &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由⾼电平向低电平跳变。4.2. I2C终⽌信号1) 终⽌信号:SCL为⾼电平的时候,SDA由低电平向⾼电平跳变。4.3. I2C应答信号1)I2C总线上的所有数据都是以8位字节传送的,发送器每发送⼀个字节,就在时钟脉冲9期间释放数据线,由接收器反馈⼀个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表⽰接收器已经成功地接收了该字节;应答信号为⾼电平时,规定为⾮应答位(NACK),⼀般表⽰接收器接收该字节没有成功,对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的⾼电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后⼀个字节后,发送⼀个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送⼀个停⽌信号P。4.4. I2C写时序1) 开始信号:主机+从设备地址+写命令,从机应答,应答成功,表⽰有这个设备,然后主机+设备内部寄存器地址,此时不⽤再加写命令控制字,从机应答,应答成功,表⽰设备内有这个地址,主机写⼊数据,从机应答,是否继续发送,不发送的话,发送停⽌信号P。4.5. I2C读时序1) ⾸先要知道将要所读取设备的地址告诉从设备,从设备才能将数据放到(发送)SDA上使主设备读取,从设备将数据放⼊SDA上的过程,由硬件主动完成,不⽤⼈为的写⼊。所以⾸先先写⼊从机地址,然后+写控制命令,从机应答,应答成功,表⽰有这个设备,然后写⼊内部寄存器地址,此时不⽤再加写命令控制字,从机应答,应答成功,表⽰设备内有这个地址。然后主机继续发出:写⼊从机地址,然后+读命令,从机应答,应答成功,此时便可以读取数据了,从设备已经将数据放⼊到SDA上了。5. I2C实际操作图解1) ⾸先本次操作是通过I2C读写CPLD的⼀个实验。第⼀,我们进⾏⼀个写数据操作,I2C从设备地址位0x16,CPLD寄存器地址为0x13,写⼊CPLD的数据为0x2;然后在读取写⼊CPLD中寄存器的值。5.1.写操作由上图所⽰,我可以得到从设备的地址为0x16,为写操作写⼊的⾼⼋位数据为0x0。由上图所⽰,我可以得到写操作写⼊的低⼋位数据为0x13,接着写⼊的⾼⼋位数据为0x00。由上图所⽰,我们可以得到写⼊的低⼋位数据为0x2,然后终⽌传输信号。5.2.读操作由上图所⽰,我可以得到从设备的地址为0x16,为写操作写⼊的⾼⼋位数据为0x0。由上图所⽰,我可以得到写操作写⼊的低⼋位数据为0x13,然后重新发送起始型号,发送从设备地址0x16,然后读取数据。由上图所⽰,我可以读取到⾼⼋位数据: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 { u16 address; u16 data_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(e_data[0],(u8 *)&s,1,(u8 *)&_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(e_data[1],(u8 *)&s,1,(u8 *)&_buff,2);

copy_to_user( (void __user *)arg, (void *)&cmdframe, sizeof( cmdframe)); break; default: printk( "invalid cmd %#xn", 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 () //创建设备号 { //静态注册设备号 = MKDEV(, 0); register_chrdev_region(, DEVICE_MINOR_NUM,id->name);

}

else

{

//动态注册设备号 alloc_chrdev_region(&, 0, DEVICE_MINOR_NUM,id->name); /*

申请设备号 */

= MAJOR(); /*

获取主设备号 */

= MINOR(); /*

获取次设备号 */

}

//创建类 175_class = class_create(THIS_MODULE,id->name); if (IS_ERR(175_class)) { return PTR_ERR(175_class);

} //设备初始化{ //数据初始化 175_ = THIS_MODULE; cdev_init(&175_cdev,&nst175_fops);

devno = MKDEV(,+id->driver_data);

//注册到系统 err = cdev_add(&175_cdev,devno, 1); if(err){ printk(KERN_EMERG "cdev_add is fail! %dn",err); } else{ printk(KERN_EMERG "cdev_add %d is success!n",+id->driver_data); }

//创建设备节点 device_create(175_class,NULL,devno,NULL,"%s",id->name);

this_client = client;

return 0;}//--------------------------移除字符函数---------------------------static int sensor_nst175_remove(struct i2c_client *client)

{

cdev_del(&175_cdev); //删除字符设备 unregister_chrdev_region(, 1); //注销字符设备

//注销掉类和设备

device_destroy(175_class, );

class_destroy(175_class);

return 0;

}//-----------------------传统匹配⽅式ID列表------------------------//-----------------------传统匹配⽅式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() "); }

//添加新的I2C设备 for(i=0;i

return -1; } e_data[i] = client->addr; }

//添加I2C驱动 ret = i2c_add_driver(&sensor_nst175_driver); if(ret) { printk("i2c_add_driver() ");

}

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的调试过程讲解就到这了,主要是想讲述⼀下调试中的过程,同时附带了驱动代码,欢迎⼤家⼀起来学习。。。

发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1687755291a39821.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信