Zynq平台下linux的I2C驱动(RTC+EEPROM)

现在ARM下对SoC开发板的硬件描述都是采用devicetree文件,使用linux自带的dtc程序将dts编译成dtb之后,由u-boot将dtb导入给linux内核,linux内核读取dtb,然后注册设备的resource,linux内核使用of_系列函数API读取硬件资源。具体的说明可以看下

宋宝华的blog

http://blog.csdn.net/21cnbao/article/details/8457546


.dts文件根据具体的硬件配置好后,编译生成.dtb文件。

然后需要在menuconfig内核配置中为硬件选择驱动程序,只有硬件驱动程序和dts中的硬件名字匹配时,才能触发驱动的probe函数

rtc-8564和pcf8563的驱动是兼容的,均为pcf8563驱动。


 注:以下的分析基于3.12.0linux内核。个人分析难免存在纰漏,恳请大家指正。

一、I2C的linux主要涉及4个结构体:i2c_adapter,i2c_algorithm,i2c_client,i2c_driver

[cpp]  view plain  copy
  1. struct i2c_adapter {  
  2.     struct module *owner;  
  3.     unsigned int class;       /* classes to allow probing for */  
  4.     const struct i2c_algorithm *algo; /* the algorithm to access the bus */  
  5.     void *algo_data;  
  6.   
  7.     /* data fields that are valid for all devices   */  
  8.     struct rt_mutex bus_lock;  
  9.   
  10.     int timeout;            /* in jiffies */  
  11.     int retries;  
  12.     struct device dev;      /* the adapter device */  
  13.   
  14.     int nr;  
  15.     char name[48];  
  16.     struct completion dev_released;  
  17.   
  18.     struct mutex userspace_clients_lock;  
  19.     struct list_head userspace_clients;  
  20.   
  21.     struct i2c_bus_recovery_info *bus_recovery_info;  
  22. };  

i2c总线控制器数据依附于algo_data,比如xi2cps,s3c24xx_i2c。


struct device dev;成员表明i2c_adapter是一个硬件,对应SoC上的I2C控制器。

而i2c_algorithm则是这个I2C控制器的底层驱动程序。

同理:

[cpp]  view plain  copy
  1. struct i2c_client {  
  2.     unsigned short flags;       /* div., see below      */  
  3.     unsigned short addr;        /* chip address - NOTE: 7bit    */  
  4.                     /* addresses are stored in the  */  
  5.                     /* _LOWER_ 7 bits       */  
  6.     char name[I2C_NAME_SIZE];  
  7.     struct i2c_adapter *adapter;    /* the adapter we sit on    */  
  8.     struct i2c_driver *driver;  /* and our access routines  */  
  9.     struct device dev;      /* the device structure     */  
  10.     int irq;            /* irq issued by device     */  
  11.     struct list_head detected;  
  12. };  

struct i2c_client代表一个挂载到i2c总线上的i2c从设备,该设备所需要的数据结构,其中包括

  • 该i2c从设备所依附的i2c主设备 struct i2c_adapter *adapter
  • 该i2c从设备的驱动程序struct i2c_driver *driver
  • 作为i2c从设备所通用的成员变量,比如addr, name等
  • 该i2c从设备驱动所特有的数据,依附于dev->driver_data下,在i2c_driver中的probe函数中设置这个结构体成员。比如eeprom的eeprom_data。
  • 所有i2c从设备组成的双向链表:detected


struct device dev表明struct i2c_client代表的是一个硬件,比如eeprom芯片,或则rtc芯片,通过i2c总线连接到i2c_adapter硬件上。

而i2c_driver则是这个i2c_client芯片硬件的驱动程序。

我们一般会对每个I2C字符设备定义一个私有信息结构体,而i2c_client一般被包含在这个私有信息结构体中。看过LDR3源代码的hacker应该比较清楚。

 

 

i2c_client依附于i2c_adapter,也就是I2C设备和I2C总线控制器的对应关系,一个i2c_adapter可以挂接多个i2c_client,i2c_adapter的struct list_head userspace_clients;结构成员就是所有client的链表。

linux的最新版本基本上支持目前所有的I2C适配器硬件和I2C从设备,但是对于工程师来说,可能要面临各种情况:为i2c_adapter和i2c_client编写驱动程序。

 

二、I2C核心

    I2C核心是源码位于drivers/i2c/i2c-core.c,它并不依赖于硬件平台的接口函数,是I2C总线驱动和设备驱动的纽带。

增加/删除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adapter)      //调用i2c_register_adapter()

