Linux I2C驅動體系結構主要由3部分組成,即I2C設備驅動,I2C核心層、I2C總線驅動。設備驅動層主要是針對不同的I2C硬件從設備編寫的驅動程序,I2C總線驅動是對I2C硬件體系結構中適配器端的實現,適配器可以理解為軟件上抽象出來的i2c接口,這個接口可以對應I2C總線控制器接口,也可以對應用用GPIO模擬的I2C控制器接口。I2C核心層是I2C總線驅動和I2C設備驅動的中間樞紐,它以通用的、與平台無關的接口實現了I2C中設備與適配器的溝通。I2C總線驅動填充i2c_adapter和i2c_algorithm結構體,I2C設備驅動填充i2c_driver和i2c_client結構體。驅動工程師要做的就是總線驅動和設備驅動的編寫。
I2C設備驅動
前面說了設備驅動主要是填充i2c_driver和i2c_client兩個結構體,了解Linux總線,設備,驅動模型的話就知道,注冊i2c_driver(i2c_client)結構體時,總線會從總線上尋找與其名字匹配的i2c_client(i2c_driver),並調用i2c_driver的probe函數,反之有一個被刪除時,會調用i2c_driver的remove函數。我們可以在i2c_driver的probe函數里做想做的事情,比如創建設備節點等,在remove函數里在一些清除工作。那么i2c_driver該如何定義和注冊呢,參考4.43內核其它的i2c設備驅動文件,我們先定義好i2c_driver結構體,並填充好幾個關鍵成員driver{.name},probe,remove,id_table后,用module_i2c_driver()這個宏來注冊i2c_driver,這個宏在/include/linux/i2c.h定義,注釋上說用它來注冊一個i2c_driver,並通過調用它代替module_init() and module_exit()。當然你也可以在入口函數里用i2c_add_driver,出口函數里用i2c_del_driver來注冊和刪除i2c_driver。這樣,i2c_driver結構體就算完成了。
接下來我們來注冊一個i2c_client結構體,內核文檔Documentation/i2c/instantiating-devices中介紹了如何實例化一個i2c設備,總共有以下幾種方法:
Method 1a: Declare the I2C devices by bus number。通過總線號來注冊I2C設備,如下所示:
1 static struct i2c_board_info h4_i2c_board_info[] __initdata = { 2 { 3 I2C_BOARD_INFO("isp1301_omap", 0x2d), 4 .irq = OMAP_GPIO_IRQ(125), 5 }, 6 { /* EEPROM on mainboard */ 7 I2C_BOARD_INFO("24c01", 0x52), 8 .platform_data = &m24c01, 9 }, 10 { /* EEPROM on cpu card */ 11 I2C_BOARD_INFO("24c01", 0x57), 12 .platform_data = &m24c01, 13 }, 14 }; 15 16 static void __init omap_h4_init(void) 17 { 18 (...) 19 i2c_register_board_info(1, h4_i2c_board_info, 20 ARRAY_SIZE(h4_i2c_board_info)); 21 (...) 22 }
i2c_register_board_info的傳統用法是在內核初始化時,在i2c_adapter注冊之前。查看i2c_adapter的注冊代碼可以發現,i2c_adapter_register里會調用i2c_scan_static_board_info掃描board_info的鏈表,為每一個注冊的信息調用i2c_new_device函數生成i2c_client,這樣在i2c_driver注冊的時候,設備和驅動就能匹配並調用probe.
如果想在adapter注冊之后調用i2c_register_board_info,注冊的信息沒有機會生成i2c_client,從而無法與i2c_driver匹配。這時還是要使用i2c_new_device或i2c_new_probed_device。
Method 1b: Declare the I2C devices via devicetree,通過設備樹來聲明I2C設備,如下所示:
1 i2c1: i2c@400a0000 { 2 /* ... master properties skipped ... */ 3 clock-frequency = <100000>; 4 5 flash@50 { 6 compatible = "atmel,24c256"; 7 reg = <0x50>; 8 }; 9 10 pca9532: gpio@60 { 11 compatible = "nxp,pca9532"; 12 gpio-controller; 13 #gpio-cells = <2>; 14 reg = <0x60>; 15 }; 16 };
Method 1c: Declare the I2C devices via ACPI。通過ACPI來聲明I2C設備,詳見Documentation/acpi/enumeration.txt。
Method 2: Instantiate the devices explicitly(明確地)。主要有如下兩種方法:
1 static struct i2c_board_info sfe4001_hwmon_info = { 2 I2C_BOARD_INFO("max6647", 0x4e), 3 }; 4 5 int sfe4001_init(struct efx_nic *efx) 6 { 7 (...) 8 efx->board_info.hwmon_client = 9 i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info); 10 11 (...) 12 }
1 static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END }; 2 3 static int usb_hcd_nxp_probe(struct platform_device *pdev) 4 { 5 (...) 6 struct i2c_adapter *i2c_adap; 7 struct i2c_board_info i2c_info; 8 9 (...) 10 i2c_adap = i2c_get_adapter(2); 11 memset(&i2c_info, 0, sizeof(struct i2c_board_info)); 12 strlcpy(i2c_info.type, "isp1301_nxp", I2C_NAME_SIZE); 13 isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, 14 normal_i2c, NULL); 15 i2c_put_adapter(i2c_adap); 16 (...) 17 }
這類方法適用於事先不知道I2C總線號的情況,前者用i2c_new_device來聲明設備獲得一個i2c_client結構體,后者通過i2c_new_probed_device來實現,兩者的區別在於i2c_new_probed_device會依次探測normal_i2c數組中的地址,查看該地址的設備是否存在,如果存在,就實例化,而i2c_new_device不會管這么多,它會直接實例化某個固定地址的設備,不管它是否存在。兩者都是用i2c_unregister_device()來刪除實例化的設備。
Method 3: Probe(探測) an I2C bus for certain devices。某些時刻,我們不知道I2C設備的一些具體信息,以至於不能用上面的方法,當我們裝載這類設備的驅動時,I2C核心層會為我們探測這些設備並自動實例化。這要求驅動必須有detect函數和address_list成員,address_list為要探測的地址序列。而且總線必須支持該設備,並同意檢測。詳見drivers/hwmon/lm90.c。一般來說這種方法不推薦,更推薦方法一和二。
Method 4: Instantiate from user-space:我們可以從用戶空間實例化一個I2C設備和刪除一個設備。示例如下:
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device //實例化一個名為eeprom地址為0x50的設備
這種方法只應該在內核中的設備聲明無法使用時才用到,但它也有一些優勢,你不用去重新載入驅動去更改設備的某些設置,你可以在驅動裝載前就實例化它,不用管它需要哪個驅動。
以上就是實例化設備的幾種方法,這樣,我們就把i2c_client和i2c_driver填充完畢了,設備驅動的主要任務也就完成了,具體其它的工作不同的設備各有不同,這里就不介紹了。
通常來說,I2C設備都由內核驅動來控制,但我們也可以從用戶空間使用一個適配器來使用所有設備,這通過/dev接口來實現,我們需要裝載i2c-dev模塊,我們可以把它理解為一個內核幫我們實現的一個通用I2C設備驅動。如果想要在一個C應用程序里使用這個adapter,需要#include <linux/i2c-dev.h>,這個頭文件在i2c-tool里有,i2c-tool是一款I2C調試工具,將這個頭文件拷入內核的/include/linux/目錄下即可。具體如何在C應用程序里使用這個adapter詳見/Documentation/i2c/dev-interface文檔。