msm7227平臺linux I2C驅動分析(2.6.29)

Revision History
 
Date Issue Description Author   
<08/07/2010> <1.0> Msm7227平臺I2C驅動分析 滕景東   
      
    
目錄
1. 摘要 3
2. 簡介 3
3. I2C架構 3
4. I2C總線初始化 4
5. I2C適配器驅動 5
6. I2C設備驅動 9
7. 用戶空間驅動支持 12
8. 數據傳輸框架 16
9. References 16

1. 摘要
主要介紹Msm7227平臺上I2C驅動原理,多數部分是29內核標準架構。
2. 簡介
I2C只有兩條線,一條串行數據線:SDA,一條是時鐘線SCL。I2C是一種多主機控制總線,同一總線上可允許多個master.
i2c總線適配器(adapter)就是一條i2c總線的控制器,在物理連接上若干i2c設備。在linux驅動中,每種處理器平臺有自己的適配器驅動。
3. I2C架構

內核中i2c相關代碼可以分爲三個層次:
i2c框架層:i2c.h和i2c-core.c爲其主體框架代碼,提供了核心數據結構的定義、i2c適配器驅動和設備驅動的註冊、註銷管理等;i2c-dev.c用於創建i2c適配器的/dev/i2c-%d設備節點,提供i2c設備的用戶空間訪問方法等。
i2c總線適配器驅動:i2c/busses/目錄下,如i2c-msm.c。定義描述具體i2c總線適配器的i2c_adapter數據結構、實現在具體i2c適配器上的i2c總線通信的具體實現,並由i2c_algorithm數據結構描述與i2c設備通信的方法。
i2c設備驅動:定義描述具體設備的i2c_client和可能的私有數據結構。

                    
 
 上圖展示了內核I2C結構大整體框架,以下根據內核加載順序介紹I2C總線初始化,I2C總線適配器驅動,I2C設備驅動和用戶空間驅動支持及數據傳輸框架五部分介紹。
4. I2C總線初始化
 
該過程主要完成了sysfs總線結構,最終形成如下結構:
/sys/bus/i2c/
|-- devices
|-- drivers
|   |-- dummy
|      |-- bind
|      |-- uevent
|      `-- unbind
|-- drivers_autoprobe
|-- drivers_probe
`-- uevent

/sys/class/i2c-adapter/
dummy_driver僅僅是註冊了一個空的設備驅動,註冊驅動時會遍歷加載/sys/class/i2c-adapter/中的所有設備,該過程在初始話總線過程中完成,/sys/class/i2c-adapter/基本爲空,所以我認爲這裏的驅動註冊只是驗證i2c總線結構的完整性考慮的。
5. I2C適配器驅動


Linux內核的所有適配器驅動程序都在driver/i2c/busses/目錄下,當前高通的驅動是i2c-msm.c,適配器驅動的註冊過程如下:

 
在kernel中提供了兩個adapter註冊接口,分別爲i2c_add_adapter()和i2c_add_numbered_adapter().由於在系統中可能存在多個adapter,因爲將每一條I2C總線對應一個編號,下文中稱爲I2C總線號。對於i2c_add_adapter()而言,它使用的是動態總線號,即由系統給其分配一個總線號,而i2c_add_numbered_adapter()則是自己指定總線號,如果這個總線號非法或者是被佔用,就會註冊失敗。高通的adapter驅動使用了i2c_add_numbered_adapter()註冊,總線號最初保存在platform_data中。
I2C adapter以platform_device方式註冊進系統,在proble函數中初始化了struct i2c_adapter結構:


struct i2c_adapter {  
 struct module *owner;  
 unsigned int id;  
 unsigned int class;    /* classes to allow probing for */ 
 const struct i2c_algorithm *algo; /* the algorithm to access the bus */ 
 void *algo_data;  
 
 /* --- administration stuff. */ 
 int (*client_register)(struct i2c_client *);  
 int (*client_unregister)(struct i2c_client *);  
 
 /* data fields that are valid for all devices */ 
 u8 level;    /* nesting level for lockdep */ 
 struct mutex bus_lock;  
 struct mutex clist_lock;  
 
 int timeout;   /* in jiffies */ 
 int retries;  
 struct device dev;  /* the adapter device */ 
 
 int nr; /*該成員描述了總線號*/ 
 struct list_head clients; /* i2c_client結構鏈表,該結構包含device,driver和 
adapter結構*/ 
 char name[48];  
 struct completion dev_released;  
}; 
struct i2c_adapter {
 struct module *owner;
 unsigned int id;
 unsigned int class;    /* classes to allow probing for */
 const struct i2c_algorithm *algo; /* the algorithm to access the bus */
 void *algo_data;

