Linux驅動之I2C總線驅動開發

一、IIC 基礎概念

1.I2C 基礎概念

IIC(Inter-Integrated Circuit)總線是一種由PHILIPS公司開發的兩線式串行總線 ###1.IC總線的特點
IIC總線最主要的優點是其簡單性和有效性。由於接口直接在組件之上,因此IIC總線佔用的空間非常小,減少了電路板的空間和芯片管腳的數量,降低了互聯成本。總線的長度可高達25英尺,並且能夠以10Kbps的最大傳輸速率支持40個組件。IIC總線的另一個優點是,它支持多主控(multimastering), 其中任何能夠進行發送和接收的設備都可以成爲主總線。一個主控能夠控制信號的傳輸和時鐘頻率。當然,在任何時間點上只能有一個主控。

2.I2C總線工作原理

a – 總線構成
IIC總線是由數據線SDA和時鐘SCL構成的串行總線,可發送和接收數據。在CPU與被控IC之間、IC與IC之間進行雙向傳送,最高傳送速率100kbps。各種被控制電路均並聯在這條總線上,但就像電話機一樣只有撥通各自的號碼才能工作,所以每個電路和模塊都有唯一的地址,在信息的傳輸過程中,IIC總線上並接的每一模塊電路既是主控器(或被控器),又是發送器(或接收器),這取決於它所要完成的功能。
CPU發出的控制信號分爲地址碼和控制量兩部分:
1) 地址碼用來選址,即接通需要控制的電路,確定控制的種類;
2) 控制量決定該調整的類別(如對比度、亮度等)及需要調整的量。
這樣,各控制電路雖然掛在同一條總線上,卻彼此獨立,互不相關。
b – 信號類型
IIC總線在傳送數據過程中共有四種類型信號:
開始信號:SCL爲高電平時,SDA由高電平向低電平跳變,開始傳送數據;
結束信號:SCL爲高電平時,SDA由低電平向高電平跳變,結束傳送數據;
數據傳輸信號:在開始條件以後,時鐘信號SCL的高電平週期期問,當數據線穩定時,數據線SDA的狀態表示數據有效,即數據可以被讀走,開始進行讀操作。在時鐘信號SCL的低電平週期期間,數據線上數據才允許改變。每位數據需要一個時鐘脈衝。
應答信號:接收數據的IC在接收到8bit數據後,向發送數據的IC發出特定的低電平脈衝,表示已收到數據。CPU向受控單元發出一個信號後,等待受控單元發出一個應答信號,CPU接收到應答信號後,根據實際情況作出是否繼續傳遞信號的判斷。若未收到應答信號,由判斷爲受控單元出現故障。

目前有很多半導體集成電路上都集成了IIC接口。帶有IIC接口的單片機有:CYGNAL的 C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外圍器件如存儲器、監控芯片等也提供IIC接口。

3、總線基本操作

