1. 程式人生 > >Linux--核心---I2C匯流排驅動分析 以linux3.10.0 RK3288為例

Linux--核心---I2C匯流排驅動分析 以linux3.10.0 RK3288為例

Linux 3.10.0 iic匯流排註冊過程
I2C匯流排驅動包括I2C介面卡驅動載入與解除安裝以及I2C匯流排通訊方法
I2C核心提供了i2c_adapter的增加和刪除函式、i2c_driver的增加和刪除函式、i2c_client的依附和脫離函式
以及i2c傳輸、傳送和接收函式。

1、I2C核心 i2c_core.c

I2C核心提供了i2c_adapter的增加和刪除函式、i2c_driver的增加和刪除函式、i2c_client的依附和脫離函式
以及i2c傳輸、傳送和接收函式。

2、I2C匯流排驅動 i2c_rockchip.c

I2C匯流排驅動包括I2C介面卡驅動載入與解除安裝以及I2C匯流排通訊方法。其中I2C介面卡驅動載入(與解除安裝)要完成初
始化(釋放)I2C介面卡所使用的硬體資源,申請I/0地址、中斷號、通過i2c_add_ adapter()新增i2c_adapter
的資料結構(通過i2c_del_adapter()刪除i2c_adapter的資料結構)的工作。12C匯流排通訊方法主要對特定的I2C
介面卡實現i2c_algorithm的master_xfer()方法來實現i2c_ msg的傳輸。不同的介面卡對應的master_xfer()
方法由其處理器的硬體特性決定。

3、I2C裝置驅動 i2c_mydriver.c

I2C裝置驅動主要用於I2C裝置驅動模組載入與解除安裝以及提供I2C裝置驅動檔案操作介面。I2C裝置驅動的模組加
載通用的方法遵循以下流程:首先通過register_chrdev()將I2C設備註冊為一個字元裝置,然後利用I2C核心
中的i2c_add_adapter()新增i2c_driver。呼叫i2c_add_adapter()過程中會引發i2c_driver結構體中的
YYY_attach_adapter()的執行,它通過呼叫I2C核心的i2c_probe()實現物理裝置的探測。i2c_probe()會引發
yyy_detect()的呼叫。yyy_detect()中會初始化i2c_ client,然後呼叫核心的i2c_attach_client()通知I2C
核心此時系統中包含了一個新的I2C裝置。之後會引發I2C裝置驅動中yyy_init_client()來初始化裝置。解除安裝
過程執行相反的操作。


關心的主要檔案
kernel\drivers\i2c\busses\i2c-rockchip.c匯流排驅動程式,不同的晶片有不同的匯流排驅動程式
kernel\drivers\i2c\i2c-core.c 核心i2c核心程式

關心的幾個主要結構體
static struct platform_driver rockchip_i2c_driver = { //匯流排驅動是一個platform_driver型別
    .probe        = rockchip_i2c_probe,      //在probe裡完成adapter,client註冊(硬體資源的繫結中斷號,引腳,時鐘,控制器暫存器等)
    .remove        = rockchip_i2c_remove,
    .driver        = {
        .owner    = THIS_MODULE,
        .name    = "rockchip_i2c",
        .pm    = ROCKCHIP_I2C_PM_OPS,
        .of_match_table    = of_match_ptr(rockchip_i2c_of_match),
    },
};
static int __init rockchip_i2c_init_driver(void)
{
    return platform_driver_register(&rockchip_i2c_driver);
}


struct rockchip_i2c {
    spinlock_t        lock; 
    wait_queue_head_t    wait; 
    unsigned int        suspended:1;

    struct i2c_msg        *msg; //傳送的從機地址和資料,時鐘頻率
    unsigned int        is_busy;
    int            error;
    unsigned int        msg_ptr;

    unsigned int        irq;//中斷號

    enum rockchip_i2c_state    state;  //i2c狀態 開始 結束 讀/寫
    unsigned int        complete_what;
    unsigned long        clkrate;

    void __iomem        *regs; //存放對應控制器的暫存器
    struct clk        *clk;
    struct device        *dev;
    struct resource        *ioarea;
    struct i2c_adapter    adap; //i2c介面卡 對應一個硬體i2c控制器

    unsigned long        scl_rate;
    unsigned long        i2c_rate;
    unsigned int        addr;
    unsigned char        addr_1st, addr_2nd;
    unsigned int        mode;
    unsigned int        count;

    unsigned int        check_idle;//標記是否有裝置樹資源
    int            sda_gpio, scl_gpio;
    struct pinctrl_state    *gpio_state;
};
struct i2c_adapter { 
    struct module *owner;
    unsigned int class;          /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* the algorithm to access the bus */ //其中master_xfer()介面是硬體相關的,就是收發函式,操作控制器暫存器
    void *algo_data;

    /* data fields that are valid for all devices    */
    struct rt_mutex bus_lock;