 /* --- administration stuff. */
 int (*client_register)(struct i2c_client *);
 int (*client_unregister)(struct i2c_client *);

 /* data fields that are valid for all devices */
 u8 level;    /* nesting level for lockdep */
 struct mutex bus_lock;
 struct mutex clist_lock;

 int timeout;   /* in jiffies */
 int retries;
 struct device dev;  /* the adapter device */

 int nr; /*該成員描述了總線號*/
 struct list_head clients; /* i2c_client結構鏈表,該結構包含device,driver和
adapter結構*/
 char name[48];
 struct completion dev_released;
};

其中nr的值是在arch/arm/mach-msm/devices.c中定義的:
struct platform_device msm_device_i2c = {  
 .name  = "msm_i2c",  
 .id  = 0,  
 .num_resources = ARRAY_SIZE(resources_i2c),  
 .resource = resources_i2c,  
};  
struct platform_device msm_device_i2c_2 = {  
 .name  = "msm_i2c",  
 .id  = 2,  
 .num_resources = ARRAY_SIZE(resources_i2c_2),  
 .resource = resources_i2c_2,  
}; 
struct platform_device msm_device_i2c = {
 .name  = "msm_i2c",
 .id  = 0,
 .num_resources = ARRAY_SIZE(resources_i2c),
 .resource = resources_i2c,
};
struct platform_device msm_device_i2c_2 = {
 .name  = "msm_i2c",
 .id  = 2,
 .num_resources = ARRAY_SIZE(resources_i2c_2),
 .resource = resources_i2c_2,
};

該結構以參數形式傳進i2c_add_numbered_adapter(),下一步將進入


static int i2c_register_adapter(struct i2c_adapter *adap)  
{  
 int res = 0, dummy;  
 
 /* Can't register until after driver model init */ 
 if (unlikely(WARN_ON(!i2c_bus_type.p)))  
  return -EAGAIN;  
 
 mutex_init(&adap->bus_lock);  
 mutex_init(&adap->clist_lock);  
 INIT_LIST_HEAD(&adap->clients);/*初始化設備鏈表*/ 
 
 mutex_lock(&core_lock);  
 
 /* Add the adapter to the driver core. 
  * If the parent pointer is not set up, 
  * we add this adapter to the host bus. 
  */ 
 if (adap->dev.parent == NULL) {  
  adap->dev.parent = &platform_bus;/*父設備是platform_bus*/ 
  pr_debug("I2C adapter driver [%s] forgot to specify " 
    "physical device/n", adap->name);  
 }  
 dev_set_name(&adap->dev, "i2c-%d", adap->nr);/*設備節點名字*/ 
 adap->dev.release = &i2c_adapter_dev_release;  
 adap->dev.class = &i2c_adapter_class;  
 res = device_register(&adap->dev); /*註冊adapter這個設備本身*/ 
 if (res)  
  goto out_list;  
 
 dev_dbg(&adap->dev, "adapter [%s] registered/n", adap->name);  
 
 /*以下部分完成i2c設備和驅動的註冊*/ 
 if (adap->nr < __i2c_first_dynamic_bus_num)/*主板初始化時的動態總線號,該值已導出符號表*/ 
  i2c_scan_static_board_info(adap);/*完成新類型i2c設備的註冊,一般只在主板初始化時*/ 
 
 /* Notify drivers */ 
 dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,  
     i2c_do_add_adapter);  /*探測總線上的所有i2c設備驅動,同時完成client、driver、device、adapter的綁定,但driver->address_data非空的情況下有用,而這又意味着只對舊的i2c機制有效*/ 
 
out_unlock:  
 mutex_unlock(&core_lock);  
 return res;  
 
out_list:  
 idr_remove(&i2c_adapter_idr, adap->nr);  
 goto out_unlock;  

