Linux I2C設備驅動註冊(通過板級信息文件註冊)

本文轉自:https://blog.csdn.net/airk000/article/details/21345457

在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匹配函數了。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章