天天看點

[Linux 驅動] -- Linux 下 I2C 驅動架構全面分析

I2C 架構概述

Linux 的 I2C 體系結構分為3個組成部分:

I2C 核心:I2C 核心提供了 I2C 總線驅動和裝置驅動的注冊、登出方法,I2C 通信方法(“algorithm”)上層的,與具體擴充卡無關的代碼以及探測裝置,檢測裝置位址的上層代碼等。

I2C 總線驅動:I2C 總線驅動是對 I2C 硬體體系結構中擴充卡端的實作,擴充卡可由 CPU 控制,甚至可以直接內建在CPU内部。

I2C 裝置驅動:I2C 裝置驅動(也稱為客戶驅動)是對 I2C 硬體體系結構中裝置端的實作,裝置一般挂接在受 CPU 控制的 I2C 擴充卡上,通過 I2C 擴充卡與 CPU 交換資料。

Linux 驅動中 I2C 驅動架構

[Linux 驅動] -- Linux 下 I2C 驅動架構全面分析

上圖完整的描述了 Linux I2C 驅動架構,雖然 I2C 硬體體系結構比較簡單,但是 I2C 體系結構在 Linux 中的實作卻相當複雜。

那麼我們如何編寫特定 I2C 接口器件的驅動程式?就是說上述架構中的哪些部分需要我們完成,而哪些是Linux核心已經完善的或者是晶片提供商已經提供的?

架構層次分類

第一層:提供 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中的(與特定硬體相關的代碼)已由晶片提供商編寫完成,例如三星的s3c2440平台 i2c 總線 bus 為 /drivers/i2c/buses/i2c-s3c2410.c。

第三第四層與特定 device 相幹的就需要驅動工程師來實作了。

Linux 下 I2C 系統檔案構架

在 Linux 核心源代碼中的 driver 目錄下包含一個 I2C 目錄。

i2c-core.c 這個檔案實作了 I2C 核心的功能以及 /proc/bus/i2c* 接口。

i2c-dev.c 實作了 I2C 擴充卡裝置檔案的功能,每一個 I2C 擴充卡都被配置設定一個裝置。通過擴充卡通路裝置時的主裝置号都為89,次裝置号為 0-255. i2c-dev.c 并沒有針對特定的裝置而設計,隻是提供了通用的 read()、write() 和 ioctl() 等接口,應用層可以借用這些接口通路挂接在擴充卡上的 I2C 裝置的存儲空間或寄存器,并控制 I2C  裝置的工作方式。

busses 檔案夾包含了一些 I2C 總線的驅動,如針對 S3C2410、S3C2440、S3C6410 等處理器的 I2C 控制器驅動為 i2c-s3c2410.c。

algos 檔案夾實作了一些 I2C 總線擴充卡的 algorithm。

重要的結構體

i2c_driver

struct i2c_driver {
    unsigned int class;
    int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函數指針
    int (*detach_adapter)(struct i2c_adapter *);//脫離i2c_adapter函數指針
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
    void (*alert)(struct i2c_client *, unsigned int data);
    int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//指令清單
    struct device_driver driver;
    const struct i2c_device_id *id_table;//該驅動所支援的裝置ID表
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};
           

i2c_client

struct i2c_client {
     unsigned short flags;//标志  
     unsigned short addr; //低7位為晶片位址  
     char name[I2C_NAME_SIZE];//裝置名稱
     struct i2c_adapter *adapter;//依附的i2c_adapter
     struct i2c_driver *driver;//依附的i2c_driver 
     struct device dev;//裝置結構體  
     int irq;//裝置所使用的結構體  
     struct list_head detected;//連結清單頭
 };
           

i2c_adapter

struct i2c_adapter {
     struct module *owner;//所屬子產品
     unsigned int id;//algorithm的類型,定義于i2c-id.h,
     unsigned int class;    
     const struct i2c_algorithm *algo; //總線通信方法結構體指針
     void *algo_data;//algorithm資料
     struct rt_mutex bus_lock;//控制并發通路的自旋鎖
     int timeout;   
     int retries;//重試次數
     struct device dev; //擴充卡裝置 
     int nr;
     char name[48];//擴充卡名稱
     struct completion dev_released;//用于同步
     struct list_head userspace_clients;//client連結清單頭
};
           

i2c_algorithm

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);//I2C傳輸函數指針
    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傳輸函數指針
    u32 (*functionality) (struct i2c_adapter *);//傳回擴充卡支援的功能
};
           

各結構體的作用與它們之間的關系

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          */  
}; 
           

i2c_driver 和 i2c_client

i2c_driver 對應一套驅動方法,其主要函數是 attach_adapter() he detach_client()。

i2c_client 對應正式的 i2c 實體裝置 device,每個 i2c 裝置都需要一個 i2c_client 來描述。

i2c_driver 與 i2c_client 的關系是一對多。一個i2c_driver 上可以支援多個同等類型的 i2c_client。

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 層起到了承上啟下的作用。

代碼調用層次圖

[Linux 驅動] -- Linux 下 I2C 驅動架構全面分析

上圖的展示告訴我們:Linux 核心和晶片提供商為我們的驅動程式提供了 I2C 驅動的架構,以及架構底層與硬體相關的代碼的實作。

剩下的就是針對挂載在 I2C 兩線上的 I2C 裝置 device,而編寫的即具體裝置驅動了,這裡的裝置就是硬體接口外挂載的裝置,而非硬體接口本身(SOC 硬體接口本身的驅動可以了解為總線驅動)。

編寫驅動需要完成的工作

編寫具體的 I2C 驅動時,工程師需要處理的主要工作如下:

  1. 提供 I2C 擴充卡的硬體驅動,探測,初始化 I2C 擴充卡(如申請 I2C 的 I/O 位址和中斷号),驅動CPU控制的 I2C 擴充卡從硬體上産生。
  2. 提供 I2C 控制的 algorithm,用具體擴充卡的 xxx_xfer()函數填充 i2c_algorithm 的 master_xfer 指針,并把 i2c_algorithm 指針賦給 i2c_adapter 的 algo 指針。
  3. 實作 I2C 裝置驅動中的 i2c_driver 接口,用具體 yyy 的 yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函數指針和 i2c_device_id 裝置 ID 表賦給 i2c_driver 的 probe, remove, suspend, resume 和 id_table 指針。
  4. 實作 I2C 裝置所對應類型的具體驅動, i2c_driver 隻是實作裝置與總線的挂接。

上面的工作中前兩個輸入 I2C 總線驅動,後面兩個屬于 I2C 裝置驅動。

繼續閱讀