static int i2c_register_adapter(struct i2c_adapter *adap)
{
 int res = 0, dummy;

 /* Can't register until after driver model init */
 if (unlikely(WARN_ON(!i2c_bus_type.p)))
  return -EAGAIN;

 mutex_init(&adap->bus_lock);
 mutex_init(&adap->clist_lock);
 INIT_LIST_HEAD(&adap->clients);/*初始化設備鏈表*/

 mutex_lock(&core_lock);

 /* Add the adapter to the driver core.
  * If the parent pointer is not set up,
  * we add this adapter to the host bus.
  */
 if (adap->dev.parent == NULL) {
  adap->dev.parent = &platform_bus;/*父設備是platform_bus*/
  pr_debug("I2C adapter driver [%s] forgot to specify "
    "physical device/n", adap->name);
 }
 dev_set_name(&adap->dev, "i2c-%d", adap->nr);/*設備節點名字*/
 adap->dev.release = &i2c_adapter_dev_release;
 adap->dev.class = &i2c_adapter_class;
 res = device_register(&adap->dev); /*註冊adapter這個設備本身*/
 if (res)
  goto out_list;

 dev_dbg(&adap->dev, "adapter [%s] registered/n", adap->name);

 /*以下部分完成i2c設備和驅動的註冊*/
 if (adap->nr < __i2c_first_dynamic_bus_num)/*主板初始化時的動態總線號,該值已導出符號表*/
  i2c_scan_static_board_info(adap);/*完成新類型i2c設備的註冊,一般只在主板初始化時*/

 /* Notify drivers */
 dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
     i2c_do_add_adapter);  /*探測總線上的所有i2c設備驅動,同時完成client、driver、device、adapter的綁定,但driver->address_data非空的情況下有用,而這又意味着只對舊的i2c機制有效*/

out_unlock:
 mutex_unlock(&core_lock);
 return res;

out_list:
 idr_remove(&i2c_adapter_idr, adap->nr);
 goto out_unlock;
}

i2c_scan_static_board_info對應的初始化過程在board-msm7x27.c中完成,
i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices));
6. I2C設備7. 驅動
驅動的編寫方法已在《msm7227-I2C設備驅動實現要點.doc》中介紹,此節分析驅動和設備的註冊過程。
 
本還想詳細分析代碼,但發現,這張圖已經足夠說明i2c驅動的註冊過程了,下面對我看代碼時碰到的一些問題簡要分析。
設備和驅動的關聯
大家知道,對於一個驅動程序有兩個元素不可或缺,即設備和驅動,一般驅動都是通過設備名和驅動名的匹配建立關係的,我從i2c/chips/裏看到的示例代碼了只能發現驅動的註冊,卻不見設備註冊的蹤影,令人疑惑,跟蹤發現,在i2c adapter註冊時會遍歷i2c_board_info這樣一個結構,而這個結構在29以前或更早的內核裏是不存在的,該數據結構在board-msm7x27.c中初始化了i2c設備名及設備地址,這便解決了驅動與設備的匹配問題,同時器件地址的提供也有所改變,舊的內核是在驅動中使用一個normal_i2c數組保存地址的。
名字匹配
一個i2c驅動是可以有多個名字的,即一個驅動程序可以支持多個設備,該機制是通過 struct i2c_device_id實現的,驅動中建立這麼一個結構體數組,i2c架構層便會掃描該數組,與設備名去匹配,匹配成功的都會進入相應probe函數。
進入probe
該過程困惑了我一段時間,其實要進入自己驅動的probe首先需要進入總線的probe,而進入總線probe的前提是與總線的match成功,具體實現大家可以根據上面的圖看一下相應代碼便知。
設備模型
I2C的架構充分利用的設備模型的原理及sysfs的實現,我認爲理解i2C架構前先了解一下設備模型是很有必要的。這裏將我的個人理解總結一下:
 Kobject是設備模型的最小單位,kset是對kobject的集合,struct driver_private、struct device等結構都內嵌了kobject,kset也內嵌kobject用於表徵自己。相同特性的kset的合集又構成了subsys,舉個不太恰當的類比:
kobject之於設備或驅動;kset之於某一類設備,如i2c;subsys之於子系統,如輸入子系統。其實在29內核中subsys就是一個kset結構,貼兩張圖理解一下:
 
8. 用戶空間驅動支持
這部分在i2c-dev.c中實現,這部分內容簡單的說就是通過內嵌一個具有file_operations的標準字符設備驅動來虛擬i2c設備,這樣,就可以在用戶空間直接操作i2c設備了。
流程如下圖:
 
