1. 程式人生 > >linux3.5下I2C驅動的

linux3.5下I2C驅動的

               

知識背景:

1. I2C協議

2. 4412處理器I2C介面說明

3. bus-dev-drv模型(詳見文章-Linux下驅動:分層、分離機制學習筆記)

4. linux核心下驅動設計基本知識

一、驅動框架

      以4412+linux3.5平臺為例,說明Linux下I2C裝置驅動程式。

      如果一條I2C總線上只連線一個I2C裝置,那麼只需要以字元型裝置驅動框架來寫驅動即可(填充file_opreoration結構體中的各個成員),那是典型的字元型裝置驅動程式。

      實際上,一款處理器可能有多條I2C匯流排,多個I2C控制器,每條總線上可能掛接多個裝置,按照典型的字元型驅動框架寫驅動就要為每個這樣的裝置寫一個驅動程式,而且要做很多重複性的工作。

      所以,有很多東西可以抽象的就抽象出來,實現一種框架,這種框架模型還是bus-dev-drv模型。4412處理器有8條可用I2C匯流排,每條總線上掛接的每個I2C裝置都明白它們跟4412通訊資料的含義。4412訪問I2C裝置時,都需要發出Start訊號,都需要發出裝置地址等等,這些共性的東西就可以抽象出來用一些函式實現,這些函式就是在另外一層即核心層驅動程式。

      核心層驅動程式提供統一的I2C裝置操作函式,比如讀寫I2C裝置的操作函式;還有裝置驅動層(file_opreoration結構體相關);另外一層就是適配層,這一層是處理器的每個I2C介面卡(也叫控制器)的驅動,適配層驅動程式提供統一的處理器I2C介面硬體操作函式。

       應用程式需要操作I2C裝置時,根據裝置節點呼叫讀寫函式(系統呼叫),然後就會執行裝置驅動層的相應讀寫函式(它們是file_opreoration結構體的成員),這些讀寫函式裡會呼叫核心層提供的統一的I2C裝置讀寫操作函式,這些函式最終會通過介面卡(I2C控制器)給連線在I2C總線上的I2C裝置傳送相應讀寫命令。介面卡那一層驅動程式就是些I2C控制器的硬體操作,它是根據I2C協議來的(這裡根據的I2C協議往往是硬體已經做好了的,軟體只需要配置CPU的I2C控制器的某個暫存器即可實現I2C協議),是最底層,當I2C控制器的某個暫存器被控制後,I2C控制器就會自動地往I2C線上根據I2C協議發出相應訊號與2IC裝置通訊。

       由此可見,寫裝置驅動程式即實現裝置驅動層時,只要弄清楚bus-dev-drv驅動模型,(呼叫了probe函式後)剩下的就是典型的字元型裝置驅動那一套了(構建file_opreoration結構體、分配主次裝置號、建立裝置節點),file_opreoration的讀寫函式呼叫的方法也由核心層提供。圖一。

      上述三層是通過匯流排-裝置-驅動模型融合到一起的。Linux核心中構建了許多匯流排,但並不是都是真實的,比如平臺匯流排就是虛擬的,平臺匯流排中也有裝置連結串列,驅動連結串列。

      針對I2C匯流排,也有一個I2C匯流排的結構即i2c_bus_type結構,此結構裡面也有裝置連結串列和也有驅動連結串列。裝置連結串列裡存放i2c_client的結構體,這些結構體是註冊i2c_client時加入的,不但要加入這些結構體,還會在匯流排的驅動連結串列中一個一個地比較drv即i2c_driver來判斷是否有匹配的,如果有將呼叫drv裡面的probe函式,匹配函式由匯流排提供;驅動連結串列裡存放i2c_driver結構體,這些結構體是註冊i2c_driver時加入的,不但要加入這些結構體,還會在匯流排的裝置連結串列中一個一個地比較dev即比較i2c_client來判斷是否有匹配的,如果匹配將呼叫drv裡面的probe函式。

     上述的匹配函式就是i2c_bus_type結構裡面的i2c_device_match函式。i2c_device_match函式通過id_table(i2c_driver結構的成員,它表示能支援哪些裝置)來比較dev與drv是否匹配,具體方法是用id_table的name去比較,name分別是i2c_client結構和i2c_driver結構的成員。如果名字相同,就表示此驅動i2c_driver能支援這個裝置i2c_client。

      總的說來,I2C基於bus-dev-drv模型的構建過程如下:

