歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

第15章:Linux I2C核心、總線與設備驅動

Linux I2C體系結構

I2C核心
I2C核心提供了I2C總線驅動和設備驅動的注冊、注銷的方法,I2C通信(Algorithm)方法上層的與具體適配器無關代碼以及探測設備、檢測設備地址的上層代碼等 I2C總線驅動
是對I2C體系結構中適配器端的實現,適配器可由CPU控制,甚至可以直接集成在CPU內部 總線驅動包含I2C適配器數據結構i2c_adapter、I2C適配器的Algorithm數據結構i2c_algorithm和控制I2C適配器產生通信信號的函數 I2C設備驅動
它是對I2C硬件體系結構中設備端的實現,設備一般掛接在受CPU控制的I2C適配器上,通過I2C適配器與CPU通信 I2C驅動主要包含數據結構i2c_driver和i2c_client,需要根據的設備實現其中的成員函數 所有的I2C設備都在sysfs文件系統中顯示,存在/sys/bus/i2c目錄下,以適配器地址和芯片地址的形式列出 /drivers/i2c/下的文件介紹
i2c-core.c:實現了I2C核心的功能以及/proc/bus/i2c*接口 i2c-dev.c:實現I2C適配器設備文件的功能,每一個適配器被分配一個設備
設配器的主設備號位89,次設備號位:0-255 i2c_dev.c並不是根據具體的設備而設計的,只是提供了同用的read()、write()和ioctl()等接口,應用層可以通過這些接口訪問掛接在適配器上的I2C設備的存儲空間或寄存器,並控制I2C設備的工作方式 busses文件夾:包含一些I2C主機控制器驅動,如i2c_omap.c、i2c_s3c2440c.等 algos文件夾:實現了一些I2C總線適配器的通信方法 i2c_adapter、i2c_algorithm、i2c_driver和i2c_client數據結構的作用及其之間的關系
i2c_adapter與i2c_algorithm
i2c_adapter對應於物理上的一個適配器,而i2c_algorithm對應於一套通信方法 一個i2c_adapter需要i2c_algorithm提供的通信函數來控制適配器產生特定的訪問周期 i2c_algorithm中的關鍵函數master_xfer()用於產生I2C訪問周期需要的信號,以i2c_msg(即I2C消息)為單位 i2c_driver與i2c_client
i2c_driver對應於一套驅動方法,struct i2c_device_id形式的id_table是該驅動所支持的I2C設備的ID表 i2c_client對應於真實的物理設備,每個I2C設備都需一個i2c_client來描述 一個i2c_driver支持多個同類型的i2c_client i2c_client的信息通常在BSP的板文件中通過i2c_board_info填充,包括設備的ID號、地址、中斷號等信息 在I2C總線驅動i2c_bus_type的match()函數i2c_device_match()中,會調用i2c_match_id()函數匹配在板文件中定義的ID和i2c_driver所支持的ID表 i2c_adapter與i2c_client
其關系與I2C設備體系中適配器與設備的關系一致,即i2c_client依附於i2c_driver 一個i2c_driver可以被多個i2c_client依附,i2c_driver中包含有依附它的i2c_client的鏈表

15.2 I2C核心

增加、刪除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);
增加、刪除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
i2c_client依附/脫離
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);

當一個具體的client被偵測到並被關聯的時候,設備和sysfs文件將被注冊。相反地,在client被取消關聯的時候,sysfs文件和設備也被注銷
代碼清單15.6 I2C核心client attach/detach函數

1  int i2c_attach_client(struct i2c_client *client)
2  {
3    ...
4   device_register(&client->dev);
5   device_create_file(&client->dev, &dev_attr_client_name);
6   
7   return 0;
8  }
9  
10 int i2c_detach_client(struct i2c_client *client)
11 {
12   ...
13  device_remove_file(&client->dev, &dev_attr_client_name);
14  device_unregister(&client->dev);
15   ...
16 }
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()函數分別完成一條寫消息和一條讀消
例如:

