I2C总线只有两根线,分别是串行数据线SDA和时钟线SCL,方便了工程人员的布线。I2C是多主机制。I2C协议不再多说。
参考文档:http://blog.sina.com.cn/s/blog_a56ef549010187m2.html
Linux的i2c分为i2c核心、i2c总线驱动和i2c设备驱动。
I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm),上层的,与具体适配器(adapter)无关的代码以及探测设备、检测设备地址的上层代码。对应的主要文件为i2c-core.c。这一部分是linux本身提供的,我们都不需要修改。但是知道函数是什么功能,还是很有帮助的。因为在驱动中发送数据时往往会调用到i2c-core.c中的函数。比较常用的函数主要有:i2c_add_adapter、i2c_register_driver、i2c_smbus_read_byte、i2c_smbus_write_byte、i2c_smbus_xfer等等等等。
I2C总线驱动:I2C总线驱动是I2C适配器的软件实现。I2C适配器可由CPU控制,也可以集成在CPU内部。davinci的I2C总线驱动主要是i2c-davinci.c文件,dm365也是使用的这个文件。
I2C设备驱动:I2C设备驱动是对I2C设备的软件实现。由于一般I2C总线驱动一般都有提供,我们主要编写的也就是这一部分设备驱动。
I2C总线驱动的工作流程:
在devices.c中添加总线设备,即定义platform_device,并调用platform_device_register函数对总线设备进行注册。
个人理解:这里应该就是定义并注册了一个i2c adapter。
在i2c-davinc.c文件中添加总线驱动。
这里开始就需要提到几个文件:
1、devices.c:位于arch/arm/mach-davinci中。devices.c中主要是定义了一些davinci的相关设备,在文件的注释中有:DaVinci platform device setup/initialization
即对platform device的设置/初始化。
在devices.c中开始部分宏定义中定义了相关外设的起始地址等相关信息,若I2C、MMCSD0、MMCSD1等。
函数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 resource和struct 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设备。
下面类似,分别定义的资源为:ide、mmcsd0、mmcsd1、wdt(watchdog)、pcm、timer等,并调用相关的函数进行初始化/设置,这里不具体尽心分析了。
所以,综上可以看出,devices.c的主要作用就是对davinci系列中的设备资源进行定义、初始化设置等工作。
2、i2c-davinci.c:位于drivers/i2c/busses文件夹中。这个就是我们davinci设备的总线驱动,在其文件注释中有:TI DAVINCI I2C adapter driver.在这个总线驱动中就能看到对davinci系列芯片的I2C寄存器的操作。
在文件开始的宏定义中定义了davinci芯片有关I2C的寄存器的偏移地址以及一些寄存器的相关位定义,如IMR(I2C Interrupt Mask Register)、MDR(I2C Mode Register)、STR(I2C Interrupt Status Register)、IVR(I2C Interrupt Vector Register)的相关位。
davinci_i2c_write_reg、davinci_i2c_read_reg分别是写寄存器和读寄存器。
generic_i2c_clock_pulse是在SCL时钟引脚上产生一个脉冲。
i2c_recover_bus函数作用是i2c bus recovery。这个代码注释中说参考i2c protocol中bus clear一节,但是我没有找到。函数的实现主要是调用了之前的davinci_i2c_read_reg和davinci_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_reg和davinci_i2c_write_reg函数对MDR寄存器进行操作,主要是操作IRS位(查阅datasheet,IRS是i2c reset bit)
i2c_davinci_calc_clk_dividers是计算预分频、时钟,具体为什么这么实现不是很懂。就是按照注释中的进行运算。同时,利用读写寄存器函数对CLKH、CLKL和PSC寄存器进行操作。
i2c_davinci_init函数:配置I2C,同时使能I2C。在初始化I2C和I2C发生error时,调用此函数。函数主要是调用之前的davinci_i2c_reset_ctrl和i2c_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:这个函数好像是使工作在I2C和SMbus模式下。不是很懂。
terminate_read:读取DRR寄存器中的数据;
terminate_write:操作MDR寄存器
二者都是在函数i2c_davinci_isr中被调用。
i2c_davinci_cpufreq_transition、i2c_davinci_cpufreq_register、i2c_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函数中定义了支持I2C和SMBus,i2c_davinci_xfer则定义了传递message的方法。
接下来两个函数:davinci_i2c_probe和davinci_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_device,name也是i2c_davinci,当找到对应的adapter时,就会执行davinci_i2c_probe函数.
最后是module_init和module_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_driver和i2c_del_driver函数都是定于在i2c-core.c中的,属于i2c核心范围。
另外,在rtc-ds1307.c中定义了struct i2c_device_id ds1307_id[]这个table,table中有ds1307这个name;并调用宏MODULE_DEVICE_TABLE(i2c, ds1307_id);
在定义了i2c_driver后,当设备注册ds1307_driver时,就会匹配ds1307_id[]中的name和I2C_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_device、rtc_sysfs_add_device、rtc_proc_add_device三个函数分别注册cdev、在sysfs和proc下产生相关文件。
而在rtc-ds1307.c中则定义了struct rtc_class_ops,在结构体中定义了相关操作,如read time,set time等。
总的来说,整个i2c驱动的思路大致就是这样。
有参考几个文档:http://blog.csdn.net/lanmanck/article/details/7833546
http://blog.csdn.net/lanmanck/article/details/7836734
还有之前自己写的几个相关文档。