(1)左邊註冊一個裝置,i2c_client

(2)右邊註冊一個驅動,i2c_driver

(3)比較它們的名字,如果相同,則呼叫driver的probe函式。

(4)probe函式裡面做的事情由使用者決定(比如註冊、構建裝置節點等,這些屬於裝置驅動)。

二、設定和註冊i2c_client結構體(EEPROM為例)

      在Linux核心文件(/Documentation/i2c/instantiating-devices)中,介紹了註冊即構造一個i2c_client的4種方法,並且建議使用如下前三種方法,後一種較複雜,萬不得已才用。

1.  通過匯流排號宣告一個I2C裝置

      通過匯流排號宣告一個I2C裝置即構造一個i2c_board_info結構體,它裡面有名字和地址,名字可以用來找到drv結構,地址可以用來訪問哪個I2C裝置,還可以放其他資訊,這些資訊會被將來的probe函式用到,也就是說在probe函式中要使用到哪些資訊,這裡面就可以增加哪些資訊。然後使用i2c_register_board_info函式註冊這個結構體,此函式的第一個引數即匯流排號,表示此裝置屬於哪條匯流排(這就是標題通過匯流排號宣告一個i2c裝置的含義),在此函式中會把此結構體放入連結串列__i2c_board_list

      然後在i2c_scan_static_board_info函式中使用到__i2c_board_list連結串列,即呼叫i2c_new_device函式把連結串列中的每個成員構造成一個i2c_client,並放入bus-dev-drv模型中匯流排中裝置連結串列中去。在i2c_new_device函式中,分配一個i2c_client結構體後,設定它,並呼叫device_register函式註冊,此函式最終會呼叫前面文章中提到device_add函式。i2c_scan_static_board_info函式是被i2c_register_adapter函式呼叫的,所以這裡總的過程為i2c_register_adapter  > i2c_scan_static_board_info > i2c_new_device。

     ft5x06_ts型別的I2C觸控式螢幕驅動中就是使用的這種方法。在核心檔案arch/arm/mach-exynos/mach-tiny4412.c中,對應觸控式螢幕的i2c_register_board_info函式呼叫過程如下:

MACHINE_START(TINY4412, "TINY4412")     .xxx     .init_machine    = smdk4x12_machine_init,     .xxx MACHINE_END                                  ↓

         呼叫smdk4x12_machine_init函式

                                 ↓

呼叫i2c_register_board_info(1, smdk4x12_i2c_devs1,ARRAY_SIZE(smdk4x12_i2c_devs1));

       可見,上述i2c_register_board_info函式的第一個引數為1,表示ft5x06_ts型別的I2C觸控式螢幕掛接在處理器的I2C1那條總線上。

這種方法使用限制:在註冊I2C介面卡之前註冊i2c_board_info結構體,即必須在 i2c_register_adapter 之前 i2c_register_board_info,所以不適合動態載入insmod。

2.  直接建立裝置(直接呼叫i2c_new_device、i2c_new_probed_device)

(1)i2c_new_device方法

      第一種方法顯得有些麻煩,這裡就直接呼叫 i2c_new_device或i2c_new_probed_device函式實現。

      i2c_new_device函式總共有兩個引數,第一個為要指定的介面卡i2c_adapter(一個用來標識物理I2C匯流排結構,即用哪個I2C控制器發出I2C訊號,某些CPU有多個I2C介面卡),即要把i2c裝置跟哪個介面卡相連,這樣以後在訪問I2C裝置時,就知道使用哪個介面卡的操作函數了。第二個引數是用來描述I2C裝置的相關資訊的,即i2c_board_info結構體。所以這種方法需要在dev程式中定義i2c_adapter和i2c_board_info結構,i2c_board_info結構可以直接在dev程式中定義,i2c_adapter建立的程式碼如下:

    struct   i2c_adapter *i2c_adap;     i2c_adap = i2c_get_adapter(0);     at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);     i2c_put_adapter(i2c_adap);

     先定義一個i2c_adapter結構指標存放i2c_get_adapter(0)的返回值,此函式的引數為0,表示第0條匯流排,i2c_new_device呼叫完後要呼叫  i2c_put_adapter函式。通過這樣的方式,在第0條匯流排下建立了一個新裝置,以後就可以使用i2c_adap這個i2c_adapter的操作函式發出I2C的訊號了,比如起始訊號、停止訊號等。實驗程式碼如下:

