linux下I2C總線驅動架構分析

        人生十有八九不如意,在最黑暗的日子裏,依然寄希望於奮筆疾書減輕心中的苦悶。緣起緣滅,上天註定,只需要靜靜的看着天意的安排就好了。看到一部微電影,故事情節挺老套的,但是結尾卻給我留下了深刻印象。故事的開始,女主角過馬路,差點被一輛車撞倒,男二號從車上下來,詢問女主角是否受傷,還遞給她一張名片,然後開車離開了。這時候,女主角手腕上的感應硃砂亮了,她堅信男二號就是她苦苦追尋的前世戀人,因爲她沒有忘記前世的記憶。女主角瘋狂的追求男二號,男二號對她並沒有好感。這時候男主角出現,他對女主角一見鍾情,但是女主角從來沒有把他放在心上。故事就這樣在三角戀裏糾纏,在危機關頭女主角替男主角當了一刀,男主角抱着她的時候,她手腕上的感應硃砂再次亮了,原來她一直追尋的前世戀人是男主角。謎底揭曉了,原來第一次硃砂亮起的時候,男主角也在現場,他藏在男二號的車尾後備箱裏,準備殺死男二號,因爲男二號是男主角的殺父仇人的兒子。那麼問題來了,現實中會不會出現像劇中的女主角那樣,錯把過往記憶裏的人當作前世註定的情緣不肯放手,錯過真正的前世情緣呢?不扯閒話了,開始進入正題。    

     Linux系統中i2c驅動分成三個部分:i2c-corei2c-busi2c-devi2c核心是i2c總線驅動和i2c設備驅動的中間樞紐,它以通用的、與平臺無關的接口實現了i2c中設備與適配器的溝通。i2c總線驅動填充i2c_adapteri2c_algorithm結構體。i2c設備驅動填充i2c_driveri2c_client結構體。i2c核心有操作系統實現,i2c設備驅動在以前的文檔中已經詳細介紹,本文檔重點關注i2c總線驅動的實現。

   Linux2.6版本以後,系統使用platform總線模式實現外設驅動,i2c總線驅動也遵循這種設計模式。Platform總線模式的優點:設備驅動中引入platform 概念,隔離BSP和驅動。在BSP中定義platform設備和設備使用的資源、設備的具體匹配信息,而在驅動中,只需要通過API去獲取資源和數據,做到了板相關代碼和驅動代碼的分離,使得驅動具有更好的可擴展性和跨平臺性。

一、 platform總線架構

      Linux實現了一種虛擬的總線,稱爲platform總線,相應的設備稱爲platform_device,而驅動稱爲 platform_driverPlatform總線的初始化有內核實現,platform設備和platform驅動需要自己註冊到platform總線上。針對i2c總線驅動,我們把i2c總線驅動作爲一個設備,然後遵循platform設計模式註冊到platform總線上。基於Platform總線的驅動開發流程如下:(1)定義platform device;(2)註冊platform device;(3)定義platform driver;(4)註冊platform driver

1、 定義platform device

       Linux內核在\include\linux\platform_device.h文件中定義了

        struct platform_device {

               const char * name;

               int id;

               struct device dev;

               u32 num_resources;

               struct resource * resource;

               const struct platform_device_id *id_entry;

              /* MFD cell pointer */

              struct mfd_cell *mfd_cell;

             /* arch specific additions */

             struct pdev_archdata archdata;

};

2、註冊platform device

     Imx6\arch\arm\mach-mx6\board-mx6q_sabresd.c文件中調用imx6q_add_imx_i2c(2, &mx6q_sabresd_i2c_data);函數實現平臺設備註冊,該函數中調用

Imx6\arch\arm\plat-mxc\devices\platform-imx-i2c.c文件中的如下函數實現真正的註冊功能。

     struct platform_device *__init imx_add_imx_i2c(const struct imx_imx_i2c_data *data,

                                                  const struct imxi2c_platform_data *pdata)

     {

                struct resource res[] = {

              {

                    .start = data->iobase,

                   .end = data->iobase + data->iosize - 1,

                   .flags = IORESOURCE_MEM,

            },

           {

                 .start = data->irq,

                 .end = data->irq,

                 .flags = IORESOURCE_IRQ,

           },

           };

         return imx_add_platform_device("imx-i2c", data->id,res, ARRAY_SIZE(res),pdata, sizeof(*pdata));

   }

   imx_add_platform_device()函數最終調用platform_device_add()函數實現平臺設備註冊,i2c總線設備名字爲imx-i2c