IIC規程運用主/從雙向通訊。器件發送數據到總線上,則定義爲發送器,器件接收數據則定義爲接收器。主器件和從器件都可以工作於接收和發送狀態。 總線必須由主器件(通常爲微控制器)控制,主器件產生串行時鐘(SCL)控制總線的傳輸方向,併產生起始和停止條件。SDA線上的數據狀態僅在SCL爲低電平的期間才能改變,SCL爲高電平的期間,SDA狀態的改變被用來表示起始和停止條件。
a – 控制字節
在起始條件之後,必須是器件的控制字節,其中高四位爲器件類型識別符(不同的芯片類型有不同的定義,EEPROM一般應爲1010),接着三位爲片選,最後一位爲讀寫位,當爲1時爲讀操作,爲0時爲寫操作。
b – 寫操作
寫操作分爲字節寫和頁面寫兩種操作,對於頁面寫根據芯片的一次裝載的字節不同有所不同。關於頁面寫的地址、應答和數據傳送的時序。
c – 讀操作
讀操作有三種基本操作:當前地址讀、隨機讀和順序讀。圖4給出的是順序讀的時序圖。應當注意的是:最後一個讀操作的第9個時鐘週期不是“不關心”。爲了結束讀操作,主機必須在第9個週期間發出停止條件或者在第9個時鐘週期內保持SDA爲高電平、然後發出停止條件。
d – 總線仲裁
主機只能在總線空閒的時候啓動傳輸。兩個或多個主機可能在起始條件的最小持續內產生一個起始條件,結果在總線上產生一個規定的起始條件。
當SCL線是高電平時,仲裁在SDA線發生:這樣,在其他主機發送低電平時,發送高電平的主機將斷開它的數據輸出級,因爲總線上的電平和它自己的電平不同。
仲裁可以持續多位。從地址位開始,同一個器件的話接着就是數據位(如果主機-發送器),或者比較相應位(如果主機-接收器)。IIC總線的地址和數據信息由贏得仲裁的主機決定,在這個過程中不會丟失信息。
仲裁不能在下面情況之間進行:
1)重複起始條件和數據位;
2)停止條件和數據位;
3)重複起始條件和停止條件。
4、特性總結
IIC肯定是2線的(不算地線)IIC協議確實很科學,比3/4線的SPI要好,當然線多通訊速率相對就快了
IIC的原則是
a – 在SCL=1(高電平)時,SDA千萬別忽悠!!!否則,SDA下跳則”判罰”爲”起始信號S”,SDA上跳則”判罰”爲”停止信號P”.
b – 在SCL=0(低電平)時,SDA隨便忽悠!!!(可別忽悠過火到SCL跳高)
c – 每個字節後應該由對方回送一個應答信號ACK做爲對方在線的標誌.非應答信號一般在所有字節的最後一個字節後.一般要由雙方協議簽定.
d – SCL必須由主機發送,否則天下大亂
e – 首字節是”片選信號”,即7位從機地址加1位方向(讀寫)控制.從機收到(聽到)自己的地址才能發送應答信號(必須應答!!!)表示自己在線.其他地址的從機不允許忽悠!!!(當然羣呼可以忽悠但只能聽不許說話)
f – 讀寫是站在主機的立場上定義的.”讀”是主機接收從機數據,”寫”是主機發送數據給從機.
g– 重複位主要用於主機從發送模式到接收模式的轉換”信號”,由於只有2線,所以收發轉換肯定要比SPI複雜,因爲SPI可用不同的邊沿來收發數據,而IIC不行.
h – 在硬件IIC模塊,特別是MCU/ARM/DSP等每個階段都會得到一個準確的狀態碼,根據這個狀態碼可以很容易知道現在在什麼狀態和什麼出錯信息.
i – 7位IIC總線可以掛接127個不同地址的IIC設備,0號”設備”作爲羣呼地址.10位IIC總線可以掛接更多的10位IIC設備.

二、 Linux下IIC驅動架構

Linux定義了系統的IIC驅動體系結構,在Linux系統中,IIC驅動由3部分組成,即IIC核心、IIC總線驅動和IIC設備驅動。這3部分相互協作,形成了非常通用、可適應性很強的IIC框架。

1、架構層次分類

  第一層:提供i2c adapter的硬件驅動,探測、初始化i2c adapter(如申請i2c的io地址和中斷號),驅動soc控制的i2c adapter在硬件上產生信號(start、stop、ack)以及處理i2c中斷。覆蓋圖中的硬件實現層
  第二層:提供i2c adapter的algorithm,用具體適配器的xxx_xferf()函數來填充i2c_algorithm的master_xfer函數指針,並把賦值後的i2c_algorithm再賦值給i2c_adapter的algo指針。覆蓋圖中的訪問抽象層、i2c核心層
  第三層:實現i2c設備驅動中的i2c_driver接口,用具體的i2c device設備的attach_adapter()、detach_adapter()方法賦值給i2c_driver的成員函數指針。實現設備device與總線(或者叫adapter)的掛接。覆蓋圖中的driver驅動層
  第四層:實現i2c設備所對應的具體device的驅動,i2c_driver只是實現設備與總線的掛接,而掛接在總線上的設備則是千差萬別的,所以要實現具體設備device的write()、read()、ioctl()等方法,賦值給file_operations,然後註冊字符設備(多數是字符設備)。覆蓋圖中的driver驅動層

第一層和第二層又叫i2c總線驅動(bus),第三第四屬於i2c設備驅動(device driver)。
在linux驅動架構中,幾乎不需要驅動開發人員再添加bus,因爲linux內核幾乎集成所有總線bus,如usb、pci、i2c等等。並且總線bus中的(與特定硬件相關的代碼)已由芯片提供商編寫完成,例如三星的s3c-2440平臺i2c總線bus爲/drivers/i2c/buses/i2c-s3c2410.c
第三第四層與特定device相干的就需要驅動工程師來實現了.