at24cxx_dev.c :

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. //0x50表示I2C裝置的地址,一般在 I2C裝置晶片手冊可以查到
  9. static struct i2c_board_info at24cxx_info = { 
  10.  I2C_BOARD_INFO("at24c08", 0x50),//這個名字要和drv程式中的id_table中名字要一樣
  11. };
  12. static struct i2c_client *at24cxx_client;
  13. static int at24cxx_dev_init(void)
  14. {
  15.  struct i2c_adapter *i2c_adap;
  16.  i2c_adap = i2c_get_adapter(0);//這裡要實驗的EEPROM是掛接在第0條I2C總線上的,所以這裡的引數是0
  17.  at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
  18.  i2c_put_adapter(i2c_adap);
  19.  return 0;
  20. }
  21. static void at24cxx_dev_exit(void)
  22. {
  23.  i2c_unregister_device(at24cxx_client);
  24. }
  25. module_init(at24cxx_dev_init);
  26. module_exit(at24cxx_dev_exit);
  27. MODULE_LICENSE("GPL");
at24cxx_drv.c :
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. static int __devinit at24cxx_probe(struct i2c_client *client,
  9.       const struct i2c_device_id *id)
  10. {
  11.  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  12.  return 0;
  13. }
  14. static int __devexit at24cxx_remove(struct i2c_client *client)
  15. {
  16.  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  17.  return 0;
  18. }
  19. static const struct i2c_device_id at24cxx_id_table[] = {
  20.  { "at24c08", 0 },//用到哪些就宣告哪些內容,比如driver_data用不到,所以這裡就寫0
  21.  {}
  22. };
  23. /* 1. 分配/設定i2c_driver */
  24. static struct i2c_driver at24cxx_driver = {
  25.  .driver = {
  26.   .name = "100ask",//在這裡,這個名字並不重要,重要的是id_table裡面的名字,所以這裡可以隨便起
  27.   .owner = THIS_MODULE,
  28.  },
  29.  .probe  = at24cxx_probe,
  30.  .remove  = __devexit_p(at24cxx_remove),
  31.  .id_table = at24cxx_id_table,
  32. };
  33. static int at24cxx_drv_init(void)
  34. {
  35.  /* 2. 註冊i2c_driver */
  36.  i2c_add_driver(&at24cxx_driver);//實際使用時一定要判斷返回值
  37.  return 0;
  38. }
  39. static void at24cxx_drv_exit(void)
  40. {
  41.  i2c_del_driver(&at24cxx_driver);
  42. }
  43. module_init(at24cxx_drv_init);
  44. module_exit(at24cxx_drv_exit);
  45. MODULE_LICENSE("GPL");<span style="display: none; width: 0px; height: 0px;" id="transmark"></span>

Makefile :

  1. KERN_DIR = /home/samba/linuxKernel_ext4Fs_src/linux-3.5-2015-8
  2. all:
  3.  make -C $(KERN_DIR) M=`pwd` modules
  4. clean:
  5.  make -C $(KERN_DIR) M=`pwd` modules clean
  6.  rm -rf modules.order
  7. obj-m += at24cxx_dev.o
  8. obj-m += at24cxx_drv.o

      分別載入at24cxx_dev.ko和at24cxx_drv.ko,at24cxx_probe函式會被呼叫,隨意修改i2c_board_info結構裡面的地址,at24cxx_probe函式照樣被呼叫。所以這種方法不會去真正地驗證連線的I2C裝置的地址是否為i2c_board_info結構裡面的地址。