3、定義platform driver

      Linux內核在\include\linux\platform_device.h文件中定義了

      struct platform_driver {

                int (*probe)(struct platform_device *);

                int (*remove)(struct platform_device *);

                void (*shutdown)(struct platform_device *);

                int (*suspend)(struct platform_device *, pm_message_t state);

                int (*resume)(struct platform_device *);

                struct device_driver driver;

                const struct platform_device_id *id_table;

       };

     Imx6\drivers\i2c\busses\i2c-imx.c文件中對該結構體的部分成員進行了初始化

     static struct platform_driver i2c_imx_driver = {

                     .remove = __exit_p(i2c_imx_remove),

                     .driver = {

                     .name = DRIVER_NAME,

                     .owner = THIS_MODULE,

                 }

         };

    I2c總線驅動的名字爲imx-i2c

 4、註冊platform driver

     Imx6\drivers\i2c\busses\i2c-imx.c文件中調用platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);函數實現平臺驅動的註冊。當struct platform_device結構體中的namestruct platform_driver結構體中的struct device_driver結構體裏的name名稱一致時,註冊的i2c_imx_probe探測函數開始運行,從此處開始進入i2c總線的初始化和配置。

二、 I2c總線驅動

1i2c總線結構體

I2c總線驅動主要填充i2c_adapteri2c_algorithm結構體,在\include\linux\i2c.h文件中定義了這兩個結構體:

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 */

          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 */

        int nr;

        char name[48];

        struct completion dev_released; 

        struct mutex userspace_clients_lock;

        struct list_head userspace_clients;

};

struct i2c_algorithm {

/* If an adapter algorithm can't do I2C-level access, set master_xfer

   to NULL. If an adapter algorithm can do SMBus access, set

   smbus_xfer. If set to NULL, the SMBus protocol is simulated

   using common I2C messages */

/* master_xfer should return the number of messages successfully

   processed, or a negative value on error */

        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);

       /* To determine what the adapter supports */

       u32 (*functionality) (struct i2c_adapter *);

};

Imx6\drivers\i2c\busses\i2c-imx.c文件中實現了i2c總線驅動,不過對這兩個結構體的實現進行了進一步的封裝,變成了填充如下的結構體:

struct imx_i2c_struct {

         struct i2c_adapter adapter;

         struct resource *res;

         struct clk *clk;

         void __iomem *base;

         int irq;

         wait_queue_head_t queue;

         unsigned long i2csr;

         unsigned int disable_delay;

         int stopped;

         unsigned int ifdr; /* IMX_I2C_IFDR */

         unsigned int cur_clk;

};

2、 i2c總線結構體實現

(1) 註冊i2c平臺驅動

     在註冊平臺驅動部分提到,當平臺設備中的名稱和平臺驅動中的名稱一致時,函數platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);中的i2c_imx_probe()函數將會被調用,從此開始i2c總線的配置。

(2) I2c總線的配置

I2c總線的配置操作在i2c_imx_probe()函數中實現,由於函數體的內容太多,這裏只概括大致的功能:

① i2c寄存器所在的物理地址映射到虛擬地址,便與在系統中操作i2c寄存器;

② 填充struct imx_i2c_struct結構體,重點關注其中的struct i2c_algorithm結構體的具體實現,因爲它實現了i2c總線讀寫操作的時序,後面在詳細說明該過程;

③ 註冊i2c中斷事件,調用i2c_imx_isr()函數完成中斷處理,後面詳細說明該過程;

④ 設置i2c的總線速率;

⑤ I2c總線適配器註冊到內核中,調用i2c_add_numbered_adapter(&i2c_imx->adapter);

(3) I2c總線操作

I2c總線操作在struct i2c_algorithm結構體中實現,imx6中通過註冊

static struct i2c_algorithm i2c_imx_algo = {

.master_xfer = i2c_imx_xfer,

.functionality = i2c_imx_func,

};

           I2c總線的讀寫操作通過i2c_imx_xfer()函數實現,該函數實現了主機收發的操作,對於從機接收操作在i2c中斷函數中處理。I2c總線使用中斷方式收發數據,該函數中使用了內核等待隊列機制實現i2c狀態位的等待wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10);。

(4) I2c中斷