餘下的就是常規file_operation了,open操作:
static int i2cdev_open(struct inode *inode, struct file *file)  
{  
 unsigned int minor = iminor(inode);  
 struct i2c_client *client;  
 struct i2c_adapter *adap;  
 struct i2c_dev *i2c_dev;  
 int ret = 0;  
 
 lock_kernel();/*內核上鎖,一般只在多cpu是有用*/ 
 i2c_dev = i2c_dev_get_by_minor(minor);/*因爲有兩個adapter,同一個主設備號*/ 
 if (!i2c_dev) {  
  ret = -ENODEV;  
  goto out;  
 }  
 
 adap = i2c_get_adapter(i2c_dev->adap->nr);  
 if (!adap) {  
  ret = -ENODEV;  
  goto out;  
 }  
 
 /* This creates an anonymous i2c_client, which may later be 
  * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE. 
  * 
  * This client is ** NEVER REGISTERED ** with the driver model 
  * or I2C core code!!  It just holds private copies of addressing 
  * information and maybe a PEC flag. 
  */ 
 client = kzalloc(sizeof(*client), GFP_KERNEL);  
 if (!client) {  
  i2c_put_adapter(adap);  
  ret = -ENOMEM;  
  goto out;  
 }  
 snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);  
 client->driver = &i2cdev_driver;/*綁定字符設備驅動*/ 
 
 client->adapter = adap;  
 file->private_data = client;  
 
out:  
 unlock_kernel();  
 return ret;  

static int i2cdev_open(struct inode *inode, struct file *file)
{
 unsigned int minor = iminor(inode);
 struct i2c_client *client;
 struct i2c_adapter *adap;
 struct i2c_dev *i2c_dev;
 int ret = 0;

 lock_kernel();/*內核上鎖,一般只在多cpu是有用*/
 i2c_dev = i2c_dev_get_by_minor(minor);/*因爲有兩個adapter,同一個主設備號*/
 if (!i2c_dev) {
  ret = -ENODEV;
  goto out;
 }

 adap = i2c_get_adapter(i2c_dev->adap->nr);
 if (!adap) {
  ret = -ENODEV;
  goto out;
 }

 /* This creates an anonymous i2c_client, which may later be
  * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
  *
  * This client is ** NEVER REGISTERED ** with the driver model
  * or I2C core code!!  It just holds private copies of addressing
  * information and maybe a PEC flag.
  */
 client = kzalloc(sizeof(*client), GFP_KERNEL);
 if (!client) {
  i2c_put_adapter(adap);
  ret = -ENOMEM;
  goto out;
 }
 snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
 client->driver = &i2cdev_driver;/*綁定字符設備驅動*/

 client->adapter = adap;
 file->private_data = client;

out:
 unlock_kernel();
 return ret;
}
 
注意這裏分配並初始化了一個struct i2c_client結構.但是沒有註冊這個clinet.此外,這個函數中還有一個比較奇怪的操作.不是在前面已經將i2c_dev->adap指向要操作的adapter麼?爲什麼還要以adapter->nr爲關鍵字從i2c_adapter_idr去找這個操作的adapter呢?注意了,調用i2c_get_adapter()從總線號nr找到操作的adapter的時候,還會增加module的引用計數.這樣可以防止模塊意外被釋放掉.也許有人會有這樣的疑問,那 i2c_dev->adap->nr操作,如果i2c_dev->adap被釋放掉的話,不是一樣會引起系統崩潰麼?這裏因爲,在i2cdev_attach_adapter()間接的增加了一次adapter的一次引用計數.如下:
view plaincopy to clipboardprint?
static int i2cdev_attach_adapter(struct i2c_adapter *adap)  
{  
......  
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,  
                     MKDEV(I2C_MAJOR, adap->nr),  
                     "i2c-%d", adap->nr);  
......  

static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
......
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                     MKDEV(I2C_MAJOR, adap->nr),
                     "i2c-%d", adap->nr);
......
}
看到了麼,i2c_dev內嵌的device是以adap->dev爲父結點,在device_create()中會增次adap->dev的一次引用計數.
好了,open()操作到此就完成了.
使用方法:(參考kernel-test/i2c-msm-test.c)
1、構造struct i2c_msg
[讀] struct i2c_msg msgs[] = {  
  [0] = {  
   .addr = slave_address,  
   .flags = 0,  
   .buf = (void *)offset_data,  
   .len = ARRAY_SIZE(offset_data),  
  },  
  [1] = {  
   .addr = slave_address,  
   .flags = I2C_M_RD,  
   .buf = (void *)buf,  
   .len = count,  
  },  
 };  
[寫] struct i2c_msg msgs[] = {  
  [0] = {  
   .addr = slave_address,  
   .flags = 0,  
   .buf = (void *)data,  
   .len = (2 + len) * sizeof(*data),  
  },  
  } 