(2) i2c_new_probed_device方法

     i2c_new_device : 認為裝置肯定存在,實驗時可以用這種方法改掉I2C裝置的地址,改成任意值都是可以的。     i2c_new_probed_device :對於"已經識別出來的裝置"(probed_device),才會建立("new")     i2c_new_probed_device函式主要做如下三件事:                probe(adap, addr_list[i])   /* 如果i2c_new_probed_device最後個引數中沒有指定probe函式,將使用預設probe函式。不管哪個,它都要確定裝置是否真實存在 */               info->addr = addr_list[i];   /* 如果在addr_list中找到了一個地址和現實中連線在I2C總線上的裝置匹配,將這個地址放入i2c_board_info結構,並傳給i2c_new_device */                i2c_new_device(adap, info);     為了實驗,分別編譯如下程式碼,然後分別載入at24cxx_dev.ko和at24cxx_drv.ko,如果在addr_list陣列中有一個地址是I2C總線上裝置的地址,那麼在載入at24cxx_dev.ko驅動模組時,能載入成功,並且載入at24cxx_drv.ko模組後,將呼叫drv的probe函式。如果沒有那個地址,那麼在載入at24cxx_dev.ko驅動模組時會失敗,提示如下資訊:

insmod: can't insert 'at24cxx_dev.ko': No such device

     這就是直接使用i2c_new_device和使用i2c_new_probed_device建立i2c_client的區別。

at24cxx_dev.c :

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. static struct i2c_client *at24cxx_client;
  9. //如果掛接在I2C總線上的i2c裝置的地址在此數組裡面都找不到,將不能載入此驅動模組
  10. static const unsigned short addr_list[] = { 0x60,0x50,I2C_CLIENT_END };
  11. static int at24cxx_dev_init(void)
  12. {
  13.  struct i2c_adapter *i2c_adap;
  14.  struct i2c_board_info at24cxx_info;
  15.  memset(&at24cxx_info, 0, sizeof(struct i2c_board_info)); 
  16.  strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
  17.  i2c_adap = i2c_get_adapter(0);
  18.  at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
  19.  i2c_put_adapter(i2c_adap);
  20.  if (at24cxx_client)
  21.   return 0;
  22.  else
  23.   return -ENODEV;
  24. }
  25. static void at24cxx_dev_exit(void)
  26. {
  27.  i2c_unregister_device(at24cxx_client);
  28. }
  29. module_init(at24cxx_dev_init);
  30. module_exit(at24cxx_dev_exit);
  31. MODULE_LICENSE("GPL");
at24cxx_drv.c :
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. static int __devinit at24cxx_probe(struct i2c_client *client,
  9.                        const struct i2c_device_id *id)
  10. {
  11.  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  12.  return 0;
  13. }
  14. static int __devexit at24cxx_remove(struct i2c_client *client)
  15. {
  16.  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  17.  return 0;
  18. }
  19. static const struct i2c_device_id at24cxx_id_table[] = {
  20.  { "at24c08", 0 },//用到哪些就宣告哪些內容,比如driver_data用不到,所以這裡就寫0
  21.  {}
  22. };
  23. /* 1. 分配/設定i2c_driver */
  24. static struct i2c_driver at24cxx_driver = {
  25.  .driver = {
  26.   .name = "100ask",//在這裡,這個名字並不重要,重要的是id_table裡面的名字,所以這裡可以隨便起
  27.   .owner = THIS_MODULE,
  28.  },
  29.  .probe  = at24cxx_probe,
  30.  .remove  = __devexit_p(at24cxx_remove),
  31.  .id_table = at24cxx_id_table,
  32. };
  33. static int at24cxx_drv_init(void)
  34. {
  35.  /* 2. 註冊i2c_driver */
  36.  i2c_add_driver(&at24cxx_driver);//一定要判斷返回值
  37.  return 0;
  38. }
  39. static void at24cxx_drv_exit(void)
  40. {
  41.  i2c_del_driver(&at24cxx_driver);
  42. }
  43. module_init(at24cxx_drv_init);
  44. module_exit(at24cxx_drv_exit);
  45. MODULE_LICENSE("GPL");