代碼清單15.7 I2C核心i2c_master_send函數
1  int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
2  {
3   int ret;
4   struct i2c_adapter *adap=client->adapter;
5   struct i2c_msg msg;
6    /*構造一個寫消息*/
7   msg.addr = client->addr;
8   msg.flags = client->flags & I2C_M_TEN;
9   msg.len = count;
10  msg.buf = (char *)buf;
11  /*傳輸消息*/
12  ret = i2c_transfer(adap, &msg, 1);
13 
14  return (ret == 1) ? count : ret;
15 }
代碼清單15.8 I2C核心i 2c_master_recv函數
1  int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
2  {
3   struct i2c_adapter *adap=client->adapter;
4   struct i2c_msg msg;
5   int ret;
6   /*構造一個讀消息*/
7   msg.addr = client->addr;
8   msg.flags = client->flags & I2C_M_TEN;
9   msg.flags |= I2C_M_RD;
10  msg.len = count;
11  msg.buf = buf;
12  /*傳輸消息*/
13  ret = i2c_transfer(adap, &msg, 1);
14 
15  /* 成功(1條消息被處理), 返回讀的字節數 */
16  return (ret == 1) ? count : ret;
17 }

i2c_transfer()函數本身不具備驅動適配器物理硬件完成消息交互的能力,它只是尋找到i2c_adapter對應的i2c_algorithm,並使用i2c_algorithm的master_xfer()函數真正驅動硬件流程,如下例程:

I2C核心i 2c_transfer函數
1  int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
2  {
3   int ret;
4  
5   if (adap->algo->master_xfer) {
6    down(&adap->bus_lock);
7    ret = adap->algo->master_xfer(adap,msgs,num); /* 消息傳輸 */
8    up(&adap->bus_lock);
9    return ret;
10  } else {
11   dev_dbg(&adap->dev, "I2C level transfers not supported\n");
12   return -ENOSYS;
13  }
14 }

15.3 Linux I2C總線驅動

15.3.1 I2C適配器驅動加載與卸載

I2C總線驅動模塊的加載函數要完成兩個工作:
初始化I2C適配器所使用的硬件資源,申請I/O地址、中斷號等。 通過i2c_add_adapter()添加i2c_adapter的數據結構,當然這個i2c_adapter數據結構的成員已經被xxx適配器的相應函數指針所初始化。 I2C總線驅動模塊的卸載函數要完成的工作與加載函數的相反:
釋放I2C適配器所使用的硬件資源,釋放I/O地址、中斷號等。 通過i2c_del_adapter()刪除i2c_adapter的數據結構。 代碼清單給出了I2C適配器驅動模塊加載和卸載函數的模板。
I2C總線驅動模塊加載和卸載函數模板
1  static int __init i2c_adapter_xxx_init(void)
2  {
3    xxx_adpater_hw_init();
4    i2c_add_adapter(&xxx_adapter);
5  }
6  
7  static void __exit i2c_adapter_xxx_exit(void)
8  {
9    xxx_adpater_hw_free();
10   i2c_del_adapter(&xxx_adapter);
11 }
 - 上述代碼中xxx_adpater_hw_init()和xxx_adpater_hw_free()函數的實現都與具體的CPU和I2C設備硬件直接相關。

15.3.2 I2C總線通信方法

我們需要為特定的I2C適配器實現其通信方法,主要實現i2c_algorithm的master_xfer()函數和functionality()函數。
functionality()函數用於返回algorithm所支持的通信協議,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。 master_xfer()函數在I2C適配器上完成傳遞給它的i2c_msg數組中的每個I2C消息 代碼給出了xxx設備的master_xfer()函數模板。
I2C總線驅動master_xfer函數模板
1  static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
2    int num)
3  {
4    ...
5    for (i = 0; i < num; i++)
6    {
7      i2c_adapter_xxx_start(); /*產生開始位*/
8      /*是讀消息*/
9      if (msgs[i]->flags &I2C_M_RD)
10     {
11       i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*發送從設備讀地址*/
12       i2c_adapter_xxx_wait_ack(); /*獲得從設備的ack*/
13       i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /*讀取msgs[i]
14         ->len長的數據到msgs[i]->buf*/
15     }
16     else
17      /*是寫消息*/
18     {
19       i2c_adapter_xxx_setaddr(msg->addr << 1); /*發送從設備寫地址*/
20       i2c_adapter_xxx_wait_ack(); /*獲得從設備的ack*/
21       i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /*讀取msgs[i]
22         ->len長的數據到msgs[i]->buf*/
23     }
24   }
25   i2c_adapter_xxx_stop(); /*產生停止位*/
26 }
 - 上述代碼實際上給出了一個master_xfer()函數處理I2C消息數組的流程,對於數組中的每個消息,判斷消息類型,若為讀消息,則賦從設備地址為    (msg->addr << 1) | 1,否則為msg->addr << 1。對每個消息產生1個開始位,緊接著傳送從設備地址,然後開始數據的發送或接收,對最後的消息還需產生1個停止位。圖15.3描述了整個master_xfer()完成的時序。
