全志公司对I2C的讲解.docx_第1页
全志公司对I2C的讲解.docx_第2页
全志公司对I2C的讲解.docx_第3页
全志公司对I2C的讲解.docx_第4页
全志公司对I2C的讲解.docx_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1.几个基本概念1.1.设备模型由总线(bus_type)+设备(device)+驱动(device_driver)组成,在该模型下,所有的设备通过总线连接起来,即使有些设备没有连接到一根物理总线上,linux为其设置了一个内部的、虚拟的platform总线,用以维持总线、驱动、设备的关系。因此,对于实现一个linux下的设备驱动,可以划分为两大步:1、设备注册;2、驱动注册。当然,其中还有一些细节问题:1、驱动的probe函数2、驱动和设备是怎么进行绑定的。1.2.i2c设备驱动的几个数据结构i2c_adapter:每一个i2c_adapter对应一个物理上的i2c控制器,在i2c总线驱动probe函数中动态创建。通过i2c_add_adapter注册到i2c_core。i2c_algorithm:i2c_algorithm中的关键函数master_xfer(),以i2c_msg为单位产生i2c访问需要的信号。不同的平台所对应的master_xfer()是不同的,需要根据所用平台的硬件特性实现自己的xxx_xfer()方法以填充i2c_algorithm的master_xfer指针;在A31上即是sun6i_i2c_algorithm函数。i2c_client:代表一个挂载到i2c总线上的i2c从设备,包含该设备所需要的数据:该i2c从设备所依附的i2c控制器structi2c_adapter*adapter该i2c从设备的驱动程序structi2c_driver*driver该i2c从设备的访问地址addr,name该i2c从设备的名称name。2.i2c总线驱动2.1.功能划分从硬件功能上可划分为:i2c控制器和i2c外设(从设备)。每个i2c控制器总线上都可以挂载多个i2c外设。Linux中对i2c控制器和外设分开管理:通过i2c-sun6i.c文件完成了i2c控制器的设备注册和驱动注册;通过i2c-core.c为具体的i2c外设提供了统一的设备注册接口和驱动注册接口,它分离了设备驱动devicedriver和硬件控制的实现细节(如操作i2c的寄存器)。2.2.i2c-sun6i.c该文件是与具体硬件平台相关的,对应于A3x系列芯片。该文件实际上是i2c总线驱动的实现,本质上就是向内核注册i2c总线设备、注册总线驱动、实现总线传输的时序控制算法。i2c控制器被注册为Platform设备,如下: if (twi_used_mask & TWI0_USED_MASK) platform_device_register(&sun6i_twi0_device); if (twi_used_mask & TWI1_USED_MASK) platform_device_register(&sun6i_twi1_device); if (twi_used_mask & TWI2_USED_MASK) platform_device_register(&sun6i_twi2_device); if (twi_used_mask & TWI3_USED_MASK) platform_device_register(&sun6i_twi3_device); if (twi_used_mask) return platform_driver_register(&sun6i_i2c_driver);需要注意的是:设备与驱动的对应关系是多对一的;即如果设备类型是一样的,会共用同一套驱动,因此上面代码只是注册了一次驱动platform_driver_register(&sun6i_i2c_driver)。设备注册:将i2c控制器设备注册为platform设备,为每一个控制器定义一个structplatform_device数据结构,并且把.name都设置为sun6i-i2c(后面会通过名字进行匹配驱动的),然后是调用platform_device_register()将设备注册到platformbus上。设备注册完成后其直观的表现就是在文件系统下出现:/sys/bus/platform/devices/sun6i-i2c.0通过platform_device_register()进行的注册过程,说到底就是对structplatform_device这个数据结构的更改,逐步完成.dev.parent、.dev.kobj、.dev.bus的赋值,然后将.dev.kobj加入到platform_bus-kobj的链表上。驱动注册:步骤和设备注册的步骤类似,也是为驱动定义了一个数据结构:structplatform_driversun6i_i2c_driver;因为一个驱动是可以对应多个设备的,而在系统里的3个控制器基本上是一致的(区别就是寄存器的地址不一样),所以上面注册的3个设备共享的是同一套驱动。初始化.probe和.remove函数,然后调用platform_driver_register进行驱动注册。主要函数调用流程:platform_driver_register-driver_register-bus_add_driver-driver_attach需要注意的是driver_attach,这个函数遍历了总线上(platform_bus_type)的所有设备,寻找与驱动匹配的设备,并把满足条件的设备结构体上的驱动指针指向驱动,从而完成了驱动和设备的匹配(_driver_attach函数完成)。如果匹配到设备,这时就需要执行platform_bus_type的probe函数,最终会调用设备驱动的probe函数(sun6i_i2c_probe)。2.2.1sun6i_i2c_probe在sun6i_i2c_probe函数中完成了大量的工作,包括硬件初始化、中断注册、为每个i2c控制器创建i2c_adapter等。1268 pdata = pdev-dev.platform_data;1269 if (pdata = NULL) 1270 return -ENODEV;1271 12721273 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);1274 irq = platform_get_irq(pdev, 0);1275 if (res = NULL | irq start, resource_size(res), res-name) 1280 return -ENOMEM;1281 首先得到当前设备的私有数据指针,并将其保留在pdata;进而通过platform_get_resource得到该设备占用的内存资源,并申请:request_mem_region。同时将irq资源也保留下来。12881289 strlcpy(, sun6i-i2c, sizeof();1290 i2c-adap.owner = THIS_MODULE;1291 i2c-adap.nr = pdata-bus_num;1292 i2c-adap.retries = 3;1293 i2c-adap.timeout = 5*HZ;1294 i2c-adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;1295 i2c-bus_freq = pdata-frequency;1296 i2c-irq = irq;1297 i2c-bus_num = pdata-bus_num;1298 i2c-status = I2C_XFER_IDLE;1299 i2c-suspended = 0;1300 spin_lock_init(&i2c-lock);1301 init_waitqueue_head(&i2c-wait); 初始化i2c_adapter,并初始化一个工作队列init_waitqueue_head。 通过ioremap申请IO资源; 通过request_irq申请irq资源,中断的处理服务函数是:sun6i_i2c_handler; sun6i_i2c_hw_init,对i2c控制进行硬件初始化; i2c-adap.algo=&sun6i_i2c_algorithm,初始化控制器的总线传输算法,设备驱动调用; 将初始化好的i2c_adapter注册到i2c_core:i2c_add_numbered_adapter。至此,probe函数完成。2.2.2sun6i_i2c_core_processi2c控制器的中断服务程序sun6i_i2c_handler调用了sun6i_i2c_core_process,i2c总线的实际传输控制也是在该函数里完成的。主要流程:1. 读取i2c控制器当前状态,twi_query_irq_status,保留在state中;2. 根据state的值进行分支跳转,控制i2c的工作状态;3. 传输完成,调用sun6i_i2c_xfer_complete,唤醒工作队列。2.2.3sun6i_i2c_xfer每一个i2c控制器设备,在驱动绑定后,都会创建一个i2c_adapter,用以描述该控制器,i2c_adapter的建立与初始化是在驱动probe的时候建立的。每一个i2c_adapter包含了一个i2c_algorithm结构体的指针,i2c_algorithm是用来对外提供操作i2c控制器的函数接口的,主要是master_xfer函数,对应于i2c-sun6i.c,实际就是:static int sun6i_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)该函数的功能是通知i2c_adapter需要对外设进行数据交换,需要交换的信息通过structi2c_msg*msgs传入。sun6i_i2c_xfer实际上是调用了sun6i_i2c_do_xfer进行传输。因为i2c总线读写速率有限,sun6i_i2c_do_xfer启动i2c传输后,通过wait_event_timeout进入休眠,直到中断唤醒或者超时;中断唤醒是由sun6i_i2c_xfer_complete完成的。3.i2c设备驱动3.1.驱动注册i2c从设备的驱动注册,使用的是i2c-core.c提供的接口:i2c_register_driver;其调用如下:i2c_register_driver-driver_register-bus_add_driver;对bus_add_driver进行分析: 关于device_driver数据结构的structdriver_private*p设备驱动模型是通过kobject对设备驱动进行层次管理的,因此device_driver应该包含kobject成员,linux是将kobject包含在structdriver_private中,再在device_driver中包含structdriver_private;我们可以理解driver_private是device_driver的私有数据,由内核进行操作。structdriver_private是在驱动注册的开始,动态申请,并初始化的。 klist_init(&priv-klist_devices,NULL,NULL);初始化设备链表,每一个与该驱动匹配的device都会添加到该链表下。 priv-kobj.kset=bus-p-drivers_kset;指定该驱动所属的kset; kobject_init_and_add初始化kobject,并将kobject添加到其对应的kset集合中(即bus-p-drivers_kset)。该函数最终是调用kobject_add_internal将kobject添加到对应的kset中;需要主要的是,如果kobject的parent如果为NULL,在此会将其parent设置为所属kset集合的kobject:parent=kobject_get(&kobj-kset-kobj);接下来是为kobject创建文件夹:create_dir(kobj);从而能从/sys/目录下显示。 driver_attach,将驱动和设备进行绑定将遍历总线上的设备链表,查找可以匹配的设备,并绑定。driver_attach-bus_for_each_dev(drv-bus,NULL,drv,_driver_attach);将函数指针_driver_attach传入bus_for_each_dev,将每个查找得到的device进行驱动匹配。bus_for_each_dev:遍历总线上的所有设备,因为总线上的设备都是bus-p-klist_devices链表上的一个节点,因此该函数其实就是对链表的遍历,具体可以参考klist。_driver_attach(源码位置drivers/base/dd.c):进行设备和驱动匹配,如果匹配成功,尝试进行绑定。1. 首先进行匹配确认:driver_match_device(drv,dev);调用关系:-drv-bus-match-i2c_device_match-of_driver_match_devicei2c_match_id可以看出,最终有两种方式进行驱动匹配查询:方法一:通过of_driver_match_device对比of_device_id;方法二:通过i2c_match_id对比id_table;方法二实际上就是对比i2c_driver-id_table-name和client-name是否一致。2. 如果匹配确认,进行驱动与设备绑定:driver_probe_device;调用关系:driver_probe_device-really_probe-dev-bus-probedriver_bound在really_probe中,首先将设备的驱动指针指向该驱动:dev-driver=drv。对应于i2c_bus_type,dev-bus-probe即是:i2c_device_probe,最终调用驱动的probe函数。最后是driver_bound,将驱动与设备进行绑定:其实就是调用klist_add_tail:将设备节点添加到驱动的klist_devices; 调用klist_add_tail,将被注册的驱动添加到总线的klist_drivers上;klist_add_tail(&priv-knode_bus,&bus-p-klist_drivers); module_add_driver(drv-owner,drv)在sysfs创建drivers目录3.2.设备注册方式一:i2c设备动态发现注册在i2c_register_driver的最后: INIT_LIST_HEAD(&driver-clients); /* Walk the adapters that are already present */ i2c_for_each_dev(driver, _process_new_driver);观察i2c_for_each_dev:int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *) int res; mutex_lock(&core_lock); res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn); mutex_unlock(&core_lock); return res;其实就是遍历i2c总线上的klist_devices链表,对得到的每一个device,执行_process_new_driver。跟踪_process_new_driver-i2c_do_add_adapter-i2c_detecti2c_detect实现了i2c设备发现:在注册驱动后,通过i2c_detect检测是否有适合的设备连接在总线上。i2c_detect实现如下: 在每一个adapter上遍历驱动给出的地址列表(address_list),由i2c_detect_address函数完成;最终会调用driver-detect(即设备驱动提供的设备发现函数); 如果发现满足条件的设备,执行i2c_new_device,为设备建立i2c_client;并且将设备添加到i2c_bus_type-p-klist_devices链表上(device_register),通过bus_add_device函数完成,最后调用bus_probe_device,尝试绑定驱动。 将client添加到驱动的设备链表上:list_add_tail(&client-detected,&driver-clients)方式二:i2c设备之静态注册Linux3.3提供了静态定义的方式来注册设备,接口原型:linux-3.3/drivers/i2c/i2c-boardinfo.cint _initi2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)核心内容: 申请structi2c_devinfo,用以描述一个i2c外设; list_add_tail(&devinfo-list,&_i2c_board_list),将devinfo加入链表_i2c_board_list,以供后续查找;扫描_i2c_board_list,创建clienti2c_register_board_info只是把设备描述符加入到了_i2c_board_list,并没有创建client,当调用i

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论