    int timeout;            /* in jiffies */
    int retries;
    struct device dev;        /* the adapter device */ dev->p->driver_data = rockchip_i2c;私有值指向當前i2c結構體(包含控制器暫存器io腳等
特定資源),of_node指向當前註冊的platform_device->dev.of_node;也包含特定的裝置樹資源//會被註冊進dev裝置掛接到核心中的連結串列中來管理。

    int nr;              //id號
    char name[48];       //在i2c匯流排驅動裡命名
    struct completion dev_released;

    struct mutex userspace_clients_lock;
    struct list_head userspace_clients;  //將一個i2c_adapter物件和它所屬的i2c_client物件以及相應的i2c_driver物件連線到一起

    struct i2c_bus_recovery_info *bus_recovery_info;
};
struct i2c_client {
    unsigned short flags;        /* div., see below        */ 各種模式標識 地址型別,是否有wakeuo-source等
    unsigned short addr;        /* chip address - NOTE: 7bit    */子節點裡定義的reg屬性
                    /* addresses are stored in the    */
                    /* _LOWER_ 7 bits        */
    char name[I2C_NAME_SIZE];      //request_module("%s%s", I2C_MODULE_PREFIX, info.type);#define I2C_MODULE_PREFIX "i2c:"
    struct i2c_adapter *adapter;    /* the adapter we sit on    */匯流排註冊的時候指向對應adapter
    struct i2c_driver *driver;    /* and our access routines    */
    struct device dev;        /* the device structure        */表示這是一個device,dev.of_node指向子節點的device_node dev.bus=&i2c_bus_type
    int irq;            /* irq issued by device        */使用的中斷號
    struct list_head detected;       //將所有i2c_client連在一起的節點
};


///////////////////////////////註冊過程,目的:完成adapter,i2c_client的註冊//////////////////////

兩個問題,也是主要的兩個裝置
1,adapter是什麼,裡面有些什麼?
 答:在i2c種一個adapter對應一個硬體上的控制器,裡面包含i2c的收發函式,控制器的暫存器地址,io口等資源;它被i2c匯流排驅動建立, 並和i2c_client相連(如果裝置樹裡有子節點)
2,i2c_client是什麼,裡面有些什麼?
 答:i2c_client對應一個掛載在i2c總線上的i2c裝置,裡面包含裝置的地址,adapter的指標(註冊時會設定),裝置驅動driver的指標(裝置驅動註冊時繫結)。
     描述一個掛接在硬體i2c總線上的裝置的裝置資訊,即i2c裝置的裝置物件

linux核心裡 i2c_adapter,i2c_client,i2c_driver這幾個結構體真的是我中有你你中有我啊,繞來繞去


I2C匯流排驅動是一個platform_driver型別的驅動所以使用platform_driver_register來註冊
platform_driver_register(&rockchip_i2c_driver);
關於platform_driver_register的過程在上篇文章中分析,這裡只要知道呼叫這個函式註冊後就會進入i2c匯流排驅動的probe,完成申請資源,初始化,提供介面等工作,接下來分析匯流排的rockchip_i2c_probe()

rockchip_i2c_probe(struct platform_device *pdev)//獲取platform_device的資源
    i2c_add_adapter(&i2c->adap);配置adapter資源:將資源填入adapter的結構體中包含1、收發函式指標;2、裝置樹資源:名字,暫存器地址,中斷,id,io口
       __i2c_add_numbered_adapter(adapter);
           i2c_register_adapter(adap);配置adapter資源在sysfs裡生成
              device_register(&adap->dev);註冊adapter裝置 dev裡已經包含了rockchip_i2c; 裝置樹對應的of_node等特定資源,已經對應上了一個i2c控制器
    of_i2c_register_devices(&i2c->adap);//建立i2c_client 也就是掛在i2c總線上的從裝置
         for_each_available_child_of_node(adap->dev.of_node, node)遍歷裝置樹裡的每個子節點
             i2c_new_device(adap, &info);如果子節點存在則呼叫
                 device_register(&client->dev);//註冊i2c_client裝置 這個dev會找到這個i2c_client,i2c_client裡包含了adapter

之後自己寫的i2c裝置驅動就會匹配上這個client,在驅動的probe裡返回這個client的指標,這時就可以進行資料的收發了。

//////////////////////////////////////adapter收發函式的分析
adapter的收發函式共用一個rockchip_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
由i2c_msg指定是發還是收,裝置地址和資料
rockchip_i2c_xfer
  rockchip_i2c_doxfer
    i2c_writel
      writel(val, i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg));//對應暫存器寫入資料就行了

struct i2c_msg {
    __u16 addr;    //裝置地址
    __u16 flags; //讀寫標識
#define I2C_M_TEN        0x0010    /* this is a ten bit chip address */
#define I2C_M_RD        0x0001    /* read data, from slave to master */
#define I2C_M_STOP        0x8000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART        0x4000    /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK        0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN        0x0400    /* length will be first received byte */
    __u16 len;        /* msg length                */資料長度
    __u8 *buf;        /* pointer to msg data            */ 要傳送或者接收的資料指標
#ifdef CONFIG_I2C_ROCKCHIP_COMPAT
    __u32 scl_rate;        /* add by kfx */
#endif
};