int i2c_del_adapter(struct i2c_adapter *adapter)

增加/删除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

int i2c_add_driver(struct i2c_driver *driver)         //调用i2c_register_driver

void i2c_del_driver(struct i2c_driver *driver)

增加/删除i2c_client

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

void i2c_unregister_device(struct i2c_client *client)

      注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函数。之后attach被merge到了i2c_new_device中,而detach直接被unregister取代。实际上这两个函数内部都是调用了device_register()和device_unregister()

 

I2C传输、发送接收

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

int i2c_master_send(struct i2c_client *client,const char *buf ,int count)

int i2c_master_recv(struct i2c_client *client, char *buf ,int count)

     i2c_transfer()函数用于进行I2C 适配器和I2C 设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。

      i2c_transfer()本身不能和硬件完成消息交互,它寻找i2c_adapter对应的i2c_algorithm,要实现数据传送就要实现i2c_algorithm的master_xfer(),这个函数与具体的硬件有关,大部分时间由厂商完成。

      i2c_transfer()通过调用__i2c_transfer()完成I2C通讯:

[cpp]  view plain  copy
  1. int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)  
  2. {  
  3.     unsigned long orig_jiffies;  
  4.     int ret, try;  
  5.   
  6.     /* Retry automatically on arbitration loss */  
  7.     orig_jiffies = jiffies;  
  8.     for (ret = 0, try = 0; try <= adap->retries; try++) {  
  9.         ret = adap->algo->master_xfer(adap, msgs, num);  
  10.         if (ret != -EAGAIN)  
  11.             break;  
  12.         if (time_after(jiffies, orig_jiffies + adap->timeout))  
  13.             break;  
  14.     }  
  15.   
  16.     return ret;  
  17. }  


可见retries为重传尝试次数,timeout为超时时间。

 

三、Linux I2C总线驱动

1、I2C适配器的加载和卸除

加载:申请硬件资源,比如IO地址,中断号,调用i2c_add_adapter加载适配器

i2c_add_adapter中会调用i2c_register_adapter函数

static int i2c_register_adapter(struct i2c_adapter *adap)
{
   ... ...

  device_register(&adap->dev);     //完成I2C主设备adapter的注册,即注册object和发送uevent等

  i2c_scan_static_board_info(adap);       //注册i2c_client

   ... ...
}

[cpp]  view plain  copy
  1. static void i2c_scan_static_board_info(struct i2c_adapter *adapter)  
  2. {  
  3.  struct i2c_devinfo *devinfo;  
  4.   
  5.  down_read(&__i2c_board_lock);  
  6.  list_for_each_entry(devinfo, &__i2c_board_list, list) {  
  7.   if (devinfo->busnum == adapter->nr  
  8.     && !i2c_new_device(adapter,  
  9.       &devinfo->board_info))  
  10.    dev_err(&adapter->dev,  
  11.     "Can't create device at 0x%02x\n",  
  12.     devinfo->board_info.addr);  
  13.  }  
  14.  up_read(&__i2c_board_lock);  
  15. }  


i2c_new_device调用device_register注册i2c从设备。

那么,这个I2C从设备组成的双向循环链表,是什么时候通过什么方式建立起来的呢?

以 /arch/arm/mach-pxa/saar.c 为例

static void __init saar_init(void)

{

   ... ...

saar_init_i2c();

  ........

}

[cpp]  view plain  copy
  1. static void __init saar_init_i2c(void)  
  2. {  
  3.  pxa_set_i2c_info(NULL);  
  4.  i2c_register_board_info(0, ARRAY_AND_SIZE(saar_i2c_info));  
  5. }  
[cpp]  view plain  copy
  1. static struct i2c_board_info saar_i2c_info[] = {  
  2.     [0] = {  
  3.         .type       = "da9034",  
  4.         .addr       = 0x34,  
  5.         .platform_data  = &saar_da9034_info,  
  6.         .irq        = PXA_GPIO_TO_IRQ(mfp_to_gpio(MFP_PIN_GPIO83)),  
  7.     },  
  8. };  



 

 /* drivers/i2c/i2c-boardinfo.c */

 int __init i2c_register_board_info(int busnum, structi2c_board_info const *info, unsigned len)
{

   ... ...

  struct i2c_devinfo *devinfo;
  devinfo->board_info = *info;
  list_add_tail(&devinfo->list, &__i2c_board_list);    //将I2C从设备加入该链表中
   ... ...
}

 所以,在系统初始化的过程中,我们可以通过 i2c_register_board_info,将所需要的I2C从设备加入一个名为__i2c_board_list双向循环链表,系统在成功加载I2C主设备adapt后,就会对这张链表里所有I2C从设备逐一地完成 i2c_client的注册。