多數I2C總線驅動會定義一個xxx_i2c結構體,作為i2c_adapter的algo_data(類似“私有數據”)
xxx_i2c結構體包含I2C消息數組指針、數組索引及I2C適配器algorithm訪問控制用的自旋鎖、等待隊列等 master_xfer()函數完成消息數組中消息的處理也可通過對xxx_i2c結構體相關成員的訪問來控制。
1  struct xxx_i2c 
2  {
3   spinlock_t  lock;
4   wait_queue_head_t wait;  
5   struct i2c_msg  *msg;
6   unsigned int  msg_num;
7   unsigned int  msg_idx;
8   unsigned int  msg_ptr;
9   ...
10  struct i2c_adapter adap;
11 };

15.4 linux I2C設備驅動

15.4.1 Linux I2C設備驅動模塊加載與卸載

I2C設備驅動模塊加載函數通用的方法是在I2C設備驅動模塊加載函數中完成兩件事:
通過register_chrdev()函數將I2C設備注冊為一個字符設備。 通過I2C核心的i2c_add_driver()函數添加i2c_driver。 在模塊卸載函數中需要做相反的兩件事:
通過I2C核心的i2c_del_driver()函數刪除i2c_driver。 通過unregister_chrdev()函數注銷字符設備。
1  static int __init yyy_init(void)
2  {
3    int res;
4    /*注冊字符設備*/
5    res = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老內核接口
6    if (res)
7      goto out;
8    /*添加i2c_driver*/
9    res = i2c_add_driver(&yyy_driver);
10   if (res)
11     goto out_unreg_class;
12   return 0;
13 
14   out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c");
15   out: printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
16   return res;
17 }
18 
19 static void __exit yyy_exit(void)
20 {
21   i2c_del_driver(&i2cdev_driver);
22   unregister_chrdev(YYY_MAJOR, "yyy");
23 }

15.4.2 Linux I2C設備驅動的數據傳輸

I2C設備上的讀寫數據的時序且數據通常通過i2c_msg消息數組進行組織,最後通過i2c_transfer函數完成
1  static int yyy_cmd1(struct i2c_client *client, struct rtc_time *dt)
2  {
3   struct i2c_msg msg[2];
4   /*第一條消息是寫消息*/
5   msg[0].addr = client->addr;
6   msg[0].flags = 0;
7   msg[0].len = 1;
8   msg[0].buf = &offs;
9   /*第二條消息是讀消息*/
10  msg[1].addr = client->addr;
11  msg[1].flags = I2C_M_RD;
12  msg[1].len = sizeof(buf);
13  msg[1].buf = &buf[0];
14  
15  i2c_transfer(client->adapter, msg, 2);
16   ...
17 }

15.4.3 Linux i2c-dev.c文件分析

i2c-dev.c文件可以被看作一個I2C設 備驅動,它實現的一個i2c_client是虛擬的、臨時的,隨著設備文件的打開而產生,並隨設備文件的關閉而撤銷,並沒有被添加到i2c_adapter的clients鏈表中。

i2c-dev.c針對每個I2C適配器生成一個主設備為89的設備文件,實現了i2c_driver的成員函數以及文件操作接口

i2c-dev.c的主體是“i2c_driver成員函數 + 字符設備驅動”。

i2c-dev.c中提供i2cdev_read()、i2cdev_write()函數來對應用戶空間要使用的read()和 write()文件操作接口,這兩個函數分別調用I2C核心的i2c_master_recv()和i2c_master_send()函數來構造1條 I2C消息並引發適配器algorithm通信函數的調用,完成消息的傳輸