2、Linux下I2C驅動體系結構三部分詳細分析

a – IIC核心
IIC 核心提供了IIC總線驅動和設備驅動的註冊、註銷方法,IIC通信方法(即“algorithm”,筆者認爲直譯爲“運算方法”並不合適,爲免引起誤解, 下文將直接使用“algorithm”)上層的、與具體適配器無關的代碼以及探測設備、檢測設備地址的上層代碼等。
在我們的Linux驅動的i2c文件夾下有algos,busses,chips三個文件夾,另外還有i2c-core.c和i2c-dev.c兩個文件。
i2c-core.c文件實現了I2Ccore框架,是Linux內核用來維護和管理的I2C的核心部分,其中維護了兩個靜態的List,分別記錄系統中的I2Cdriver結構和I2Cadapter結構。I2Ccore提供接口函數,允許一個I2Cadatper,I2Cdriver和I2Cclient初始化時在I2Ccore中進行註冊,以及退出時進行註銷。同時還提供了I2C總線讀寫訪問的一般接口,主要應用在I2C設備驅動中。
b – IIC總線驅動
IIC總線驅動是對IIC硬件體系結構中適配器端的實現,適配器可由CPU控制,甚至直接集成在CPU內部。總線驅動的職責,是爲系統中每個I2C總線增加相應的讀寫方法。但是總線驅動本身並不會進行任何的通訊,它只是存在那裏,等待設備驅動調用其函數。
IIC總線驅動主要包含了IIC適配器數據結構i2c_adapter、IIC適配器的algorithm數據結構i2c_algorithm和控制IIC適配器產生通信信號的函數。經由IIC總線驅動的代碼,我們可以控制IIC適配器以主控方式產生開始位、停止位、讀寫週期,以及以從設備方式被讀寫、產生ACK等。
 Busses文件夾下的i2c-mpc.c文件實現了PowerPC下I2C總線適配器驅動,定義描述了具體的I2C總線適配器的i2c_adapter數據結構,實現比較底層的對I2C總線訪問的具體方法。I2Cadapter 構造一個對I2Ccore層接口的數據結構,並通過接口函數向I2Ccore註冊一個控制器。I2Cadapter主要實現對I2C總線訪問的算法,iic_xfer() 函數就是I2Cadapter底層對I2C總線讀寫方法的實現。同時I2Cadpter 中還實現了對I2C控制器中斷的處理函數。
c – IIC設備驅動
IIC設備驅動是對IIC硬件體系結構中設備端的實現,設備一般掛接在受CPU控制的IIC適配器上,通過IIC適配器與CPU交換數據。設備驅動則是與掛在I2C總線上的具體的設備通訊的驅動。通過I2C總線驅動提供的函數,設備驅動可以忽略不同總線控制器的差異,不考慮其實現細節地與硬件設備通訊。
IIC設備驅動主要包含了數據結構i2c_driver和i2c_client,我們需要根據具體設備實現其中的成員函數。
i2c-dev.c文件中實現了I2Cdriver,提供了一個通用的I2C設備的驅動程序,實現了字符類型設備的訪問接口,實現了對用戶應用層的接口,提供用戶程序訪問I2C設備的接口,包括實現open,release,read,write以及最重要的ioctl等標準文件操作的接口函數。我們可以通過open函數打開 I2C的設備文件,通過ioctl函數設定要訪問從設備的地址,然後就可以通過 read和write函數完成對I2C設備的讀寫操作。
通過I2Cdriver提供的通用方法可以訪問任何一個I2C的設備,但是其中實現的read,write及ioctl等功能完全是基於一般設備的實現,所有的操作數據都是基於字節流,沒有明確的格式和意義。爲了更方便和有效地使用I2C設備,我們可以爲一個具體的I2C設備開發特定的I2C設備驅動程序,在驅動中完成對特定的數據格式的解釋以及實現一些專用的功能。

3、重要的結構體