也就是说,i2c_client和i2c_adapter都是由i2c_core来维护的。

在xilinx-linux中,i2c从设备是通过dts文件传递给内核的,内核通过zynq_init_machine函数注册所有的i2c从设备,i2c_client.

      在linux的设备和驱动管理体系中,所有的非热插拔设备默认是在 init_machine函数成员中加入相应维护设备的双向链表中,包括platform_device和其他的设备。当一个特定的设备驱动通过driver_register加入对应的总线下时,回去遍历对应总线下的设备双向链表,当驱动和设备匹配时,会触发驱动的probe函数。

[cpp]  view plain  copy
  1. DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")  
  2.     .smp        = smp_ops(zynq_smp_ops),  
  3.     .map_io     = zynq_map_io,  
  4.     .init_irq   = zynq_irq_init,  
  5.     .init_machine   = zynq_init_machine,  
  6.     .init_late  = zynq_init_late,  
  7.     .init_time  = zynq_timer_init,  
  8.     .dt_compat  = zynq_dt_match,  
  9.     .reserve    = zynq_memory_init,  
  10.     .restart    = zynq_system_reset,  
  11. MACHINE_END  


可以参考mach-zynq的电路板初始化代码



卸除:释放硬件资源,调用i2c_del_adapter卸载i2c适配器

void i2c_del_adapter(struct i2c_adapter *adap)

{

..........

[cpp]  view plain  copy
  1. list_for_each_entry_safe(client, next, &adap->userspace_clients,  
  2.              detected) {  
  3.     dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,  
  4.         client->addr);  
  5.     list_del(&client->detected);  
  6.     i2c_unregister_device(client);  
  7. }  

卸载所有的从i2c设备

..............

[cpp]  view plain  copy
  1. device_unregister(&adap->dev);  

卸载i2c适配器
..............

}

 

2、编写I2C的总线通讯方法algorithm

[cpp]  view plain  copy
  1. int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,  
  2.            int num);  
  3. /* To determine what the adapter supports */  
  4. u32 (*functionality) (struct i2c_adapter *);  


主要实现上面的两个函数。

 

  大部分时间,我们需要定义一个XXX_i2c结构体,比如drivers/i2c/busses/i2c-s3c2410.c中的struct s3c24xx_i2c。

XXX_i2c结构体中包含struct i2c_msg  *msg;struct i2c_adapter adap;void __iomem  *regs;等

struct i2c_msg  *msg接收用户层的数据发到i2c总线,或从i2c总线读取数据到用户层。

在适配器的probe函数中:

struct xi2cps *id;

platform_set_drvdata(pdev, id); 

id->adap.dev.of_node = pdev->dev.of_node;
id->adap.algo = (struct i2c_algorithm *) &xi2cps_algo;
id->adap.timeout = 0x1F;/* Default timeout value */
id->adap.retries = 3;/* Default retry value. */
id->adap.algo_data = id;
id->adap.dev.parent = &pdev->dev;


四、linux i2c从设备驱动

        硬件方面,I2C主设备已经集成在主芯片内,软件方面,linux也为我们提供了相应的驱动程序,位于drivers/i2c/bus下。那么接下来I2C从设备驱动就变得容易得多。既然系统加载I2C主设备驱动时已经注册了i2c_adapter和i2c_client,那么I2C从设备主要完成三大任务:

  • 系统初始化时添加以i2c_board_info为结构的I2C从设备的信息(针对不使用dts描述硬件信息的电路板)
  • 在I2C从设备驱动程序里使用i2c_adapter里所提供的算法,即实现I2C通信。
  • 将I2C从设备的特有数据结构挂在到i2c_client.dev->driver_data下。

以/driver/misc/eeprom/eeprom.c为例:

 

[cpp]  view plain  copy
  1. static struct i2c_driver eeprom_driver = {  
  2.     .driver = {  
  3.         .name   = "eeprom",  
  4.     },  
  5.     .probe      = eeprom_probe,  
  6.     .remove     = eeprom_remove,  
  7.     .id_table   = eeprom_id,  
  8.   
  9.     .class      = I2C_CLASS_DDC | I2C_CLASS_SPD,  
  10.     .detect     = eeprom_detect,  
  11.     .address_list   = normal_i2c,  
  12. };  


       i2c_driver 中的driver.name 不一定要和i2c_client一致,因为这只是他们配备的依据之一。id_table 是i2c_device_id结构体的一个对象,里面定义了i2c驱动对应设备的i2c地址。struct i2c_device_id里面的字符串与 I2C_BOARD_INFO里面的匹配后,xxx_led_probe也会调用,这是设备和驱动匹配的依据之二。

