實現 I2C 裝置驅動程式¶
這是一份小指南,適用於那些希望使用 Linux 作為協議主機/主裝置(而不是從裝置)為 I2C 或 SMBus 裝置編寫核心驅動程式的人。
要設定驅動程式,您需要執行幾個操作。 有些是可選的,有些事情可以略有或完全不同地完成。 將其用作指南,而不是規則手冊!
一般說明¶
儘量保持核心名稱空間儘可能乾淨。 最好的方法是對所有全域性符號使用唯一字首。 這對於匯出的符號尤其重要,但對於未匯出的符號也是一個好主意。 在本教程中,我們將使用字首 foo_。
驅動程式結構¶
通常,您將實現單個驅動程式結構,並從中例項化所有客戶端。 請記住,驅動程式結構包含通用訪問例程,並且應進行零初始化,除非您提供資料的欄位。 客戶端結構包含裝置特定的資訊,如驅動程式模型裝置節點及其 I2C 地址。
static const struct i2c_device_id foo_idtable[] = {
{ "foo", my_id_for_foo },
{ "bar", my_id_for_bar },
{ }
};
MODULE_DEVICE_TABLE(i2c, foo_idtable);
static struct i2c_driver foo_driver = {
.driver = {
.name = "foo",
.pm = &foo_pm_ops, /* optional */
},
.id_table = foo_idtable,
.probe = foo_probe,
.remove = foo_remove,
.shutdown = foo_shutdown, /* optional */
.command = foo_command, /* optional, deprecated */
}
name 欄位是驅動程式名稱,不能包含空格。 它應該與模組名稱匹配(如果驅動程式可以編譯為模組),儘管您可以使用 MODULE_ALIAS(在本例中傳遞“foo”)為模組新增另一個名稱。 如果驅動程式名稱與模組名稱不匹配,則不會自動載入該模組(熱插拔/冷插拔)。
所有其他欄位都是用於回撥函式,這將在下面進行解釋。
額外的客戶端資料¶
每個客戶端結構都有一個特殊的 data 欄位,可以指向任何結構。 您應該使用它來儲存特定於裝置的資料。
/* store the value */
void i2c_set_clientdata(struct i2c_client *client, void *data);
/* retrieve the value */
void *i2c_get_clientdata(const struct i2c_client *client);
請注意,從核心 2.6.34 開始,您不必再在 remove() 或 probe() 失敗時將 data 欄位設定為 NULL。 i2c-core 會在這些情況下自動執行此操作。 這些也是核心將觸及此欄位的唯一時間。
訪問客戶端¶
假設我們有一個有效的客戶端結構。 在某個時候,我們需要從客戶端收集資訊,或將新資訊寫入客戶端。
我發現為此定義 foo_read 和 foo_write 函式很有用。 對於某些情況,直接呼叫 I2C 函式會更容易,但許多晶片都有某種可以輕鬆封裝的暫存器值概念。
以下函式是簡單的示例,不應逐字複製
int foo_read_value(struct i2c_client *client, u8 reg)
{
if (reg < 0x10) /* byte-sized register */
return i2c_smbus_read_byte_data(client, reg);
else /* word-sized register */
return i2c_smbus_read_word_data(client, reg);
}
int foo_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if (reg == 0x10) /* Impossible to write - driver error! */
return -EINVAL;
else if (reg < 0x10) /* byte-sized register */
return i2c_smbus_write_byte_data(client, reg, value);
else /* word-sized register */
return i2c_smbus_write_word_data(client, reg, value);
}
探測和附加¶
Linux I2C 堆疊最初是為支援訪問 PC 主機板上的硬體監控晶片而編寫的,因此過去會嵌入一些更適合 SMBus(和 PC)而不是 I2C 的假設。 其中一個假設是,大多數介面卡和裝置驅動程式都支援 SMBUS_QUICK 協議來探測裝置是否存在。 另一個假設是,可以使用這些探測原語充分配置裝置及其驅動程式。
隨著 Linux 及其 I2C 堆疊在嵌入式系統和 DVB 介面卡等複雜元件中得到更廣泛的使用,這些假設變得更加成問題。 發出中斷的 I2C 裝置驅動程式需要更多(和不同)的配置資訊,處理無法透過協議探測區分的晶片變體的驅動程式也是如此,或者需要一些板特定的資訊才能正確操作。
裝置/驅動程式繫結¶
系統基礎架構,通常是板特定的初始化程式碼或啟動韌體,報告存在哪些 I2C 裝置。 例如,核心或啟動載入程式中可能有一個表,用於識別 I2C 裝置並將它們連結到關於 IRQ 和其他接線人為因素、晶片型別等的板特定配置資訊。 這可以用於為每個 I2C 裝置建立 i2c_client 物件。
使用此繫結模型的 I2C 裝置驅動程式的工作方式與 Linux 中的任何其他型別的驅動程式一樣:它們提供一個 probe() 方法來繫結到這些裝置,並提供一個 remove() 方法來取消繫結。
static int foo_probe(struct i2c_client *client);
static void foo_remove(struct i2c_client *client);
請記住,i2c_driver 不會建立這些客戶端控制代碼。 該控制代碼可以在 foo_probe() 期間使用。 如果 foo_probe() 報告成功(零而不是負狀態程式碼),它可以儲存該控制代碼並使用它直到 foo_remove() 返回。 大多數 Linux 驅動程式都使用此繫結模型。
當 id_table name 欄位中的條目與裝置的名稱匹配時,將呼叫 probe 函式。 如果 probe 函式需要該條目,它可以檢索它,使用
const struct i2c_device_id *id = i2c_match_id(foo_idtable, client);
裝置建立¶
如果您確定 I2C 裝置已連線到給定的 I2C 匯流排,則可以透過簡單地使用裝置地址和驅動程式名稱填充 i2c_board_info 結構並呼叫 i2c_new_client_device() 來例項化該裝置。 這將建立裝置,然後驅動程式核心將負責查詢正確的驅動程式並呼叫其 probe() 方法。 如果驅動程式支援不同的裝置型別,您可以使用 type 欄位指定所需的型別。 您還可以根據需要指定 IRQ 和平臺數據。
有時您知道裝置已連線到給定的 I2C 匯流排,但您不知道它使用的確切地址。 例如,這發生在電視介面卡上,其中同一驅動程式支援數十種略有不同的型號,並且 I2C 裝置地址因型號而異。 在這種情況下,您可以使用 i2c_new_scanned_device() 變體,它與 i2c_new_client_device() 類似,只不過它需要一個額外的可能 I2C 地址列表來探測。 為列表中第一個響應地址建立裝置。 如果您期望地址範圍內存在多個裝置,只需多次呼叫 i2c_new_scanned_device() 即可。
對 i2c_new_client_device() 或 i2c_new_scanned_device() 的呼叫通常發生在 I2C 匯流排驅動程式中。 您可能希望儲存返回的 i2c_client 引用以供以後使用。
裝置檢測¶
裝置檢測機制存在許多缺點。 您需要一些可靠的方法來識別受支援的裝置(通常使用特定於裝置的專用識別暫存器),否則可能會發生誤檢測,並且事情很快就會出錯。 請記住,I2C 協議不包含任何檢測晶片是否以給定地址存在的標準方法,更不用說識別裝置的標準方法了。 更糟糕的是,缺少與匯流排傳輸相關的語義,這意味著同一個傳輸可以被一個晶片視為讀取操作,而被另一個晶片視為寫入操作。 由於這些原因,裝置檢測被認為是遺留機制,不應在新程式碼中使用。
裝置刪除¶
可以使用 i2c_new_client_device() 或 i2c_new_scanned_device() 建立的每個 I2C 裝置可以透過呼叫 i2c_unregister_device() 來登出。 如果您沒有顯式呼叫它,則會在刪除底層 I2C 匯流排本身之前自動呼叫它,因為裝置無法在其父裝置在裝置驅動程式模型中存在的情況下繼續存在。
初始化驅動程式¶
當核心啟動時,或者當您的 foo 驅動程式模組插入時,您必須進行一些初始化。 幸運的是,通常只需註冊驅動程式模組就足夠了。
static int __init foo_init(void)
{
return i2c_add_driver(&foo_driver);
}
module_init(foo_init);
static void __exit foo_cleanup(void)
{
i2c_del_driver(&foo_driver);
}
module_exit(foo_cleanup);
The module_i2c_driver() macro can be used to reduce above code.
module_i2c_driver(foo_driver);
請注意,某些函式標有 __init。 這些函式可以在核心啟動(或模組載入)完成後刪除。 同樣,標有 __exit 的函式在程式碼構建到核心中時會被編譯器丟棄,因為它們永遠不會被呼叫。
驅動程式資訊¶
/* Substitute your own name and email address */
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>"
MODULE_DESCRIPTION("Driver for Barf Inc. Foo I2C devices");
/* a few non-GPL license types are also allowed */
MODULE_LICENSE("GPL");
電源管理¶
如果您的 I2C 裝置在進入系統低功耗狀態時需要特殊處理 - 例如將收發器置於低功耗模式或啟用系統喚醒機制 - 請透過為驅動程式的 dev_pm_ops 實現適當的回撥(如 suspend 和 resume)來實現。
這些是標準驅動程式模型呼叫,它們的工作方式與任何其他驅動程式堆疊一樣。 這些呼叫可以休眠,並且可以使用 I2C 訊息傳遞到正在掛起或恢復的裝置(因為當發出這些呼叫時,它們的父 I2C 介面卡處於活動狀態,並且 IRQ 仍然啟用)。
系統關機¶
如果您的 I2C 裝置在系統關閉或重新啟動(包括 kexec)時需要特殊處理 - 例如關閉某些東西 - 請使用 shutdown() 方法。
同樣,這是一個標準驅動程式模型呼叫,其工作方式與任何其他驅動程式堆疊一樣:這些呼叫可以休眠,並且可以使用 I2C 訊息傳遞。
命令函式¶
支援通用的 ioctl 類函式回撥。 您很少需要它,並且它的使用已被棄用,因此較新的設計不應使用它。
傳送和接收¶
如果您想與您的裝置通訊,有幾個函式可以執行此操作。 您可以在 <linux/i2c.h> 中找到所有這些函式。
如果您可以在普通的 I2C 通訊和 SMBus 級別通訊之間進行選擇,請使用後者。 所有介面卡都理解 SMBus 級別命令,但只有部分介面卡理解普通的 I2C!
普通的 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);
這些例程從客戶端讀取/向客戶端寫入一些位元組。 客戶端包含 I2C 地址,因此您不必包含它。 第二個引數包含要讀取/寫入的位元組,第三個引數是要讀取/寫入的位元組數(必須小於緩衝區的長度,也應小於 64k,因為 msg.len 是 u16)。 返回的是實際讀取/寫入的位元組數。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg,
int num);
這會發送一系列訊息。 每個訊息可以是讀取或寫入,並且它們可以以任何方式混合。 事務是組合的:在事務之間不發出停止條件。 i2c_msg 結構包含每個訊息的客戶端地址、訊息的位元組數和訊息資料本身。
您可以閱讀檔案 I2C 協議 以獲取有關實際 I2C 協議的更多資訊。
SMBus 通訊¶
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
unsigned short flags, char read_write, u8 command,
int size, union i2c_smbus_data *data);
這是通用的 SMBus 函式。 以下所有函式都是用它實現的。 永遠不要直接使用此函式!
s32 i2c_smbus_read_byte(struct i2c_client *client);
s32 i2c_smbus_write_byte(struct i2c_client *client, u8 value);
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(struct i2c_client *client,
u8 command, u8 value);
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(struct i2c_client *client,
u8 command, u16 value);
s32 i2c_smbus_read_block_data(struct i2c_client *client,
u8 command, u8 *values);
s32 i2c_smbus_write_block_data(struct i2c_client *client,
u8 command, u8 length, const u8 *values);
s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client,
u8 command, u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(struct i2c_client *client,
u8 command, u8 length,
const u8 *values);
這些已從 i2c-core 中刪除,因為它們沒有使用者,但如果需要,以後可以重新新增
s32 i2c_smbus_write_quick(struct i2c_client *client, u8 value);
s32 i2c_smbus_process_call(struct i2c_client *client,
u8 command, u16 value);
s32 i2c_smbus_block_process_call(struct i2c_client *client,
u8 command, u8 length, u8 *values);
所有這些事務在失敗時都會返回一個負的 errno 值。 “write”事務在成功時返回 0; “read”事務返回讀取的值,但塊事務除外,塊事務返回讀取的值的數量。 塊緩衝區不必超過 32 位元組。
您可以閱讀檔案 SMBus 協議 以獲取有關實際 SMBus 協議的更多資訊。
通用例程¶
下面列出了所有通用例程,這些例程之前未提及
/* Return the adapter number for a specific adapter */
int i2c_adapter_id(struct i2c_adapter *adap);