Linux I2C裝置驅動編寫
在Linux驅動中I2C系統中主要包含以下幾個成員:
I2C adapter 即I2C介面卡
I2C driver 某個I2C裝置的裝置驅動,可以以driver理解。
I2C client 某個I2C裝置的裝置宣告,可以以device理解。
I2C adapter
是CPU整合或外接的I2C介面卡,用來控制各種I2C從裝置,其驅動需要完成對介面卡的完整描述,最主要的工作是需要完成i2c_algorithm結構體。這個結構體包含了此I2C控制器的資料傳輸具體實現,以及對外上報此裝置所支援的功能型別。i2c_algorithm結構體如下:
struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); u32 (*functionality) (struct i2c_adapter *); };
如果一個I2C介面卡不支援I2C通道,那麼就將master_xfer成員設為NULL。如果介面卡支援SMBUS協議,那麼需要去實現smbus_xfer,如果smbus_xfer指標被設為NULL,那麼當使用SMBUS協議的時候將會通過I2C通道進行模擬。master_xfer指向的函式的返回值應該是已經成功處理的訊息數,或者返回負數表示出錯了。functionality指標很簡單,告訴詢問著這個I2C主控器都支援什麼功能。
在核心的drivers/i2c/i2c-stub.c中實現了一個i2c adapter的例子,其中實現的是更為複雜的SMBUS。
SMBus 與 I2C的區別
通常情況下,I2C和SMBus是相容的,但是還是有些微妙的區別的。
時鐘速度對比:
I2C | SMBus | |
---|---|---|
最小 | 無 | 10kHz |
最大 | 100kHZ(標準)400kHz(快速模式)2MHz(高速模式) | 100kHz |
超時 | 無 | 35ms |
在電氣特性上他們也有所不同,SMBus要求的電壓範圍更低。
I2C driver
具體的I2C裝置驅動,如相機、感測器、觸控式螢幕、背光控制器常見硬體裝置大多都有或都是通過I2C協議與主機進行資料傳輸、控制。結構體如下:
struct i2c_driver { unsigned int class; /* Notifies the driver that a new bus has appeared or is about to be * removed. You should avoid using this, it will be removed in a * near future. */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; //舊的與裝置進行繫結的介面函式 int (*detach_adapter)(struct i2c_adapter *) __deprecated; //舊的與裝置進行解綁的介面函式 /* Standard driver model interfaces */ int (*probe)(struct i2c_client *, const struct i2c_device_id *); //現行通用的與對應裝置進行繫結的介面函式 int (*remove)(struct i2c_client *); //現行通用與對應裝置進行解綁的介面函式 /* driver model interfaces that don't relate to enumeration */ void (*shutdown)(struct i2c_client *); //關閉裝置 int (*suspend)(struct i2c_client *, pm_message_t mesg); //掛起裝置,與電源管理有關,為省電 int (*resume)(struct i2c_client *); //從掛起狀態恢復 /* Alert callback, for example for the SMBus alert protocol. * The format and meaning of the data value depends on the protocol. * For the SMBus alert protocol, there is a single bit of data passed * as the alert response's low bit ("event flag"). */ void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions * with the device. */ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver; //I2C裝置的驅動模型 const struct i2c_device_id *id_table; //匹配裝置列表 /* Device detection callback for automatic device creation */ int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients; }; #define to_i2c_driver(d) container_of(d, struct i2c_driver, driver) //一般編寫驅動過程中物件常是driver型別,可以通過to_i2c_driver找到其父型別i2c_driver
如同普通裝置的驅動能夠驅動多個裝置一樣,一個I2C driver也可以對應多個I2C client。
以重力感測器AXLL34X為例,其實現的I2C驅動為:
static const struct i2c_device_id adxl34x_id[] = {
{ "adxl34x", 0 }, //匹配i2c client名為adxl34x的裝置
{ }
};
MODULE_DEVICE_TABLE(i2c, adxl34x_id);
static struct i2c_driver adxl34x_driver = {
.driver = {
.name = "adxl34x",
.owner = THIS_MODULE,
.pm = &adxl34x_i2c_pm, //指定裝置驅動的電源管理介面,包含suspend、resume
},
.probe = adxl34x_i2c_probe, //組裝裝置匹配時候的匹配動作
.remove = adxl34x_i2c_remove, //組裝裝置移除介面
.id_table = adxl34x_id, //制定匹配裝置列表
};
module_i2c_driver(adxl34x_driver);
這裡要說明一下module_i2c_driver巨集定義(i2c.h):
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
module_driver():
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
理解上述巨集定義後,將module_i2c_driver(adxl34x_driver)展開就可以得到:
static int __int adxl34x_driver_init(void)
{
return i2c_register_driver(&adxl34x_driver);
}
module_init(adxl34x_driver_init);
static void __exit adxl34x_driver_exit(void)
{
return i2c_del_driver(&adxl34x_driver);
}
module_exit(adxl34x_driver_exit);
這一句巨集就解決了模組module安裝解除安裝的複雜程式碼。這樣驅動開發者在實現I2C驅動時只要將i2c_driver結構體填充進來就可以了,無需關心裝置的註冊與反註冊過程。
I2C client
即I2C裝置。I2C裝置的註冊一般在板級程式碼中,在解析例項前還是先熟悉幾個定義:
struct i2c_client {
unsigned short flags; //I2C_CLIENT_TEN表示裝置使用10bit從地址,I2C_CLIENT_PEC表示裝置使用SMBus檢錯
unsigned short addr; //裝置從地址,7bit。這裡說一下為什麼是7位,因為最後以為0表示寫,1表示讀,通過對這個7bit地址移位處理即可。addr<<1 & 0x0即寫,addr<<1 | 0x01即讀。
char name[I2C_NAME_SIZE]; //從裝置名稱
struct i2c_adapter *adapter; //此從裝置依附於哪個adapter上
struct i2c_driver *driver; // 此裝置對應的I2C驅動指標
struct device dev; // 裝置模型
int irq; // 裝置使用的中斷號
struct list_head detected; //用於連結串列操作
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev) //通常使用device裝置模型進行操作,可以通過to_i2c_client找到對應client指標
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //裝置名,最長20個字元,最終安裝到client的name上
unsigned short flags; //最終安裝到client.flags
unsigned short addr; //裝置從地址slave address,最終安裝到client.addr上
void *platform_data; //裝置資料,最終儲存到i2c_client.dev.platform_data上
struct dev_archdata *archdata;
struct device_node *of_node; //OpenFirmware裝置節點指標
struct acpi_dev_node acpi_node;
int irq; //裝置採用的中斷號,最終儲存到i2c_client.irq上
};
//可以看到,i2c_board_info基本是與i2c_client對應的。
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
//通過這個巨集定義可以方便的定義I2C裝置的名稱和從地址(別忘了是7bit的)
下面還是以adxl34x為例:
static struct i2c_board_info i2c0_devices[] = {
{
I2C_BOARD_INFO("ak4648", 0x12),
},
{
I2C_BOARD_INFO("r2025sd", 0x32),
},
{
I2C_BOARD_INFO("ak8975", 0x0c),
.irq = intcs_evt2irq(0x3380), /* IRQ28 */
},
{
I2C_BOARD_INFO("adxl34x", 0x1d),
.irq = intcs_evt2irq(0x3340), /* IRQ26 */
},
};
...
i2c_register_board_info(0, i2c0_devices, ARRAY_SIZE(i2c0_devices));
這樣ADXL34X的i2c裝置就被註冊到了系統中,當名字與i2c_driver中的id_table中的成員匹配時就能夠出發probe匹配函數了。
在(一)中簡述了Linux I2C子系統的三個主要成員i2c_adapter、i2c_driver、i2c_client。三者的關係也在上一節進行了描述。應該已經算是對Linux I2C子系統有了初步的瞭解。下面再對他們之間的關係進行程式碼層的深入分析,我認為對他們的關係瞭解的越好,越有助於I2C裝置的驅動開發及除錯。
帶著問題去分析可能會更有幫助吧,通過對(一)的瞭解後,可能會產生以下的幾點疑問:
- i2c_adapter驅動如何新增?
- i2c_client與i2c_board_info究竟是什麼關係?
I2C對外API
// 對外資料結構
struct i2c_driver — 代表一個I2C裝置驅動
struct i2c_client — 代表一個I2C從裝置
struct i2c_board_info — 從裝置建立的模版
I2C_BOARD_INFO — 建立I2C裝置的巨集,包含名字和地址
struct i2c_algorithm — 代表I2C傳輸方法
struct i2c_bus_recovery_info — I2C匯流排恢復資訊?核心新加入的結構,不是很清楚。
//對外函式操作
module_i2c_driver — 註冊I2C裝置驅動的巨集定義
i2c_register_board_info — 靜態宣告(註冊)I2C裝置,可多個
i2c_verify_client — 如果裝置是i2c_client的dev成員則返回其父指標,否則返回NULL。用來校驗裝置是否為I2C裝置
i2c_lock_adapter — I2C匯流排持鎖操作,會找到最根源的那個i2c_adapter。說明你的模組必須符合GPL協議才可以使用這個介面。後邊以GPL代表。
i2c_unlock_adapter — 上一個的反操作,GPL
i2c_new_device — 由i2c_board_info資訊宣告一個i2c裝置(client),GPL
i2c_unregister_device — 上一個的反操作,GPL。
i2c_new_dummy — 宣告一個名為dummy(指定地址)的I2C裝置,GPL
i2c_verify_adapter — 驗證是否是i2c_adapter
i2c_add_adapter — 宣告I2C介面卡,系統動態分配匯流排號。
i2c_add_numbered_adapter — 同樣是宣告I2C介面卡,但是指定了匯流排號,GPL
i2c_del_adapter — 解除安裝I2C介面卡
i2c_del_driver — 解除安裝I2C裝置驅動
i2c_use_client — i2c_client引用數+1
i2c_release_client — i2c_client引用數-1
__i2c_transfer — 沒有自動持鎖(adapter lock)的I2C傳輸介面
i2c_transfer — 自動持鎖的I2C傳輸介面
i2c_master_send — 單條訊息傳送
i2c_master_recv — 單條訊息接收
i2c_smbus_read_byte — SMBus “receive byte” protocol
i2c_smbus_write_byte — SMBus “send byte” protocol
i2c_smbus_read_byte_data — SMBus “read byte” protocol
i2c_smbus_write_byte_data — SMBus “write byte” protocol
i2c_smbus_read_word_data — SMBus “read word” protocol
i2c_smbus_write_word_data — SMBus “write word” protocol
i2c_smbus_read_block_data — SMBus “block read” protocol
i2c_smbus_write_block_data — SMBus “block write” protocol
i2c_smbus_xfer — execute SMBus protocol operations
(一)中對幾個基本的結構體和巨集定義也有了大概的解釋,相信結合I2C的理論基礎不難理解。對以上一些I2C的API進行分類:
No. | Adapter | Driver | Device(client) | Transfer |
---|---|---|---|---|
1 | i2c_add_adapter | module_i2c_driver | i2c_register_board_info | __i2c_transfer |
2 | i2c_add_numbered_adapter | i2c_del_driver | i2c_new_device | i2c_transfer |
3 | i2c_del_adapter | i2c_new_dummy | i2c_master_send | |
4 | i2c_lock_adapter | i2c_verify_client | i2c_master_recv | |
5 | i2c_unlock_adapter | i2c_unregister_device | i2c_smbus_read_byte | |
6 | i2c_verify_adapter | i2c_use_client | i2c_smbus_write_byte | |
7 | i2c_release_client | i2c_smbus_read_byte_data | ||
8 | i2c_smbus_write_byte_data | |||
9 | i2c_smbus_read_word_data | |||
10 | i2c_smbus_write_word_data | |||
11 | i2c_smbus_read_block_data | |||
12 | i2c_smbus_write_block_data | |||
13 | i2c_smbus_xfer |
經過一個表格的整理,不難發現在Linux I2C子系統中,最重要的要數i2c_client,而最多樣化的就是資料的傳輸。
為了更好的理解和銜接,我想也許倒著分析會更有幫助,而這裡先暫且不討論I2C傳輸過程中的細節。下邊的順序是由client到driver,再到adapter。
I2C client的註冊
i2c_client即I2C裝置的註冊介面有三個:
i2c_register_board_info
i2c_new_device
i2c_new_dummy
而i2c_new_dummy在內部其實也就是將client的name指定為dummy後依舊執行的是i2c_new_device,所以就只分析前兩個就可以了。首先看這兩個函式的原型:
i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
busnum 通過匯流排號指定這個(些)裝置屬於哪個匯流排
info i2c裝置的陣列集合 i2c_board_info格式
len 陣列個數ARRAY_SIZE(info)
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
adap 此裝置所依附的I2C介面卡指標
info 此裝置描述,i2c_board_info格式,bus_num成員是被忽略的
i2c_register_board_info具體實現
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock); //i2c裝置資訊讀寫鎖,鎖寫操作,其他只讀
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num) //與動態分配的匯流排號相關,動態分配的匯流排號應該是從已經現有最大匯流排號基礎上+1的,這樣能夠保證動態分配出的匯流排號與板級匯流排號不會產生衝突
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) { //處理info陣列中每個成員
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum; //組裝匯流排號
devinfo->board_info = *info; //組裝裝置資訊
list_add_tail(&devinfo->list, &__i2c_board_list); //加入到__i2c_board_list連結串列中(尾部)
}
up_write(&__i2c_board_lock); //釋放讀鎖,其他可讀可寫
return status;
}
看完後相信都會產生個疑問?怎麼將相關資訊放到連結串列中就算完事了嗎?不著急,來看下核心中已經給出的解釋:
* Systems using the Linux I2C driver stack can declare tables of board info
* while they initialize. This should be done in board-specific init code
* near arch_initcall() time, or equivalent, before any I2C adapter driver is
* registered. For example, mainboard init code could define several devices,
* as could the init code for each daughtercard in a board stack.
*
* The I2C devices will be created later, after the adapter for the relevant
* bus has been registered. After that moment, standard driver model tools
* are used to bind "new style" I2C drivers to the devices. The bus number
* for any device declared using this routine is not available for dynamic
* allocation.
核心內容就是說關於整合的I2C設備註冊過程應該在板級程式碼初始化期間,也就是arch_initcall前後的時間,或者就在這個時候(board-xxx-yyy.c中),切記切記!!!一定要在I2C介面卡驅動註冊前完成!!!為什麼說是靜態註冊,是因為真實的I2C裝置是在介面卡成功註冊後才被生成的。如果在I2C介面卡註冊完後還想要新增I2C裝置的話,就要通過新方式!(即i2c_new_device)
小弟永遠要擋在老大前邊嘛!老大還沒出來前,小弟們趕緊前排列陣好,老大到了可不等你,你列陣也沒用了。而對於遲到的小弟,自己想辦法追上去吧,在原地自己列陣是白費工夫了。
對於__i2c_board_list連結串列中的資訊是如何變成實際的i2c裝置資訊的過程放在之後adapter註冊過程的分析中。記得,重點是i2c_register_board_info方式一定要趕在I2C介面卡的註冊前,這樣就沒有問題。
i2c_new_device
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL); //為即將註冊的client申請記憶體
if (!client)
return NULL;
client->adapter = adap; //繫結指定的adapter介面卡
client->dev.platform_data = info->platform_data; //儲存裝置資料
if (info->archdata) //程式碼上看是DMA相關操作資料
client->dev.archdata = *info->archdata;
client->flags = info->flags; //型別,(一)中說過,或是10位地址,或是使用SMBus檢錯
client->addr = info->addr; //裝置從地址
client->irq = info->irq; //裝置終端
strlcpy(client->name, info->type, sizeof(client->name)); //從裝置名
//瞧!(一)中說過i2c_board_info中的資訊是與i2c_client有對應關係的,靈驗了吧!
/* Check for address validity */
status = i2c_check_client_addr_validity(client); //檢測地址是否有效,10位地址是否大於0x3ff,7位地址是否大於0x7f或為0
if (status) { //非零(實際上為-22,無效引數Invalid argument
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
/* Check for address business */
status = i2c_check_addr_busy(adap, client->addr); //檢測指定介面卡上該地址狀態
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev; //建立從裝置與介面卡的父子關係
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
ACPI_HANDLE_SET(&client->dev, info->acpi_node.handle);
/* For 10-bit clients, add an arbitrary offset to avoid collisions */
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr | ((client->flags & I2C_CLIENT_TEN)
? 0xa000 : 0)); //如果是10位地址裝置,那名字格式與7bit的會有不同
status = device_register(&client->dev); //註冊了!註冊了!!!
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
out_err_silent:
kfree(client);
return NULL;
}
i2d_new_device沒什麼好多說的,由於有i2c_register_board_info的鋪墊,相信也很好理解了。而i2c_new_device不但印證了i2c_client與i2c_board_info的對應關係,還順便體現了10bit地址裝置與7bit地址裝置的略微不同。通過兩者的對比,可以再總結出幾點區別,從而更好的理解I2C裝置的註冊方法:
- i2c_register_board_info的形參需要的是匯流排號
- i2c_new_device的形參需要的直接是介面卡的指標
我想這也正好能完美的說明兩者的根本區別,對於板級裝置更在乎介面卡的匯流排號,因為這是固定的,沒有異議的。而對於可插拔裝置,因為其介面卡可能非板級整合的,所以不能在乎其匯流排號,反而只要尋求其介面卡指標進行繫結即可。後邊adapter註冊的分析能更好的證明這一點。
- i2c_register_board_info可以同時註冊多個I2C裝置
- i2c_new_device只能一次註冊一個I2C裝置
這也是其根本區別決定的,板級程式碼中常包含有許多I2C裝置,所以i2c_register_board_info需要有同時註冊多個I2C裝置的能力也可以說是剛需。而i2c_new_device既然是用來給可插拔裝置用的,想必裝置數量並不多,而常可能只是一個兩個而已,所以一次一個就夠了,需求量並不大。
I2C driver
I2C裝置驅動。Linux核心給出的介面只有兩個,一個是註冊,另一個就是解除安裝。在(一)也分析過module_i2c_driver這個巨集定義,因為有它的存在,I2C裝置驅動的開發可以不用在意你的I2C驅動需要如何註冊以及如何解除安裝的,全部的精力都放在i2c_driver的完善上就可以了。
通過最開始的表單能明顯察覺到,I2C子系統中I2C driver的開放介面最少,說白了就是需要驅動編寫者完成完了i2c_driver放入module_i2c_driver巨集中即可,而正因為如此,也恰恰說明,i2c_driver的靈活性是最高的。通常驅動會首先在意在使用者空間的開啟、關閉、讀寫等介面,但是對於i2c_driver來說,這些工作是I2C子系統已經做好的,關於常用的讀寫最終也是通過adapter實現的i2c_algorithm達到目的。好吧,再次說明了I2C子系統的完善程度,對於I2C裝置及驅動開發來說是極其方便的。那麼I2C驅動要實現什麼呢?
再次回顧一下i2c_driver結構體,不過現在要剔除一些不常用的成員:
struct i2c_driver {
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 *); //從掛起狀態恢復
struct device_driver driver; //I2C裝置的驅動模型
const struct i2c_device_id *id_table; //匹配裝置列表
...
};
如果有可能的話,我還想再精簡一下:
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //現行通用的與對應裝置進行繫結的介面函式
int (*remove)(struct i2c_client *); //現行通用與對應裝置進行解綁的介面函式
struct device_driver driver; //I2C裝置的驅動模型
const struct i2c_device_id *id_table; //匹配裝置列表
...
};
好了,精簡到這種程度,為什麼把電源管理相關也幹掉了呢?實際上沒有,通常實際的I2C驅動喜歡在drivers中完成這個動作(以mpu3050為例):
static UNIVERSAL_DEV_PM_OPS(mpu3050_pm, mpu3050_suspend, mpu3050_resume, NULL);
static const struct i2c_device_id mpu3050_ids[] = {
{ "mpu3050", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, mpu3050_ids);
static const struct of_device_id mpu3050_of_match[] = {
{ .compatible = "invn,mpu3050", },
{ },
};
MODULE_DEVICE_TABLE(of, mpu3050_of_match);
static struct i2c_driver mpu3050_i2c_driver = {
.driver = {
.name = "mpu3050",
.owner = THIS_MODULE,
.pm = &mpu3050_pm,
.of_match_table = mpu3050_of_match,
},
.probe = mpu3050_probe,
.remove = mpu3050_remove,
.id_table = mpu3050_ids,
};
module_i2c_driver(mpu3050_i2c_driver);
可以看到,實際驅動中喜歡將電源管理整合在i2c_driver的driver成員中。
UNIVERSAL_DEV_PM_OPS這個名字很犀利,貌似是“宇宙終極驅動電源管理大法”的樣子:
#define UNIVERSAL_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn) \
const struct dev_pm_ops name = { \
SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
}
#define SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
.suspend = suspend_fn, \
.resume = resume_fn, \
.freeze = suspend_fn, \
.thaw = resume_fn, \
.poweroff = suspend_fn, \
.restore = resume_fn,
#define SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
.runtime_suspend = suspend_fn, \
.runtime_resume = resume_fn, \
.runtime_idle = idle_fn,
結合MPU3050的驅動將其完整展開可以得到:
static const struct dev_pm_ops mpu3050_pm = {
.suspend = mpu3050_suspend,
.resume = mpu3050_resume,
.freeze = mpu3050_suspend,
.thaw = mpu3050_resume,
.poweroff = mpu3050_suspend,
.restore = mpu3050_resume,
.runtime_suspend = mpu3050_suspend,
.runtime_resume = mpu3050_resume,
.runtime_idle = NULL,
}
對電源管理有興趣的可以去查閱pm.h,其中對電源管理有詳盡的說明了,這裡不做分析。可以看到,在電源管理中,有很多成員實際上是一樣的,在現實驅動中這樣的情況也經常出現,所以會有“終極電源管理大法”巨集的出現了。
of_match_table是OpenFirmware相關,在3.0(具體版本本人不清楚)kernel後對arm平臺引入了Device Tree,可通過dts配置檔案代替大量板級程式碼,有興趣可自行查閱。
上邊說過,i2c_driver的多樣化最多,從mpu3050的驅動註冊中也可以發現,其注重實現的為probe與電源管理,其中probe最為重要(好像是廢話,哪個驅動這個都是最重要的-。-)。因為主要是從驅動的角度看待I2C子系統,所以這裡不詳盡分析mpu3050的程式碼,只以其為例說明I2C驅動大體框架。在mpu3050的probe主要對此感測器進行上電、工作模式初始化、註冊INPUT子系統介面、關聯中斷處理程式(在中斷處理執行緒中上報三軸引數)等工作。
關於I2C裝置驅動的小總結
I2C裝置驅動通常只是需要掛載在I2C匯流排(即依附於I2C子系統),I2C子系統對於裝置驅動來說只是一個載體、基石。許多裝置的主要核心是建立在其他子系統上,如重力感測器、三軸感測器、觸控式螢幕等通常主要工作集中在INPUT子系統中,而相機模組、FM模組、GPS模組大多主要依附於V4L2子系統。這也能通過I2C設計理念證明,I2C的產生正是為了節省外圍電路複雜度,讓CPU使用有限的IO口掛載更多的外部模組。假設CPU的擴充套件IO口足夠多,我想I2C也沒什麼必要存在了,畢竟直接操作IO口驅動裝置比I2C來的更簡單。
I2C adapter的註冊
如上表所示,對於I2C adapter的註冊有兩種途徑:i2c_add_adapter 或i2c_add_numbered_adapter,兩者的區別是後者在註冊時已經指定了此I2C介面卡的匯流排號,而前者的匯流排號將由系統自動分配。
其各自的宣告格式為:
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
在i2c_add_numberd_adapter使用前必須制定adap->nr,如果給-1,說明還是叫系統去自動生成匯流排號的。
使用場景
之所以區分開兩種I2C adapter的註冊方式,是因為他們的使用場景有所不同。
-
i2c_add_adapter的使用經常是用來註冊那些可插拔裝置,如USB PCI裝置等。主機板上的其他模組與其沒有直接聯絡,說白了就是現有模組不在乎新加入的I2C介面卡的匯流排號是多少,因為他們不需要。反而這個可插拔裝置上的一些模組會需要其註冊成功的介面卡指標。回看一開始就分析的i2c_client,會發現不同場景的裝置與其匹配的介面卡有著這樣的對應關係:
1. i2c_register_board_info需要指定已有的busnum,而i2c_add_numbered_adapter註冊前已經指定匯流排號; 2. i2c_new_device需要指定adapter指標,而i2c_add_adapter註冊成功後恰好這個指標就有了。
想象這樣一個場景:新裝置插入後,對應的驅動程式通過i2c_add_adapter註冊自己的I2C介面卡,然後根據與小弟們的協定將其是介面卡指標存放在某處,相當於對小弟們(依附在其上的I2C裝置)說:“看見沒?你們註冊你們自己的裝置的時候就通過這個就能找到我,就能跟我混了!”然後驅動程式繼續,當執行到對自己的I2C設備註冊時候,小弟們去約定地點找老大留下的記號,發現有效資訊後,一擁而上:“看!老大在那!!!”
- i2c_add_numbered_adapter用來註冊CPU自帶的I2C介面卡,或是整合在主機板上的I2C介面卡。主機板上的其他I2C從裝置(client)在註冊時候需要這個匯流排號。
通過簡短的程式碼分析看一看他們的區別究竟如何,以及為什麼靜態註冊的i2c_client必須要在adapter註冊前(此處會精簡部分程式碼,只留重要部分):
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id); //動態獲取匯流排號
adapter->nr = id;
return i2c_register_adapter(adapter); //註冊adapter
}
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
int status;
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
status = i2c_register_adapter(adap);
return status;
}
可見,最終他們都是通過i2c_register_adapter註冊介面卡:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0;
/* Can't register until after driver model init */ //時序檢查
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
}
/* Sanity checks */
if (unlikely(adap->name[0] == '\0')) { //防禦型程式碼,檢查介面卡名稱
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) { //介面卡是否已經完成了通訊方法的實現
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", adap->name);
return -EINVAL;
}
rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev); //註冊裝置節點
if (res)
goto out_list;
/* create pre-declared device nodes */ //建立預-宣告的I2C裝置節點
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
//如果adapter的匯流排號小於動態分配的匯流排號的最小那個,說明是板級adapter。
//因為通過i2c_add_adapter加入的介面卡所分配的匯流排號一定是比__i2c_first_dynamic_bus_num大的。
...
}
對於i2c_add_numbered_adapter來說會觸發i2c_scan_static_board_info:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock); //持有讀寫鎖的讀,有使用者讀的時候不允許寫入
list_for_each_entry(devinfo, &__i2c_board_list, list) { //又見__i2c_board_list,這不是通過i2c_register_board_info組建起來的那個連結串列嗎!
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info)) //找到匯流排號與剛註冊的這個adapter相同的並通過i2c_new_device進行註冊
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock); //釋放讀寫鎖
}
而i2c_board_info成員與i2c_client的對應動作也是在i2c_new_device中進行的,這一點在上邊已經分析過了。看到這裡,對adapter與client的微妙關係應該瞭解程度就比較深了,為什麼說i2c_register_board_info與i2c_add_numbered_adapter對應而不是i2c_add_adapter也可以說得通。
那麼,最終回答開篇提出的那兩個問題:
- i2c_adapter驅動如何新增?
板級介面卡(CPU自帶、主機板整合)要通過i2c_add_numbered_adapter註冊,註冊前要指定匯流排號,從0開始。假如板級I2C介面卡註冊了3個,那麼第一個動態匯流排號一定是3,也就是說可插拔裝置所帶有的I2C介面卡需要通過i2c_add_adapter進行註冊,其匯流排號由系統指定。
- i2c_client與i2c_board_info究竟是什麼關係?
i2c_client與i2c_board_info的對應關係在i2c_new_device中有完整體現。
i2c_client->dev.platform_data = i2c_board_info->platform_data;
i2c_client->dev.archdata = i2c_board_info->archdata;
i2c_client->flags = i2c_board_info->flags;
i2c_client->addr = i2c_board_info->addr;
i2c_client->irq = i2c_board_info->irq;
TI-AM3359 I2C介面卡例項分析
I2C Spec簡述
特性:
- 相容飛利浦I2C 2.1版本規格
- 支援標準模式(100K bits/s)和快速模式(400K bits/s)
- 多路接收、傳送模式
- 支援7bit、10bit裝置地址模式
- 32位元組FIFO緩衝區
- 可程式設計時鐘發生器
- 雙DMA通道,一條中斷線
- 三個I2C模組例項I2C0\I2C1\I2C2
- 時鐘訊號能夠達到最高48MHz,來自PRCM
不支援
- SCCB協議
- 高速模式(3.4MBPS)
管腳
管腳 | 型別 | 描述 |
---|---|---|
I2Cx_SCL | I/OD | I2C 序列時鐘 |
I2Cx_SDA | I/OD | I2C 序列資料 |
I2C重置
- 通過系統重置PIRSTNA=0,所有暫存器都會被重置到上電狀態
- 軟重置,置位I2C_SYSC暫存器的SRST位。
- I2C_CON暫存器的I2C_EN位可以讓I2C模組重置。當PIRSTNA=1,I2C_EN=0會讓I2C模組功能部分重置,所有暫存器資料會被暫存(不會恢復上電狀態)
資料有效性
- SDA在SCL高電平期間必須保持穩定,而只有在SCL低電平期間資料線(SDA)才可以進行高低電平切換
開始位&停止位
當I2C模組被設定為主控制時會產生START和STOP:
- START開始位是SCL高電平期間SDA HIGH->LOW
- STOP停止位是SCL高電平期間SDA LOW->HIGH
- 在START訊號後匯流排就會被認為是busy忙狀態,而在STOP後其會被視為空閒狀態
序列資料格式
8位資料格式,每個放在SDA線上的都是1個位元組即8位長,總共有多少個位元組要傳送/接收是需要寫在DCOUNT暫存器中的。資料是高位先傳輸,如果I2C模組處於接收模式中,那麼一個應答位後跟著一個位元組的資料。I2C模組支援兩種資料格式:
- 7bit/10bit地址格式
- 帶有多個開始位的7bit/10bit地址格式
FIFO控制
I2C模組有兩個內部的32位元組FIFO,FIFO的深度可以通過控制I2C_IRQSTATUS_RAW.FIFODEPTH暫存器修改。
如何程式設計I2C
1. 使能模組前先設定
- 使分頻器產生約12MHz的I2C模組時鐘(設定I2C_PSC=x,x的值需要根據系統時鐘頻率進行計算)
- 使I2C時鐘產生100Kpbs(Standard Mode)或400Kbps(Fast Mode)(SCLL = x 及 SCLH = x,這些值也是需要根據系統時鐘頻率進行計算)
- 如果是FS模式,則配置自己的地址(I2C_OA = x)
- 重置I2C模組(I2C_CON:I2C_EN=1)
2. 初始化程式
- 設定I2C工作模式暫存器(I2C_CON)
- 若想用傳輸資料中斷則使能中斷掩碼(I2C_IRQENABLE_SET)
- 如果在FS模式中,使用DMA傳輸資料的話,使能DMA(I2C_BUF及I2C_DMA/RX/TX/ENABLE_SET)且配置DMA控制器
3. 設定從地址和資料計數器
在主動模式中,設定從地址(I2C_SA = x),設定傳輸需要的位元組數(I2C_CNT = x)
4. 初始化一次傳輸
在FS模式中。查詢一下I2C狀態暫存器(I2C_IRQSTATUS_RAW)中匯流排狀態(BB),如果是0則說明匯流排不是忙狀態,設定START/STOP(I2C_CON:STT/STP)初始化一次傳輸。
5. 接收資料
檢查I2C狀態暫存器(I2C_IRQSTATUS_RAW)中代表接收資料是否準備好的中斷位(RRDY),用這個RRDY中斷(I2C_IRQENABLE_SET.RRDY_IE置位)或使用DMA_RX(I2C_BUF.RDMA_EN置位且I2C_DMAR