因爲IIC設備種類太多,如果每一個IIC設備寫一個驅動程序,那麼顯得內核非常大。不符合軟件工程代碼複用,所以對其層次話:這裏簡單的將IIC設備驅動分爲設備層、總線層。理解這兩個層次的重點是理解4個數據結構,這4個數據結構是i2c_driver、i2c_client、i2c_algorithm、i2c_adapter。i2c_driver、i2c_client屬於設備層;i2c_algorithm、i2c_adapter屬於總線型。如下圖:
這裏寫圖片描述
設備層關係到實際的IIC設備,總線層包括CPU中的IIC總線控制器和控制總線通信的方法。值得注意的是:一個系統中可能有很多個總線層,也就是包含多個總線控制器;也可能有多個設備層,包含不同的IIC設備
a–i2c_client
一個設備由i2c_client數據結構進行描述:

struct  i2c_client  
{  
    unsigned short  flags;                          //標誌位  
    unsigned short  addr;                //設備的地址,低7位爲芯片地址  
    char name[I2C_NAME_SIZE];             //設備的名稱,最大爲20個字節  
    struct  i2c_adapter *adapter;           //依附的適配器i2c_adapter,適配器指明所屬的總線  
    struct  i2c_driver *driver;             //指向設備對應的驅動程序  
    struct device  dev;                 //設備結構體  
    int irq;                       //設備申請的中斷號  
    struct list_head  list;                //連接到總線上的所有設備  
    struct list_head   detected;           //已經被發現的設備鏈表  
    struct completion  released;           //是否已經釋放的完成量  
};  

這裏寫圖片描述
第7位是R/W位,0表示寫,2表示讀,所以I2C設備通常有兩個地址,即讀地址和寫地址;
類型器件由中間4位組成,這是由半導體公司生產的時候就已經固化了;
自定義類型由低3位組成。由用戶自己設置;
IIC設備還有一些重要的注意事項:
1、i2c_client數據結構是描述IIC設備的“模板”,驅動程序的設備結構中應包含該結構;
2、adapter指向設備連接的總線適配器,系統可能有多個總線適配器。內核中靜態指針數組adapters記錄所有已經註冊的總線適配器設備;
3、driver是指向設備驅動程序,這個驅動程序是在系統檢測到設備存在時賦值的;
b–i2c設備驅動 i2c_driver

struct  i2c_driver  
{  
    int id;                         //驅動標識ID  
    unsigned int class;               //驅動的類型  
    int (*attach_adapter)(struct i2c_adapter *);             //當檢測到適配器時調用的函數  
    int (*detach_adapter)(struct i2c_adapter*);              //卸載適配器時調用的函數  
    int (*detach_client)(struct i2c_client *)   __deprecated;             //卸載設備時調用的函數  

     //以下是一種新類型驅動需要的函數,這些函數支持IIC設備動態插入和拔出。如果不想支持只實現上面3個。要不實現上面3個。要麼實現下面5個。不能同時定義  
    int  (*probe)(struct i2c_client *,const struct  i2c_device_id *);              //新類型設備探測函數  
    int (*remove)(struct i2c_client *);                   //新類型設備的移除函數  
    void (*shutdown)(struct i2c_client *);              //關閉IIC設備  
    int (*suspend)(struct  i2c_client *,pm_messge_t mesg);           //掛起IIC設備  
        int (*resume)(struct  i2c_client *);                               //恢復IIC設備  
    int (*command)(struct i2c_client *client,unsigned int cmd,void *arg);        //使用命令使設備完成特殊的功能。類似ioctl()函數  
    struct devcie_driver  driver;                         //設備驅動結構體  
    const struct  i2c_device_id *id_table;                       //設備ID表  
    int (*detect)(struct i2c_client *,int  kind,struct  i2c_board_info *);          //自動探測設備的回調函數  

    const  struct i2c_client_address_data          *address_data;                 //設備所在的地址範圍  
    struct  list_head    clients;                    //指向驅動支持的設備  
};  

結構體i2c_driver和i2c_client的關係較爲簡單,其中i2c_driver表示一個IIC設備驅動,i2c_client表示一個IIC設備。關係如下圖:
這裏寫圖片描述

