談到在linux系統下編寫I2C驅動,目前主要有兩種方法,一種是把I2C設備當作一個普通的字符設備來處理,另一種是利用linux下I2C驅動體系結構來完成。下面比較下這兩種方法:
第一種方法:
優點:思路比較直接,不需要花很多時間去了解linux中複雜的I2C子系統的操作方法。
第一種方法的缺點:
要求工程師不僅要對I2C設備的操作熟悉,而且要熟悉I2C的適配器(I2C控制器)操作。
要求工程師對I2C的設備器及I2C的設備操作方法都比較熟悉,最重要的是寫出的程序可以移植性差。
對內核的資源無法直接使用,因爲內核提供的所有I2C設備器以及設備驅動都是基於I2C子系統的格式。
第一種方法的缺點就是第二種方法的優點。
1.Linux的I2C體系結構分爲3個組成部分:
I2C核心
I2C核心提供了I2C總線驅動和設備驅動的註冊,註銷方法,I2C通信方法(”algorithm”)上層的,與具體適配器無關的代碼以及探測設備,檢測設備地址的上層代碼等。
I2C總線驅動
I2C總線驅動是對I2C硬件體系結構中適配器端的實現,適配器可由CPU控制,甚至可以直接集成在CPU內部。
2.Linux I2C體系結構
在linux內核源代碼中drivers/i2c/busses目錄下包含着一些適配器的驅動。如S3C2440的驅動i2c-s3c2410.c。適配器加載到內核後,接下來的工作就是要針對具體的設備編寫驅動了。
i2c-core.c這個文件實現了I2C核心的功能以及/proc/bus/i2c*接口。
i2c-dev.c實現了I2C適配器設備文件的功能,每一個I2C適配器都被分配一個設備。通過適配器訪設備時的主設備號都爲89,次設備號爲0-255。I2c-dev.c並沒有針對特定的設備而設計,只是提供了通用的read(),write(),和ioctl()等接口,應用層可以借用這些接口訪問掛接在適配器上的I2C設備的存儲空間或寄存器,並控制I2C設備的工作方式。
Chips這個目錄包含了一些特定的I2C設備驅動。在具體的I2C設備驅動中,調用的都是I2C核心提供的API,因此,這使得具體的I2C設備驅動不依賴於CPU的類型和I2C適配器的硬件特性。
busses文件夾這個文件中包含了一些I2C總線的驅動,如針對S3C2410,S3C2440,S3C6410等處理器的I2C控制器驅動爲i2c-s3c2410.c.
algos文件夾實現了一些I2C總線適配器的algorithm.
1.各個結構體
在內核中的i2c.h這個頭文件中對i2c_driver,i2c_client,i2c_adapter和i2c_algorithm這個四個結構體進行了定義。理解這4個結構體的作用十分關鍵。
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鏈表頭
};
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 *);//返回適配器支持的功能
};
SMbus大部分基於I2C總線規範,SMbus不需要增加額外引腳。與I2C總線相比,SMbus增加了一些新的功能特性,在訪問時序也有
一定的差異。
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 (*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;//鏈表頭
};
2:各結構體的作用與它們之間的關係
i2c_adapter對應於物理上的一個適配器,而i2c_algorithm對應一套通信方法。一個i2c適配器需要i2c_algorithm中提供的通信函數來控制適配器上產生特定的訪問週期。缺少i2c_algorithm的i2c_adapter什麼也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指針。i2c_algorithm中的關鍵函數master_xfer()用於產生I2C訪問週期需要的信號,以i2c_msg(即I2C消息)爲單位。i2c_msg也很重要,代碼清單如下:
struct i2c_msg {
__u16 addr;//設備地址
__u16 flags;//標誌
__u16 len;//消息長度
__u8 *buf;//消息數據
};
i2c_driver與i2c_clienti2c_driver對應一套驅動方法,其主要成員函數是probe(),remove(),suspend(),resume()等,另外id_table是該驅動所支持的I2C設備的ID表。i2c_client對應於真實的物理設備,每個I2C設備都需要一個i2c_client來描述。i2c_driver與i2c_client的關係是一對多,一個i2c_driver上可以支持多個同等類型的i2c_client。i2c_client信息通常在BSP的板文件中通過i2c_board_info填充。一般在arch/arm目錄下的板文件中。在I2C總線驅動i2c_bus_type的match()函數i2c_device_match()中,會調用i2c_match_id()函數匹配板文件中的ID和i2c_driver所支持的ID表。
i2c_adpater與i2c_client i2c_adpater與i2c_client的關係與I2C硬件體系中適配器和設備的關係一致,即i2c_client依附與i2c_adpater.由於一個適配器上可以連接多個I2C設備,所以就一個i2c_adpter也可以被多個i2c_client依附,i2c_adpter中包括依附與它的i2c_client的鏈表。
3.編寫驅動需要完成的工作
編寫具體的I2C驅動時,工程師需要處理的主要工作如下:
2).提供I2C控制的algorithm, 用具體適配器的xxx_xfer()函數填充i2c_algorithm的master_xfer指針,並把i2c_algorithm指針賦給i2c_adapter的algo指針。
3).實現I2C設備驅動中的i2c_driver接口,用具體yyy的yyy_probe(),
上面的工作中前兩個屬於I2C總線驅動,後面兩個屬於I2C設備驅動。
數據傳送:SCL線呈現高電平期間,SDA線上的電平必須保持穩定,低電平表示0(此時的線電壓爲地電壓),高電平表示1(此時的電壓由元器件的VDD決定)。只有在SCL線爲低電平期間,SDA上的電平允許變化。
應答信號ACK:I2C總線的數據都是以字節(8位)的方式傳送的,發送器件每發送一個字節之後,在時鐘的第9個脈衝期間釋放數據總線,由接收器發送一個ACK(把數據總線的電平拉低)來表示數據成功接收。
無應答信號NACK: 在時鐘的第9個脈衝期間發送器釋放數據總線,接收器不拉低數據總線表示一個NACK,NACK有兩種用途:
a. 一般表示接收器未成功接收數據字節;
b. 當接收器是主控器時,它收到最後一個字節後,應發送一個NACK信號,以通知被控發送器結束數據發送,並釋放總線,以便主控接收器發送一個停止信號STOP。
開始與停止信號的時序圖
I2C的讀寫時序
讀過程
寫過程
在linux系統中,適配器驅動位於linux目錄下的\drivers\i2c\busses下,不同的處理器的適配器驅動程序設計有差異,但是總體思路不變,在適配器的驅動中,實現兩個結構體非常關鍵,也是整個適配器驅動的靈魂。下面以某個適配器的驅動程序爲例進行說明:
static struct platform_driver tcc_i2c_driver = {
.probe = tcc_i2c_probe,
.remove = tcc_i2c_remove,
.suspend = tcc_i2c_suspend_late,
.resume = tcc_i2c_resume_early,
.driver = {
.owner = THIS_MODULE,
.name = "tcc-i2c",
},
};
看見這個結構體應該不會陌生,說明這個驅動是基於平臺總線的,這樣實現的目的是與CPU緊緊聯繫起來。
static const struct i2c_algorithm tcc_i2c_algorithm = {
.master_xfer = tcc_i2c_xfer,
.functionality = tcc_i2c_func,
};
這個結構體也是非常的關鍵,這個結構體裏面的函數tcc_i2c_xfer是適配器算法的實現,這個函數實現了適配器與I2C CORE的連接。
tcc_i2c_func是指該適配器所支持的功能。tcc_i2c_xfer這個函數實質是實現I2C數據的發送與接收的處理過程。不同的處理器實
在I2C-core.c這個函數中,把握下面的幾個關鍵函數就可以了。
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_del_adapter(struct i2c_adapter *adap)
增加/刪除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
i2c_client依附/脫離
int i2c_attach_client(struct i2c_client *client)
增加/刪除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
i2c_client依附/脫離
int i2c_attach_client(struct i2c_client *client)
I2C傳輸,發送和接收
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)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
I2c_transfer這個函數實現了core與adapter的聯繫。更深一步的分析見代碼註釋。
在linux目錄下的\drivers\misc\eeprom中實現了大部分EEPROM的驅動。對於EEPROM而言,設備本身的驅動以bin_attribute二進制sysfs結點形式呈現。分析這個驅動首先看關鍵的結構體
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.owner = THIS_MODULE,
},
.probe = at24_probe,
.remove = __devexit_p(at24_remove),
.id_table = at24_ids,
};
從上面看說明這個驅動又是基於平臺總線的。再進一步看at24_bin_read()與at24_bin_write()這兩個函數,這兩個函數
會調用I2C_core.c中的i2c_transfer()函數,從而實現了設備,core,適配器這三者的聯繫。
更深一步的分析見代碼註釋。