使用Device Tree后,驱动需要与.dts中描述的设备结点进行匹配,从而引发驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表

 
[cpp]  view plain  copy
  1. static const struct i2c_device_id pcf8563_id[] = {  
  2.     { "pcf8563", 0 },  
  3.     { "rtc8564", 0 },  
  4.     { }  
  5. };  
  6. MODULE_DEVICE_TABLE(i2c, pcf8563_id);  
  7. #ifdef CONFIG_OF  
  8. static const struct of_device_id pcf8563_of_match[] = {  
  9.     { .compatible = "nxp,pcf8563" },  
  10.     {}  
  11. };  
  12. MODULE_DEVICE_TABLE(of, pcf8563_of_match);  
  13. #endif  
  14. static struct i2c_driver pcf8563_driver = {  
  15.     .driver     = {  
  16.         .name   = "rtc-pcf8563",  
  17.         .owner  = THIS_MODULE,  
  18.         .of_match_table = of_match_ptr(pcf8563_of_match),  
  19.     },  
  20.     .probe      = pcf8563_probe,  
  21.     .id_table   = pcf8563_id,  
  22. };  
  23. /* Each client has this additional data */  


不过这边有一点需要提醒的是,I2C和SPI外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为<manufacturer>,<model>,别名其实就是去掉compatible 属性中逗号前的manufacturer前缀。关于这一点,可查看drivers/spi/spi.c的源代码,函数spi_match_device()暴露了更多的细节,如果别名出现在设备spi_driver的id_table里面,或者别名与spi_driver的name字段相同,SPI设备和驱动都可以匹配上:

[cpp]  view plain  copy
  1. struct eeprom_data {  
  2.     struct mutex update_lock;  
  3.     u8 valid;           /* bitfield, bit!=0 if slice is valid */  
  4.     unsigned long last_updated[8];  /* In jiffies, 8 slices */  
  5.     u8 data[EEPROM_SIZE];       /* Register values */  
  6.     enum eeprom_nature nature;  
  7. };  



static int eeprom_probe(struct i2c_client *client,
   const struct i2c_device_id *id)
{

 ... ...

struct eeprom_data *data;

......

i2c_set_clientdata(client, data);//将设备的数据结构挂到i2c_client.dev->p->driver_data下
.............

name[0] = i2c_smbus_read_byte_data(client, 0x80);//i2c-core提供的接口,利用i2c_adapter的算法实现I2C通信


}

 这部分内容建议参考http://blog.csdn.net/zclongembedded/article/details/8207022

以及http://blog.csdn.net/zclongembedded/article/details/8207722

也就是说,i2c_adapter是一个i2c总线控制器,i2c_add_driver会把i2c_driver挂到i2c总线上,并搜索总线上所有和它匹配的i2c_client,成功的话i2c_driver的probe函数就会被调用,搜索到的i2c_client会作为参数传递给probe函数。因为一个i2c_driver可能被多个i2c_client使用,因此就了解i2c_set_clientdata(client, data);调用的必要性了。就是说多个clients可以用一个driver,但是各自有自己的私有数据。

 

注:module_i2c_driver是一个针对i2c的宏定义, 

定义位于/include/linux/i2c.h/

[cpp]  view plain  copy
  1. /** 
  2.  * module_i2c_driver() - Helper macro for registering a I2C driver 
  3.  * @__i2c_driver: i2c_driver struct 
  4.  * 
  5.  * Helper macro for I2C drivers which do not do anything special in module 
  6.  * init/exit. This eliminates a lot of boilerplate. Each module may only 
  7.  * use this macro once, and calling it replaces module_init() and module_exit() 
  8.  */  
  9. #define module_i2c_driver(__i2c_driver) \  
  10.     module_driver(__i2c_driver, i2c_add_driver, \  
  11.             i2c_del_driver)  


 

使用module_i2c_driver(xxx_i2c_driver)

可以取代

 -static int __init xxx_i2c_init(void) 
-{ 
-       return i2c_add_driver(&xxx_i2c_driver); 
-} 

-static void __exit xxx_i2c_exit(void) 
-{ 
-       i2c_del_driver(&xxx_i2c_driver); 
-} 


-module_init(xxx_i2c_init); 
-module_exit(xxx_i2c_exit); 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值