c – i2c_adapter
IIC總線適配器就是一個IIC總線控制器,在物理上連接若干個IIC設備。IIC總線適配器本質上是一個物理設備,其主要功能是完成IIC總線控制器相關的數據通信:

 struct i2c_adapter  
{  
    struct module *owner;                        //模塊計數  
        unsigned  int id;                                  //alogorithm的類型,定義於i2c_id.h中  
        unsigned   int  class;                           //允許探測的驅動類型  
    const struct i2c_algorithm *algo;         //指向適配器的驅動程序  
        void *algo_data;                                  //指向適配器的私有數據,根據不同的情況使用方法不同  
        int (*client_register)(struct  i2c_client *);          //設備client註冊時調用  
        int (*client_unregister(struct  i2c_client *);       //設備client註銷時調用  
        u8 level;                                                           
    struct  mutex  bus_lock;                             //對總線進行操作時,將獲得總線鎖  
        struct  mutex  clist_lock ;                            //鏈表操作的互斥鎖  
        int timeout;                                                  //超時  
    int retries;                                                     //重試次數  
    struct device dev;                                          //指向 適配器的設備結構體  
    int  nr ;                                                            
    struct  list_head      clients;                            //連接總線上的設備的鏈表  
    char name[48];                                              //適配器名稱  
    struct completion     dev_released;               //用於同步的完成量  
};  

d – i2c_algorithm
每一個適配器對應一個驅動程序,該驅動程序描述了適配器與設備之間的通信方法:

struct  i2c_algorithm  
{  
    int  (*master_xfer)(struct  i2c_adapter *adap,  struct  i2c_msg *msg, int num);              //傳輸函數指針,指向實現IIC總線通信協議的函數,用來確定適配器支持那些傳輸類型  
    int  (*smbus_xfer)(struct  i2c_adapter *adap, u16  addr, unsigned  short flags, char  read_write, u8 command, int size, union  i2c_smbus_data  *data);    //smbus方式傳輸函數指針,指向實現SMBus總線通信協議的函數。SMBus和IIC之間可以通過軟件方式兼容,所以這裏提供了一個函數,但是一般都賦值爲NULL  
    u32  (*functionality)(struct  i2c_adapter *);                   //返回適配器支持的功能  

};  

IIC設備驅動程序大致可以分爲設備層和總線層。設備層包括一個重要的數據結構,i2c_client。總線層包括兩個重要的數據結構,分別是i2c_adapter和i2c_algorithm。一個i2c_algorithm結構表示適配器對應的傳輸數據方法。3個數據結構關係:
這裏寫圖片描述
IIC設備層次結構較爲簡單,但是寫IIC設備驅動程序卻相當複雜。
IIC設備驅動程序的步驟:
這裏寫圖片描述

4、各結構體的作用與它們之間的關係

a – i2c_adapter與i2c_algorithm
i2c_adapter對應與物理上的一個適配器,而i2c_algorithm對應一套通信方法,一個i2c適配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下層與硬件相關的代碼提供)通信函數來控制適配器上產生特定的訪問週期。缺少i2c_algorithm的i2c_adapter什麼也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指針。
i2c_algorithm中的關鍵函數master_xfer()用於產生i2c訪問週期需要的start stop ack信號,以i2c_msg(即i2c消息)爲單位發送和接收通信數據。
  i2c_msg也非常關鍵,調用驅動中的發送接收函數需要填充該結構體

struct i2c_msg {    
    __u16 addr; /* slave address            */    
    __u16 flags;            
    __u16 len;      /* msg length               */    
    __u8 *buf;      /* pointer to msg data          */    
};    

b –i2c_driver和i2c_client
i2c_driver對應一套驅動方法,其主要函數是attach_adapter()和detach_client()
i2c_client對應真實的i2c物理設備device,每個i2c設備都需要一個i2c_client來描述
2c_driver與i2c_client的關係是一對多。一個i2c_driver上可以支持多個同等類型的i2c_client.
c – i2c_adapter和i2c_client
  i2c_adapter和i2c_client的關係與i2c硬件體系中適配器和設備的關係一致,即i2c_client依附於i2c_adapter,由於一個適配器上可以連接多個i2c設備,所以i2c_adapter中包含依附於它的i2c_client的鏈表。
 從i2c驅動架構圖中可以看出,linux內核對i2c架構抽象了一個叫核心層core的中間件,它分離了設備驅動device driver和硬件控制的實現細節(如操作i2c的寄存器),core層不但爲上面的設備驅動提供封裝後的內核註冊函數,而且還爲小面的硬件事件提供註冊接口(也就是i2c總線註冊接口),可以說core層起到了承上啓下的作用。

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