[讀] struct i2c_msg msgs[] = {
  [0] = {
   .addr = slave_address,
   .flags = 0,
   .buf = (void *)offset_data,
   .len = ARRAY_SIZE(offset_data),
  },
  [1] = {
   .addr = slave_address,
   .flags = I2C_M_RD,
   .buf = (void *)buf,
   .len = count,
  },
 };
[寫] struct i2c_msg msgs[] = {
  [0] = {
   .addr = slave_address,
   .flags = 0,
   .buf = (void *)data,
   .len = (2 + len) * sizeof(*data),
  },
  }
2、通過ioctl操作設備


view plaincopy to clipboardprint?
static int do_rdwr(int fd, struct i2c_msg *msgs, int nmsgs)  
{  
 struct i2c_rdwr_ioctl_data msgset = {  
  .msgs = msgs,  
  .nmsgs = nmsgs,  /* msgs 個數*/ 
 };  
 
 if (msgs == NULL || nmsgs <= 0)  
  return -1;  
 
 if (ioctl(fd, I2C_RDWR, &msgset) < 0)  
  return -1;  
 
 return 0;  

static int do_rdwr(int fd, struct i2c_msg *msgs, int nmsgs)
{
 struct i2c_rdwr_ioctl_data msgset = {
  .msgs = msgs,
  .nmsgs = nmsgs,  /* msgs 個數*/
 };

 if (msgs == NULL || nmsgs <= 0)
  return -1;

 if (ioctl(fd, I2C_RDWR, &msgset) < 0)
  return -1;

 return 0;
}
 

3、ioctl命令字:


#define I2C_SMBUS_READ 1 
#define I2C_SMBUS_WRITE 0 
 
#define I2C_SMBUS_QUICK 0 
#define I2C_SMBUS_BYTE 1 
#define I2C_SMBUS_BYTE_DATA 2  
#define I2C_SMBUS_WORD_DATA 3 
#define I2C_SMBUS_PROC_CALL 4 
#define I2C_SMBUS_BLOCK_DATA 5 
#define I2C_SMBUS_I2C_BLOCK_DATA 6 
#define I2C_SMBUS_BLOCK_PROC_CALL 7   
 
#define I2C_RETRIES 0x0701   
#define I2C_TIMEOUT 0x0702   
#define I2C_SLAVE 0x0703   
#define I2C_SLAVE_FORCE 0x0706   
#define I2C_TENBIT 0x0704   
#define I2C_FUNCS 0x0705   
#define I2C_RDWR 0x0707   
#define I2C_PEC 0x0708   
#define I2C_SMBUS 0x0720   
#define I2C_SMBUS_READ 1
#define I2C_SMBUS_WRITE 0

#define I2C_SMBUS_QUICK 0
#define I2C_SMBUS_BYTE 1
#define I2C_SMBUS_BYTE_DATA 2
#define I2C_SMBUS_WORD_DATA 3
#define I2C_SMBUS_PROC_CALL 4
#define I2C_SMBUS_BLOCK_DATA 5
#define I2C_SMBUS_I2C_BLOCK_DATA 6
#define I2C_SMBUS_BLOCK_PROC_CALL 7 

#define I2C_RETRIES 0x0701 
#define I2C_TIMEOUT 0x0702 
#define I2C_SLAVE 0x0703 
#define I2C_SLAVE_FORCE 0x0706 
#define I2C_TENBIT 0x0704 
#define I2C_FUNCS 0x0705 
#define I2C_RDWR 0x0707 
#define I2C_PEC 0x0708 
#define I2C_SMBUS 0x0720 

9. 數據傳輸框架
I2C架構的讀寫支持兩種類型,默認實現的操作是smbus協議,該協議與i2c協議類似,如果控制器不支持smbus,框架層可以用i2c_transfer模擬smbus的實現,系統默認的i2c傳輸函數一般都是基於i2c模擬的smbus方法傳輸的,如i2c_smbus_write_byte_data,i2c_smbus_read_byte_data等。
I2C協議的總線實現應該是I2C控制器,而不是SMBUS控制器, I2C協議和SMBUS協議不完成等同,SMBUS是I2C的子集,smbus由I2C衍生而來。smbus總線上傳輸的數據一定是I2C的格式的,但是SMBUS上傳輸的數據不一定能滿足具體某個I2C從設備的通信要求(數據序列)。
下圖以i2c_smbus_write_byte_data介紹數據流程:

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/zhenwenxian/archive/2010/09/14/5882265.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章