DSP

dm365的i2c总结最终版(菜鸟入门,欢迎拍砖)

2019-07-13 15:16发布

I2C总线只有两根线,分别是串行数据线SDA和时钟线SCL,方便了工程人员的布线。I2C是多主机制。I2C协议不再多说。   参考文档:http://blog.sina.com.cn/s/blog_a56ef549010187m2.html   Linuxi2c分为i2c核心、i2c总线驱动和i2c设备驱动。 I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm),上层的,与具体适配器(adapter)无关的代码以及探测设备、检测设备地址的上层代码。对应的主要文件为i2c-core.c。这一部分是linux本身提供的,我们都不需要修改。但是知道函数是什么功能,还是很有帮助的。因为在驱动中发送数据时往往会调用到i2c-core.c中的函数。比较常用的函数主要有:i2c_add_adapteri2c_register_driveri2c_smbus_read_bytei2c_smbus_write_bytei2c_smbus_xfer等等等等。   I2C总线驱动:I2C总线驱动是I2C适配器的软件实现。I2C适配器可由CPU控制,也可以集成在CPU内部。davinciI2C总线驱动主要是i2c-davinci.c文件,dm365也是使用的这个文件。   I2C设备驱动:I2C设备驱动是对I2C设备的软件实现。由于一般I2C总线驱动一般都有提供,我们主要编写的也就是这一部分设备驱动。   I2C总线驱动的工作流程: devices.c中添加总线设备,即定义platform_device,并调用platform_device_register函数对总线设备进行注册。 个人理解:这里应该就是定义并注册了一个i2c adapter   i2c-davinc.c文件中添加总线驱动。   这里开始就需要提到几个文件: 1devices.c:位于arch/arm/mach-davinci中。devices.c中主要是定义了一些davinci的相关设备,在文件的注释中有:DaVinci platform device setup/initialization 即对platform device的设置/初始化。   devices.c中开始部分宏定义中定义了相关外设的起始地址等相关信息,若I2CMMCSD0MMCSD1等。   函数davinci_map_sysmod中调用了函数:ioremap_nocache( )ioremap_nocache作用是把内存映射到CPU空间,返回值为线性地址,此时CPU就可以访问设备的内存了,可以像访问内存一样使用访问内存的指令访问设备的内存(寄存器)。 参考:http://blog.csdn.net/shansan/article/details/5003062 http://blog.csdn.net/huguohu2006/article/details/6396866 davinci_map_sysmod函数是将System module映射到CPU空间,大小为2K,起始地址为0x01c40000,可查阅datasheet  接下来定义了struct resourcestruct platform_device结构体,并调用davinci_init_i2c初始化i2c 定义一个platform_device一般需要初始化两方面的内容:设备占用的资源resource和设备私有数据dev.platform_data,最重要的是resource。设备占用的资源主要是两个方面:IO内存和irq资源。 davinci_init_i2c函数中就有对设备私有数据dev.platform_data的初始化。并调用platform_device_register函数来注册i2c设备。   下面类似,分别定义的资源为:idemmcsd0mmcsd1wdt(watchdog)pcmtimer等,并调用相关的函数进行初始化/设置,这里不具体尽心分析了。 所以,综上可以看出,devices.c的主要作用就是对davinci系列中的设备资源进行定义、初始化设置等工作。   2i2c-davinci.c:位于drivers/i2c/busses文件夹中。这个就是我们davinci设备的总线驱动,在其文件注释中有:TI DAVINCI I2C adapter driver.在这个总线驱动中就能看到对davinci系列芯片的I2C寄存器的操作。   在文件开始的宏定义中定义了davinci芯片有关I2C的寄存器的偏移地址以及一些寄存器的相关位定义,如IMRI2C Interrupt Mask Register)、MDRI2C Mode Register)、STRI2C Interrupt Status Register)、IVRI2C Interrupt Vector Register)的相关位。   davinci_i2c_write_regdavinci_i2c_read_reg分别是写寄存器和读寄存器。 generic_i2c_clock_pulse是在SCL时钟引脚上产生一个脉冲。 i2c_recover_bus函数作用是i2c bus recovery。这个代码注释中说参考i2c protocolbus clear一节,但是我没有找到。函数的实现主要是调用了之前的davinci_i2c_read_regdavinci_i2c_write_reg两个函数,对MDR寄存器进行操作,先设置NACK位为1,然后调用generic_i2c_clock_pulse函数在SCL引脚上产生一个脉冲,最后设置STOP位为1.便实现了bus recovery  davinci_i2c_reset_ctrl函数是设置i2c总线是否复位,发送0则复位i2c总线,发送1则使i2c总线out of reset。同样是调用davinci_i2c_read_regdavinci_i2c_write_reg函数对MDR寄存器进行操作,主要是操作IRS位(查阅datasheetIRSi2c reset bit  i2c_davinci_calc_clk_dividers是计算预分频、时钟,具体为什么这么实现不是很懂。就是按照注释中的进行运算。同时,利用读写寄存器函数对CLKHCLKLPSC寄存器进行操作。   i2c_davinci_init函数:配置I2C,同时使能I2C。在初始化I2CI2C发生error时,调用此函数。函数主要是调用之前的davinci_i2c_reset_ctrli2c_davinci_calc_clk_dividers函数,并开了中断。   i2c_davinci_wait_bus_not_busy:读取STR寄存器中bus busy位,若忙则等待; i2c_davinci_xfer_msg:底层主设备读写发送信息函数。 i2c_davinci_xfer:控制器发送函数需要调用这个函数,而在这个函数中调用了更加底层的i2c_davinci_xfer_msg函数。   i2c_davinci_func:这个函数好像是使工作在I2CSMbus模式下。不是很懂。   terminate_read:读取DRR寄存器中的数据; terminate_write:操作MDR寄存器 二者都是在函数i2c_davinci_isr中被调用。   i2c_davinci_cpufreq_transitioni2c_davinci_cpufreq_registeri2c_davinci_cpufreq_deregister等函数是有关于CPU频率的,函数实现不是很懂。   i2c_davinci_isr函数是Interrupt service routine,中断处理例程,当发生I2C中断的时候会调用这个函数。   接下来定义了i2c_algorithm static struct i2c_algorithm i2c_davinci_algo = {          .master_xfer   = i2c_davinci_xfer,          .functionality   = i2c_davinci_func, }; 这里定义了如何传递函数以及支持何种adapter,在之前i2c_davinci_func函数中定义了支持I2CSMBusi2c_davinci_xfer则定义了传递message的方法。   接下来两个函数:davinci_i2c_probedavinci_i2c_remove是在定义了platform_driver中调用的。   static const struct dev_pm_ops davinci_i2c_pm = {          .suspend        = davinci_i2c_suspend,          .resume         = davinci_i2c_resume, }; 这里定义了一个dev_pm_ops结构体,网上查到是跟电源管理有关,这里我还没有细看,回头要好好看看。   static struct platform_driver davinci_i2c_driver = {          .probe                = davinci_i2c_probe,          .remove            = davinci_i2c_remove,          .driver                = {                    .name       = "i2c_davinci",                    .owner     = THIS_MODULE,                    .pm  = davinci_i2c_pm_ops,          }, }; 这里定义了platform_driver,在devices.c中定义了相对应的platform_devicename也是i2c_davinci,当找到对应的adapter时,就会执行davinci_i2c_probe函数.   最后是module_initmodule_exit函数的调用。至此,整个总线驱动文件i2c_davinci.c文件就分析结束了。 总结一下,总线驱动主要做的几个事情;1、获取平台设备所需要的各种资源,这一部分主要是在devices.c中完成,定义struct resource结构体;2、将I2C总线驱动添加到系统上,并设置I2C总线驱动的算法,即在i2c_davinci.c中定义了i2c_algorithm结构体,调用platform_driver_register函数注册platform_driver   在完成了总线驱动的分析后,分析一下设备驱动部分。每种嵌入式设备I2C总线接的设备不同,因此设备驱动部分可能主要是我们要做的部分。 流程:1)跟总线驱动类似,需要先注册设备信息。只不过总线驱动在devices.c中,而设备驱动添加在board_dm365_evm.c中。 static struct i2c_board_info i2c_info[] = {          {                    I2C_BOARD_INFO("24c256", 0x50),                    .platform_data         = &eeprom_info,          },          {                    I2C_BOARD_INFO("tlv320aic3x", 0x18),          }, }; 这个是源码中的,我们在其中添加自己的设备信息。I2C_BOARD_INFO中前面是name,后面是设备的地址。并在evm_init_i2c中调用i2c_register_board_info函数注册i2c_info   这里我用源码中的rtc-ds1307.c文件做一个大概分析吧,这是一个实时时钟。接的是I2C总线。 设备驱动中需要有驱动入口函数module_init和驱动退出函数module_exit,而我们利用module_i2c_driver宏即可代替上述两个函数,简化了代码。 文件中代码有: static struct i2c_driver ds1307_driver = {          .driver = {                    .name       = "rtc-ds1307",                    .owner     = THIS_MODULE,          },          .probe                = ds1307_probe,          .remove            = __devexit_p(ds1307_remove),          .id_table  = ds1307_id, }; 先定义了i2c_driver,然后调用宏:module_i2c_driver(ds1307_driver); 相当于: static __init int ds1307_init(void) {          return i2c_add_driver(&ds1307_driver); }   static __exit void ds1307_exit(void) {          i2c_del_driver(&ds1307_driver); } module_init(ds1307_init); module_exit(ds1307_exit); i2c_add_driveri2c_del_driver函数都是定于在i2c-core.c中的,属于i2c核心范围。   另外,在rtc-ds1307.c中定义了struct i2c_device_id ds1307_id[]这个tabletable中有ds1307这个name;并调用宏MODULE_DEVICE_TABLE(i2c, ds1307_id);   在定义了i2c_driver后,当设备注册ds1307_driver时,就会匹配ds1307_id[]中的nameI2C_BOARD_INFO中的name信息。名称相同的话就会执行probe函数。   probe函数完成的主要功能是:分配设备号,利用alloc_chrdev_region函数,构造file_operations,分配设置注册cdev  对于ds1307来说,在ds1307_probe函数中,对ds1307的相关寄存器进行操作,并调用rtc_device_register函数。 rtc_device_register函数定义在class.c文件中,其中设置了struct rtc_class_ops,并调用rtc_dev_add_devicertc_sysfs_add_devicertc_proc_add_device三个函数分别注册cdev、在sysfsproc下产生相关文件。   而在rtc-ds1307.c中则定义了struct rtc_class_ops,在结构体中定义了相关操作,read timeset time等。   总的来说,整个i2c驱动的思路大致就是这样。 有参考几个文档:http://blog.csdn.net/lanmanck/article/details/7833546 http://blog.csdn.net/lanmanck/article/details/7836734 还有之前自己写的几个相关文档。