3.  從使用者空間建立裝置(詳細閱讀/Documentation/i2c/instantiating-devices文件)

      執行命令cd /sys/class/i2c-adapter/,可以看到內容i2c-0  i2c-1  i2c-2  i2c-3  i2c-7  i2c-8,說明有多款介面卡,即多個I2C控制器,即多條I2C匯流排。其中EEPROM是掛接在I2C-0下面的(看板子原理圖)。      < 做下面實驗需要把核心中靜態編譯進的drv驅動給去掉,然後載入自己的drv驅動>

建立裝置        echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device,導致i2c_new_device被呼叫,最後drv裡的probe函式就不會被呼叫。如果把地址改為0x51,那麼也會在bus的                                                                                                            dev連結串列中增加一個dev結構,所以這種方法也是不會判斷地址是否正確。 刪除裝置        echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device,導致i2c_unregister_device。       

4.  註冊設定i2c_client的第四種方法(此方法交複雜,前三種都不行時才用)

     上述三種方法都是知道I2C裝置屬於哪個介面卡,即知道連在了哪條I2C總線上,如果不知道屬於哪個介面卡的情況下(4412有多個I2C介面卡)就需要用本方法,本方法可以參考例子/drivers/hwmon/lm90.c。

     如果事先並不知道這個I2C裝置在哪個介面卡上,怎麼辦?去class表示的所有的介面卡上查詢。      有些I2C裝置的地址是一樣,怎麼繼續區分它是哪一款?用detect函式。

     此方法過程:         由於事先並不知道I2C裝置在哪個介面卡上,所以去"class表示的那一類"I2C介面卡上找,用"detect函式"來確定能否找到"address_list裡的裝置",如果能找到就呼叫i2c_new_device來註冊i2c_client, 這會和i2c_driver的id_table比較,如果匹配,呼叫probe。      

     詳細程式碼呼叫過程: i2c_add_driver     i2c_register_driver         a. at24cxx_driver放入i2c_bus_type的drv連結串列            並且從dev連結串列裡取出能匹配的i2c_client並呼叫probe            driver_register                     b. 對於每一個介面卡,呼叫__process_new_driver(在i2c_bus_type的dev連結串列中不但要掛i2c_client外,還會掛i2c_adpter。當drv和dev連結串列比較的時候,drv不會跟i2c_adpter比較,只會跟i2c_client比較,因為i2c_adpter的.type成員可以用來分辨是i2c_adpter還是i2c_client)。                        對於每一個介面卡,呼叫它的函式確定address_list裡的裝置是否存在(確定的方法是給I2C裝置發一個地址,看它是否迴應ACK,即SDA是否被拉低),即是否支援這個裝置。如果存在,再呼叫detect進一步確定、設定以確定是哪類裝置,因為有些裝置地址一樣,單從地址是沒辦法分辨是哪類裝置的(詳細可以閱讀核心文件            /Documentation/i2c/instantiating-devices)。然後i2c_new_device         /* Walk the adapters that are already present */         i2c_for_each_dev(driver, __process_new_driver);             __process_new_driver                 i2c_do_add_adapter                     /* Detect supported devices on that bus, and instantiate them */                     i2c_detect(adap, driver);                         for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {                             err = i2c_detect_address(temp_client, driver);                                         /* 判斷這個裝置是否存在:簡單的發出S訊號確定有ACK */                                         if (!i2c_default_probe(adapter, addr))                                             return 0;                                                                                  memset(&info, 0, sizeof(struct i2c_board_info));                                         info.addr = addr;                                                                                      // 設定info.type,呼叫strlcpy函式拷貝                                         err = driver->detect(temp_client, &info);                                                              i2c_new_device(最終註冊設定i2c_client)   

at24cxx_drv.c :

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. static int __devinit at24cxx_probe(struct i2c_client *client,
  9.       const struct i2c_device_id *id)
  10. {
  11.  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  12.  return 0;
  13. }
  14. static int __devexit at24cxx_remove(struct i2c_client *client)
  15. {
  16.  printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  17.  return 0;
  18. }
  19. static const struct i2c_device_id at24cxx_id_table[] = {
  20.  { "at24c08", 0 },
  21.  {}
  22. };
  23. static int at24cxx_detect(struct i2c_client *client,
  24.          struct i2c_board_info *info)
  25. {
  26.  /* 能執行到這裡, 表示該addr的裝置是存在的,即dev連結串列中是有這個裝置的
  27.   * 但是有些裝置單憑地址無法分辨(A晶片的地址是0x50, B晶片的地址也是0x50,當發0x50後它們都會迴應,這個時候還是不能區分到底是A還是B,A和B是不可能同時掛在一條總線上的)
  28.   * 還需要進一步讀寫I2C裝置來分辨是哪款晶片,比如讀A晶片可能有一些值,讀B晶片就會有另外一些值
  29.   * detect就是用來進一步分辨這個晶片是哪一款,並且設定info->type
  30.   */
  31.  printk("at24cxx_detect : addr = 0x%x\n", client->addr);
  32.  /* 這裡應該進一步判斷是哪一款,這裡不用判斷 */
  33.  strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
  34.  return 0;
  35. }
  36. //0x60、0x50表示I2C裝置的地址
  37. static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
  38. /* 1. 分配/設定i2c_driver */
  39. static struct i2c_driver at24cxx_driver = {
  40.  .class  = I2C_CLASS_HWMON, /* 表示去哪些介面卡上找裝置 */
  41.  .driver = {
  42.   .name = "100ask",
  43.   .owner = THIS_MODULE,
  44.  },
  45.  .probe  = at24cxx_probe,
  46.  .remove  = __devexit_p(at24cxx_remove),
  47.  .id_table = at24cxx_id_table,
  48.  .detect     = at24cxx_detect,  /* 用這個函式來檢測裝置確實存在 */
  49.  .address_list = addr_list,   /* 這些裝置的地址 */
  50. };
  51. static int at24cxx_drv_init(void)
  52. {
  53.  /* 2. 註冊i2c_driver */
  54.  i2c_add_driver(&at24cxx_driver);
  55.  return 0;
  56. }
  57. static void at24cxx_drv_exit(void)
  58. {
  59.  i2c_del_driver(&at24cxx_driver);
  60. }
  61. module_init(at24cxx_drv_init);
  62. module_exit(at24cxx_drv_exit);
  63. MODULE_LICENSE("GPL");
三、I2C裝置驅動程式的編寫

      上面主要介紹了註冊i2c_client結構的四種方法,並伴隨測試程式at24cxx_dev.c和at24cxx_drv.c。

      然而,我們的目的是應用程式能通過系統呼叫讀寫EEPROM儲存器,這就需要實現圖一中的裝置驅動程式。實現的地方就是在probe函式中,當i2c_client結構和i2c_driver結構都註冊後,在i2c_bus_type結構的i2c_client連結串列中就會有dev,i2c_driver連結串列中有drv,bus的i2c_device_match函式中匹配dev和drv,成功將呼叫probe函式。在上面測試過程中probe函式基本上什麼也沒做,原因在於為了測試i2c_client結構的註冊,這裡的目的是實現裝置驅動層,所以會在裡面實現註冊裝置驅動、建立裝置節點等操作。

      注意,針對i2c_driver結構的probe成員,也就是上面說的probe函式的引數也是非常有用的。當probe函式成功呼叫後,它的第一個引數就記錄了對應的I2C裝置,也就是i2c_client結構體,第二個引數記錄對應I2C裝置的i2c_device_id。在後面裝置驅動讀寫函式中將呼叫核心層的讀寫函式,這些函式的第一個引數就是要知道是哪個I2C裝置,即要傳入i2c_client結構體。所以,在probe函式中,可以定義結構體指標指向probe函式引數,通過這樣的方式記錄儲存了i2c_client和i2c_device_id。

     這樣,圖一中的裝置驅動層就實現了,而核心層和介面卡層都是核心自帶的,主要是提供介面供裝置驅動使用。下面是應用層操作EEPROM將要用到的所有完整程式碼:

at24cxx_dev.c :

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. //0x50表示I2C裝置的地址,一般在 I2C裝置晶片手冊可以查到
  9. static struct i2c_board_info at24cxx_info = { 
  10.  I2C_BOARD_INFO("at24c08", 0x50),//這個名字要和drv程式中的id_table中名字要一樣
  11. };
  12. static struct i2c_client *at24cxx_client;
  13. static int at24cxx_dev_init(void)
  14. {
  15.  struct i2c_adapter *i2c_adap;
  16.  int busNum = 0 ;//把這個匯流排號改為1,也能成功載入此驅動,原因在於i2c_new_device而不是i2c_new_probed_device方法
  17.  printk("at24cxx dev of bus-dev-drv module_init!\n");
  18.  i2c_adap = i2c_get_adapter(busNum);//這裡要實驗的EEPROM是掛接在第0條I2C總線上的,所以這裡的引數是0
  19.  if (!i2c_adap) {
  20.   pr_err("failed to get adapter i2c%d\n", busNum);
  21.   return -ENODEV;
  22.  }
  23.  at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);//設定和註冊i2c_client結構體
  24.  if (!at24cxx_client){
  25.   //pr_err("failed to register %s to i2c%d\n",at24cxx_info.type, busNum);
  26.   pr_err("failed to register at24c08 to i2c%d\n",busNum);
  27.   return -ENODEV;
  28.  } 
  29.  i2c_put_adapter(i2c_adap);
  30.  return 0;
  31. }
  32. static void at24cxx_dev_exit(void)
  33. {
  34.  printk("at24cxx dev of bus-dev-drv module_exit!\n");
  35.  i2c_unregister_device(at24cxx_client);
  36. }
  37. module_init(at24cxx_dev_init);
  38. module_exit(at24cxx_dev_exit);
  39. MODULE_LICENSE("GPL");

