I2C 架構概述
Linux 的 I2C 體系結構分為3個組成部分:
I2C 核心:I2C 核心提供了 I2C 總線驅動和裝置驅動的注冊、登出方法,I2C 通信方法(“algorithm”)上層的,與具體擴充卡無關的代碼以及探測裝置,檢測裝置位址的上層代碼等。
I2C 總線驅動:I2C 總線驅動是對 I2C 硬體體系結構中擴充卡端的實作,擴充卡可由 CPU 控制,甚至可以直接內建在CPU内部。
I2C 裝置驅動:I2C 裝置驅動(也稱為客戶驅動)是對 I2C 硬體體系結構中裝置端的實作,裝置一般挂接在受 CPU 控制的 I2C 擴充卡上,通過 I2C 擴充卡與 CPU 交換資料。
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 核心和晶片提供商為我們的驅動程式提供了 I2C 驅動的架構,以及架構底層與硬體相關的代碼的實作。
剩下的就是針對挂載在 I2C 兩線上的 I2C 裝置 device,而編寫的即具體裝置驅動了,這裡的裝置就是硬體接口外挂載的裝置,而非硬體接口本身(SOC 硬體接口本身的驅動可以了解為總線驅動)。
編寫驅動需要完成的工作
編寫具體的 I2C 驅動時,工程師需要處理的主要工作如下:
- 提供 I2C 擴充卡的硬體驅動,探測,初始化 I2C 擴充卡(如申請 I2C 的 I/O 位址和中斷号),驅動CPU控制的 I2C 擴充卡從硬體上産生。
- 提供 I2C 控制的 algorithm,用具體擴充卡的 xxx_xfer()函數填充 i2c_algorithm 的 master_xfer 指針,并把 i2c_algorithm 指針賦給 i2c_adapter 的 algo 指針。
- 實作 I2C 裝置驅動中的 i2c_driver 接口,用具體 yyy 的 yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函數指針和 i2c_device_id 裝置 ID 表賦給 i2c_driver 的 probe, remove, suspend, resume 和 id_table 指針。
- 實作 I2C 裝置所對應類型的具體驅動, i2c_driver 隻是實作裝置與總線的挂接。
上面的工作中前兩個輸入 I2C 總線驅動,後面兩個屬于 I2C 裝置驅動。