i2c-dev.c中i2cdev_read()和i2cdev_write()函數不具備太強的通用性,沒有太大的實用價值,只能適用於非RepStart模式的情況。

對於2條以上消息組成的讀寫,在用戶空間需要組織i2c_msg消息數組並調用I2C_RDWR IOCTL命令。

代碼給出了i2cdev_ioctl()函數的框架,其中詳細列出了I2C_RDWR命令的處理過程。

 i2c-dev.c中的i2cdev_ioctl函數
1  static int i2cdev_ioctl(struct inode *inode, struct file *file,
2    unsigned int cmd, unsigned long arg)
3  {
4   struct i2c_client *client = (struct i2c_client *)file->private_data;
5    ...  
6   switch ( cmd ) {
7   case I2C_SLAVE:
8   case I2C_SLAVE_FORCE:
9    ...   /*設置從設備地址*/
10  case I2C_TENBIT:
11   ...
12  case I2C_PEC:
13   ...
14  case I2C_FUNCS:
15   ...  
16  case I2C_RDWR:
17   if (copy_from_user(&rdwr_arg, 
18        (struct i2c_rdwr_ioctl_data __user *)arg, 
19        sizeof(rdwr_arg)))
20    return -EFAULT;
21   /* 一次傳入的消息太多 */
22   if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
23    return -EINVAL;
24   /*獲得用戶空間傳入的消息數組
25   rdwr_pa = (struct i2c_msg *)
26    kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), 
27    GFP_KERNEL);    
28   if (rdwr_pa == NULL) return -ENOMEM;
29   if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
30        rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
31    kfree(rdwr_pa);
32    return -EFAULT;
33   }
34   data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
35   if (data_ptrs == NULL) {
36    kfree(rdwr_pa);
37    return -ENOMEM;
38   }
39   res = 0;
40   for( i=0; i41    /* 限制消息的長度 */
42    if (rdwr_pa[i].len > 8192) {
43     res = -EINVAL;
44     break;
45    }
46    data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
47    rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
48    if(rdwr_pa[i].buf == NULL) {
49     res = -ENOMEM;
50     break;
51    }
52    if(copy_from_user(rdwr_pa[i].buf,
53     data_ptrs[i],
54     rdwr_pa[i].len)) {
55      ++i; /* Needs to be kfreed too */
56      res = -EFAULT;
57     break;
58    }
59   }
60   if (res < 0) {
61    int j;
62    for (j = 0; j < i; ++j)
63     kfree(rdwr_pa[j].buf);
64    kfree(data_ptrs);
65    kfree(rdwr_pa);
66    return res;
67   }
68    /*把這些消息交給通信方法去處理*/
69   res = i2c_transfer(client->adapter,
70    rdwr_pa,
71    rdwr_arg.nmsgs);
72   while(i-- > 0) {   /*如果是讀消息,把值拷貝到用戶空間*/
73    if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {
74     if(copy_to_user(
75      data_ptrs[i],
76      rdwr_pa[i].buf,
77      rdwr_pa[i].len)) {
78      res = -EFAULT;
79     }
80    }
81    kfree(rdwr_pa[i].buf);
82   }
83   kfree(data_ptrs);
84   kfree(rdwr_pa);
85   return res;
86  case I2C_SMBUS:
87   ... 
88  default:
89   return i2c_control(client,cmd,arg);
90  }
91  return 0;
92 }
 - 常用的IOCTL包括I2C_SLAVE(設置從設備地址)、I2C_RETRIES(沒有收到設備ACK情況下的重試次數,缺省為1)、I2C_TIMEOU(超時)以及I2C_RDWR。
下面兩個代碼分別演示了直接通過read()、write()接口和O_RDWR IOCTL讀寫I2C設備的例子。