I2c中斷函數讀取中斷標誌位,然後清除中斷,通過wake_up(&i2c_imx->queue);函數喚醒i2c總線操作時調用的等待隊列。

三、 i2c總線從機操作

上述主要描述了i2c做主機時的操作流程,這節描述i2c做從機接收的操作流程。I2c總線空閒時默認配置成從機模式,當i2c做主機時才配置成主模式。Imx6i2c3實現了從機接收的功能。

(1) Imx6\drivers\i2c\busses\i2c-imx.c文件i2c_imx_probe()函數中把i2c3默認配置成從機模式,在i2c_imx_xfer()函數中當需要主發送時把i2c3配成主模式,發送完成後設置成從機模式;使用init_waitqueue_head()初始化內核等待隊列,pollselect結合使用,上報從機接收到完整數據事件給應用層;使用kfifo_alloc創建環形隊列,用於存儲i2c3從機接收到的數據;使用hrtimer_init()創建10us定時器,檢測i2c3從機是否接收完一幀數據,如果接收完一幀數據就使用kfifo_in()把接收到的數據拷貝到環形隊列中,並使用wake_up_interruptible()喚醒剛纔的等待隊列,供應用層讀取數據;

(2) 在中斷函數i2c_imx_isr()裏對中斷事件類型進行判斷,如果i2c3是從機接收數據事件,把接收到的數據放在接收緩存區中;

四、 i2c設備驅動

imx6i2c3設備驅動在\drivers\ght\i2c_slave.c文件中,下面介紹設備註冊步驟:

(1) i2c3設備板信息註冊

Imx6\arch\arm\mach-mx6\board-mx6q_sabresd.c文件中調用i2c_register_board_info(2,mxc_i2c2_board_info,ARRAY_SIZE(mxc_i2c2_board_info));函數註冊設備板信息,板信息結構體定義如下:

static struct i2c_board_info mxc_i2c2_board_info[] __initdata = {

{

        I2C_BOARD_INFO("i2c_slave", 0x10),

    },

};

 

(2) i2c3設備驅動信息註冊

\drivers\ght\i2c_slave.c文件模塊初始化函數i2c_slave_init()中調用i2c_add_driver(&i2c_slave_i2c_driver);函數實現i2c3設備驅動信息註冊,驅動信息結構體定義如下:

static struct i2c_driver i2c_slave_i2c_driver = {

.driver = {

  .owner = THIS_MODULE,

  .name  = "i2c_slave",

  },

.probe  = i2c_slave_probe,

.remove = i2c_slave_remove,

.id_table = i2c_slave_id,

};

當板信息和設備驅動名稱匹配時,即兩個名稱都是”i2c_slave”時,i2c_slave_probe()函數被調用,在該函數中只是獲取了i2c3設備的設備指針。

(3) i2c3設備操作接口註冊

\drivers\ght\i2c_slave.c文件模塊初始化函數i2c_slave_init()中調用misc_register(&i2c_slave_miscdev);函數實現i2c3操作接口註冊,該函數註冊了一個雜項設備,其中定義了雜項設備的具體實現如下:

static struct miscdevice i2c_slave_miscdev = {

    .minor = I2C_SLAVE_MINOR,

    .name = "i2c_slave",

    .fops = &i2c_slave_fops,

};

具體的操作接口有i2c_slave_fops結構體實現,它的實現如下:

static const struct file_operations i2c_slave_fops = {

    .owner = THIS_MODULE,

    .llseek = NULL,//no_llseek,

    .write = i2c_slave_write,

    .read = i2c_slave_read,

    .poll = i2c_slave_poll,

    .unlocked_ioctl = NULL,

};

(4) i2c3設備操作接口解釋

i2c_slave_write()函數實現i2c3設備主寫功能,i2c_slave_read()函數實現i2c3設備從機模式讀數據功能,數據是從kfifo環形隊列中讀出的,i2c_slave_poll函數實現阻塞模式讀數據功能。

當應用層調用select()函數查看i2c3設備句柄是否有數據需要讀時,會調用到驅動的i2c_slave_poll()函數,在該函數中我們註冊了內核等待隊列並處於睡眠狀態,這樣select()函數調用後會阻塞。當定時器檢測到i2c3從機接收到完整的一幀數據時,會喚醒等待隊列,這樣select()會退出阻塞狀態,然後調用read()函數讀出數據。

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