at24cxx_drv.c :

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/i2c.h>
  5. #include <linux/err.h>
  6. #include <linux/regmap.h>
  7. #include <linux/slab.h>
  8. #include <linux/fs.h>
  9. #include <asm/uaccess.h>
  10. static int major;
  11. static struct class *class;
  12. static struct i2c_client *at24cxx_client;
  13. /* 傳入: buf[0] : addr, 即將訪問I2C裝置的地址
  14. * 輸出: buf[0] : data
  15. */
  16. static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off)
  17. 相關推薦

    linux3.5I2C驅動

                            &

    linux3.5I2C驅動

                    知識背景: 1. I2C協議 2. 4412處理器I2C介面說明 3. bus-dev-drv模型(詳見文章-Linux下驅動:分層、分離機制學習筆記) 4. linux核心下驅動設計基本知識 一、驅動框架       以4412+linux3.5平臺為例,說明Linu

    linuxi2c驅動筆記

    1. 幾個基本概念 1.1. 裝置模型 由 匯流排(bus_type) + 裝置(device) + 驅動(device_driver) 組成,在該模型下,所有的裝置通過匯流排連線起來,即使有些裝置沒有連線到一根物理總線上,linux為其設定了一個內部的、虛擬的platf

    MAC OS X10.9.5成功驅動獨立顯示卡"影馳Nvidia GeForce GTX 760大將(4GB)--非公版"

    我的機器是桌上型電腦(自己組裝的),硬體引數如下: 作業系統 Windows 7 旗艦版 64位 SP1 ( DirectX 11 ) 處理器 英特爾 Core i7-4770K @ 3.50GHz 四核 主機板

    LinuxI2C驅動分析(一)

            最近在做一個基於全志A33晶片的android移植時發現嵌入式裝置很多都用到了I2C匯流排通訊,比如說攝像頭,G-sensor,觸控式螢幕等,為此我覺得很好的理解I2C裝置驅動在今後的嵌入式開發中是非常有好處的,而目前我也是處於學習階段,便將這些學習的過程給

    Linux3.5內核以後的路由一跳緩存

    all release oca 有意義 got 重定向 這就是 rgb 了解 在Linux3.5版本號(包括)之前。存在一個路由cache。這個路由cache的初衷是美好的,可是現實往往是令人遺憾的。下面是陳列得出的兩個問題:1.面臨針對hash算法的ddos問題(描寫敘

    linux系統編寫I2C驅動

    linux驅動中i2c驅動架構    上圖完整的描述了Linux i2c驅動架構,雖然I2C硬體體系結構比較簡單,但是i2c體系結構在linux中的實現卻相當複雜。   那麼我們如何編寫特定i2c介面器件的驅動程式?就是說上述架構中的那些部分需要我們完成,而哪些是linux核心已經完善的或者是晶片提供

    Linux裝置驅動之3.4.2核心I2C驅動

    框架 1.1 硬體協議簡介 1.2 驅動框架 1.3 bus-drv-dev模型及寫程式 a. 裝置的4種構建方法 詳情參照:linux-3.4.2\Documentation\i2c:instantiating-devices: 以下摘取部分

    linux3.2adt7320的spi驅動編寫

    在3.2中沒有spi_read/spi_write, 更改為了spi_write_then_read, 好用慘了 驅動程式adt7320_driver.c #include <linux/init.h> #include <linux/module.h&g

    IMX6平臺Linux3.14.28系統編譯驅動模組ko和應用程式的Makefile模版

    obj-m += button.oARCH=armMYCROSS_COMPILE=/opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi

    centos6.5安裝docke過程

    centos devel x86 修改 版本 span yum ice 順序 由於需要到使用到docker,centOs6.5的內核是 2.6.32-431.el6.x86_64,需要更新到3.0以上。 安裝過程。 1 下載 kernel-ml-aufs-3.10.5-3.

    SylixOSIIS驅動編程

    飛利浦公司 示意圖 音頻驅動 接口 開發 目錄1. IIS簡介 12. 數字聲音簡介 12.1 采樣頻率 12.2 量化位數 12.3 聲道數 13. WAV音頻文件格式簡介 23.1 文件格式 23.2 原始聲音數據

    Django 在Python3.5 報 沒有模塊MySQLdb

    port mysql模塊 blog color mysqldb tin () col 代碼 Django 在Python3.5 下報 沒有模塊MySQLdb 解決方法: 在整個項目站點下的__init__.py 文件裏(即和setting.py在同一個文件下)寫入以下代碼:

    centos6.5安裝python3安裝、python3虛擬環境創建venv

    替代品 虛擬環境 由於 需要 tools python2.6 ipy模塊 python2 pip3   原因:在安裝完centos6.5後,通過命令行鍵入python時,默認為python2.6.6版本,系統並沒有安裝python3版本。又想學習python3,因此需要在c

    centos6.5yum安裝mysql5.5

    eps mysql 啟動 pms pan tar oar exp let 第一步就是看linu是否安裝了mysql,經過rpm -qa|grep mysql查看到centos下安裝了mysql5.1,那就開始卸載咯 2 接下來就是卸載mysql5.1了,命令

    centos 6.5編譯安裝php-7.1.6和 php memcached擴展

    php 編譯安裝 memcached 0,安裝 php-7.1.6 依賴包# yum groupinstall "Development tools" "Desktop Platform Development" # yum -y install openssl-devel libcurl-dev

    centos6.5使用yum完美搭建LNMP環境(php5.6)

    準備工作 ase write res .so -c service 啟動 tcp 準備工作 配置防火墻,開啟80端口、3306端口刪除原有的 iptables , 添加合適的配置 rm -rf /etc/sysconfig/iptables vi /etc/sysco

    Idea2016.2.5安裝破解版JRebel 6.4.3

    idea jrebel 1、下載idea下的jrebel插件,由於最新版本的插件對應的jrebel版本太新,無法破解。所以只能在idea的在線倉庫中找到老版本的jrebel插件,對應老版本的jrebel破解來安裝。 在線插件倉庫:https://plugins.jetbrains.com/ide

    Centos6.5DHCP服務器的安裝和配置

    默認 聲明 網絡 grep eve install 廣播 6.0 lan 1、首先需要安裝DHCP的軟件包,使用yum進行安裝 # yum install -y dhcp.x86_64 dhcp-devel.x86_64 2、將/usr/share/doc/dhcp-4.

    服務1RHEL6.5DNS服務器的安裝

    linux dns fqdn soa a記錄 環境介紹:node1.cn:slavenode2.cn:masters1、軟件包安裝[[email protected]/* */ ~]# yum -y install bind [[email protected]/*