代碼清單15.23 直接通過read()/write()讀寫I2C設備
1  #include 
2  #include 
3  #include 
4  #include 
5  #include 
6  #include 
7  #include 
8  
9  #define I2C_RETRIES     0x0701
10 #define I2C_TIMEOUT     0x0702
11 #define I2C_SLAVE       0x0703
12 
13 int main(int argc, char **argv)
14 {
15   unsigned int fd;
16   unsigned short mem_addr;
17   unsigned short size;
18   unsigned short idx;
19   #define BUFF_SIZE    32
20   char buf[BUFF_SIZE];
21   char cswap;
22   union
23   {
24     unsigned short addr;
25     char bytes[2];
26   } tmp;
27 
28   if (argc < 3)
29   {
30     printf("Use:\n%s /dev/i2c-x mem_addr size\n", argv[0]);
31     return 0;
32   }
33   sscanf(argv[2], "%d", &mem_addr);
34   sscanf(argv[3], "%d", &size);
35 
36   if (size > BUFF_SIZE)
37     size = BUFF_SIZE;
38 
39   fd = open(argv[1], O_RDWR);
40 
41   if (!fd)
42   {
43     printf("Error on opening the device file\n");
44     return 0;
45   }
46 
47   ioctl(fd, I2C_SLAVE, 0x50); /* 設置eeprom地址 */
48   ioctl(fd, I2C_TIMEOUT, 1); /* 設置超時 */
49   ioctl(fd, I2C_RETRIES, 1); /* 設置重試次數 */
50 
51   for (idx = 0; idx < size; ++idx, ++mem_addr)
52   {
53     tmp.addr = mem_addr;
54     cswap = tmp.bytes[0];
55     tmp.bytes[0] = tmp.bytes[1];
56     tmp.bytes[1] = cswap;
57     write(fd, &tmp.addr, 2);
58     read(fd, &buf[idx], 1);
59   }
60   buf[size] = 0;
61   close(fd);
62   printf("Read %d char: %s\n", size, buf);
63   return 0;
64 }
代碼清單15.24 通過O_RDWR IOCTL讀寫I2C設備
1  #include 
2  #include 
3  #include 
4  #include 
5  #include 
6  #include 
7  #include 
8  #include 
9  #include 
10 #include 
11 
12 #define MAX_I2C_MSG         2
13 
14 #define I2C_RETRIES     0x0701
15 #define I2C_TIMEOUT     0x0702
16 #define I2C_RDWR        0x0707
17 
18 struct i2c_msg
19 {
20   __u16 addr; /* 從地址 */
21   __u16 flags;
22   #define I2C_M_RD        0x01
23   __u8 *buf; /* 消息數據指針 */
24 };
25 struct i2c_rdwr_ioctl_data
26 {
27   struct i2c_msg *msgs; /* i2c_msg[]指針 */
28   int nmsgs; /* i2c_msg數量 */
29 };
30 
31 int main(int argc, char **argv)
32 {
33   struct i2c_rdwr_ioctl_data work_queue;
34   unsigned int idx;
35   unsigned int fd;
36   unsigned short start_address;
37   int ret;
38 
39   if (argc < 4)
40   {
41     printf("Usage:\n%s /dev/i2c-x start_addr\n", argv[0]);
42     return 0;
43   }
44 
45   fd = open(argv[1], O_RDWR);
46 
47   if (!fd)
48   {
49     printf("Error on opening the device file\n");
50     return 0;
51   }
52   sscanf(argv[2], "%x", &start_address);
53   work_queue.nmsgs = MAX_I2C_MSG; /* 消息數量 */
54 
55   work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct
56     i2c_msg));
57   if (!work_queue.msgs)
58   {
59     printf("Memory alloc error\n");
60     close(fd);
61     return 0;
62   }
63 
64   for (idx = 0; idx < work_queue.nmsgs; ++idx)
65   {
66     (work_queue.msgs[idx]).len = 0;
67     (work_queue.msgs[idx]).addr = start_address + idx;
68     (work_queue.msgs[idx]).buf = NULL;
69   }
70 
71   ioctl(fd, I2C_TIMEOUT, 2); /* 設置超時 */
72   ioctl(fd, I2C_RETRIES, 1); /* 設置重試次數 */
73 
74   ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue);
75 
76   if (ret < 0)
77   {
78     printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);
79   }
80 
81   close(fd);
82   return ;
83 }
Copyright © Linux教程網 All Rights Reserved