應用程序和驅動中使用I2C的流程

應用程序和驅動中使用I2C的流程

編寫I2C設備驅動有兩種方法。一種是利用系統給我們提供的i2c-dev.c來實現一個i2c適配器的設備文件。然後通過在應用層操作i2c適配器來控制i2c設備。另一種是爲i2c設備,獨立編寫一個設備驅動。注意:在後一種情況下,是不需要使用i2c-dev.c的。

前一種方法也就是說只要系統實現了I2C適配器的驅動並生成了設備文件, 那麼掛在其上面的I2C設備也可以在應用層直接通過操作這個設備文件來間接控制這個I2C設備, 這樣就省去了專門爲這個設備編寫驅動的麻煩。本文也詳細描述在應用層和驅動層分別控制I2C設備的流程。

一應用層操作I2C設備

即利用i2c-dev.c操作適配器,進而控制i2c設備, i2c-dev.c並沒有針對特定的設備而設計,只是提供了通用的read()、write()和ioctl()等接口,應用層可以借用這些接口訪問掛接在適配器上的i2c設備的存儲空間或寄存器,並控制I2C設備的工作方式。

需要特別注意的是:i2c-dev.c的read()、write()方法都只適合於如下方式的數據格式(可查看內核相關源碼):

 

所以不具有太強的通用性,如下面這種情況就不適用(通常出現在讀目標時)。

 

而且read()、write()方法只適用用於適配器支持i2c算法的情況,如:

static const struct i2c_algorithms3c24xx_i2c_algorithm = {

            .master_xfer = s3c24xx_i2c_xfer,

            .functionality = s3c24xx_i2c_func,

        };

 

而不適合適配器只支持smbus算法的情況,如:

        static const struct i2c_algorithmsmbus_algorithm = {

            .smbus_xfer = i801_access,

            .functionality = i801_func,

        };

 

基於上面幾個原因,所以一般都不會使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以實現上面所有的情況(兩種數據格式、以及I2C算法和smbus算法)。

針對i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。

        struct i2c_rdwr_ioctl_data {

            struct i2c_msg __user *msgs; /*pointers to i2c_msgs */

            __u32 nmsgs; /* number of i2c_msgs*/

        };

        struct i2c_msg {

            _ _u16 addr; /* slave address */

            _ _u16 flags; /* 標誌(讀、寫) */

            _ _u16 len; /* msg length */

            _ _u8 *buf; /* pointer to msg data*/

        };

 

針對smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。對於smbus算法,不需要考慮“多開始信號時序”問題。

        struct i2c_smbus_ioctl_data {

            __u8 read_write; //讀、寫

            __u8 command; //命令

            __u32 size; //數據長度標識

            union i2c_smbus_data __user *data;//數據

        };

 

下面以本人工作中的一個實例講解操作的具體過程。通過imx6q操作tlv320aic3204(一顆音頻codec)。實現對tlv320aic3204的初始化工作。

首先在內核中已經包含了對imx6q中的i2c控制器驅動的支持。提供了i2c算法(非smbus類型的,所以後面的ioctl的命令是I2C_RDWR)

 


我們所用的I2C適配器的設備文件爲:/dev/i2c-0, 接下來是使用流程:

1.      打開適配器文件, 並做相應的設置:

         /*iccard device*/

fd_codec= open(TLV320AIC3204_FILE, O_RDWR);  /*打開適配器文件*/

if(fd_codec< 0) {

           LOGE("can'topen codec device file");

           return0;

}

ioctl(fd_codec,I2C_SLAVE, TLV320AIC3204_I2C_ADDR);  /*配置slave地址*/

ioctl(fd_codec,I2C_TIMEOUT, 10);  /*配置超時時間*/

ioctl(fd_codec,I2C_RETRIES, 2);    /*配置重試次數*/

 

/*initializei2c_data*/ /*初始化I2C通信的核心數據結構*/

i2c_data.nmsgs= 2;    /*max 2 message*/

 

i2c_data.msgs= malloc(i2c_data.nmsgs * sizeof(struct i2c_msg));

if(i2c_data.msgs== NULL) {

           LOGE("cant'tmalloc memory");

           gotofailed1;

}

 

msgs[0]= i2c_data.msgs;

msgs[1]= i2c_data.msgs + 1;

 

msgs[0]->buf= malloc(2);  /*根據要傳輸的字節數來分配*/

if(!msgs[0]->buf){

           LOGE("can'tmalloc memory");

           gotofailed2;

}

 

msgs[1]->buf= malloc(2);  /*根據要傳輸的字節數來分配*/

if(!msgs[1]->buf){

           LOGE("can'tmalloc memory");

           gotofailed3;

}

        

2.      寫寄存器流程如下:

structi2c_msg *p_msg = i2c_data.msgs;

if(fd_codec< 0)

           return-1;

/*justset i2c_data*/

i2c_data.nmsgs= 1;  /*寫的話只要一個msg即可*/

 

p_msg->len= 2;

p_msg->addr= TLV320AIC3204_I2C_ADDR;

p_msg->flags= 0;     /*write*/

p_msg->buf[0]= reg;   /*register*/

p_msg->buf[1]= val;   /*data*/

ret =ioctl(fd_codec, I2C_RDWR, (unsigned long) &i2c_data);  /*發送命令給適配器*/

if(ret< 0) {

           LOGE("ioctlfailed! :%d", ret);

}

 

3.      讀寄存器的流程:

int ret= 0;

structi2c_msg *msgs[2]; /*讀需要兩個msg, 1個用來發讀register的地址, 一個讀數據*/

 

if(fd_codec< 0)

           return-1;

 

msgs[0]= i2c_data.msgs;

msgs[1]= i2c_data.msgs + 1;

/*justset i2c_data*/

i2c_data.nmsgs= 2;

 

/*指定要讀的寄存器地址的msg*/

msgs[0]->len= 1;

msgs[0]->addr= TLV320AIC3204_I2C_ADDR;

msgs[0]->flags= 0;   /*write*/

msgs[0]->buf[0]= reg;   /*register*/

 

/*讀數據*/

msgs[1]->len= 1;

msgs[1]->addr= TLV320AIC3204_I2C_ADDR;

msgs[1]->flags= 1;   /*read*/

msgs[1]->buf[0]= 0;

 

ret =ioctl(fd_codec, I2C_RDWR, (unsigned long) &i2c_data);  /*發送請求命令*/

if(ret< 0) {

           LOGE("ioctlfailed! :%d", ret);

 

           returnret;

}

else

           return msgs[1]->buf[0];

 

以上講述了一種比較常用的利用i2c-dev.c操作i2c設備的方法,這種方法可以說是在應用層完成了對具體i2c設備的驅動工作。

 

二驅動層操作I2C設備

即爲I2C設備編寫驅動程序, 首先要確保I2C適配器的驅動都正常, 因爲我們需要它來實現正真的I2C傳輸, 下面將以本人項目中的一顆RTC的I2C設備:pcf8563爲例來講解大體的I2C驅動流程。

1.      定義一個i2c_board_info的數據塊:

static structi2c_board_info mxc_i2c1_board_info[] __initdata = {

{

           I2C_BOARD_INFO("pcf8563", 0x51), /*名字要跟驅動裏的同名, 0x51爲I2C的設備地址*/

},

};

2.      註冊到系統中去:

i2c_register_board_info(1,mxc_i2c1_board_info,

                                       ARRAY_SIZE(mxc_i2c1_board_info));

3.      編寫 PCF8563的I2C驅動, 這個驅動在內核裏已經實現了, 在driver/rtc/rtc-pcf8563.c, 我們將描述這個驅動的框架, 及怎麼跟上面註冊的i2c設備綁定在一起。

3.1      I2C驅動都有一個i2c_driver的類型, 這裏是:

static struct i2c_driver pcf8563_driver = {

        .driver                = {

                 .name       = "rtc-pcf8563",

        },

        .probe                = pcf8563_probe,

        .remove            = pcf8563_remove,

        .id_table  = pcf8563_id,

};

pcf8563_id 內容如下:

static const struct i2c_device_idpcf8563_id[] = {

        {"pcf8563", 0 },   /*可以看到名稱和上面的設備名稱相同*/

        {"rtc8564", 0 },

        {}

};

3.2      把i2c_driver註冊到系統中去:

static int __init pcf8563_init(void)

{

        returni2c_add_driver(&pcf8563_driver);

}

註冊進去後, I2C總線會去查找哪些已經掛在總線上, 但還沒有找到驅動的設備, 然後,根據設備名稱和驅動支持的id列表中去匹配, 如果匹配的話, 就調用驅動的probe函數, 顯然,這邊我們找到了pcf8563的設備,pcf8563_probe會被調用。

3.3      pcf8563_probe函數會去向rtc子系統註冊一個RTC設備, 這邊不詳細解說了, 我們着重看看在這個I2C驅動裏是如何跟I2C設備通信的,

3.4      讀寫設備:

讀設備如下:

static int pcf8563_get_datetime(structi2c_client *client, struct rtc_time *tm)

{

         structpcf8563 *pcf8563 = i2c_get_clientdata(client);

         unsignedchar buf[13] = { PCF8563_REG_ST1 };

 

         structi2c_msg msgs[] = {

                   {client->addr, 0, 1, buf }, /* setupread ptr */

                   {client->addr, I2C_M_RD, 13, buf },        /*read status + date */

         };

 

         /*read registers */

         if((i2c_transfer(client->adapter, msgs, 2)) != 2) {

                   dev_err(&client->dev,"%s: read error\n", __func__);

                   return-EIO;

         }

    ….

}

 

寫設備如下:

static int pcf8563_set_datetime(structi2c_client *client, struct rtc_time *tm)

{

         structpcf8563 *pcf8563 = i2c_get_clientdata(client);

         inti, err;

         unsignedchar buf[9];

     …….

         /*hours, minutes and seconds */

         buf[PCF8563_REG_SC]= bin2bcd(tm->tm_sec);

         buf[PCF8563_REG_MN]= bin2bcd(tm->tm_min);

         buf[PCF8563_REG_HR]= bin2bcd(tm->tm_hour);

 

         buf[PCF8563_REG_DM]= bin2bcd(tm->tm_mday);

 

         /*month, 1 - 12 */

         buf[PCF8563_REG_MO]= bin2bcd(tm->tm_mon + 1);

 

         /*year and century */

         buf[PCF8563_REG_YR]= bin2bcd(tm->tm_year % 100);

         if(pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year <100))

                   buf[PCF8563_REG_MO]|= PCF8563_MO_C;

 

         buf[PCF8563_REG_DW]= tm->tm_wday & 0x07;

 

         /*write register's data */

         for(i = 0; i < 7; i++) {

                   unsignedchar data[2] = { PCF8563_REG_SC + i,

                                                        buf[PCF8563_REG_SC+ i] };

 

                   err= i2c_master_send(client, data, sizeof(data));

                   if(err != sizeof(data)) {

                            dev_err(&client->dev,

                                     "%s:err=%d addr=%02x, data=%02x\n",

                                     __func__,err, data[0], data[1]);

                            return-EIO;

                   }

         };

 

         return0;

}

 

部分內容來自劉洪濤老師, 請參考:

http://blog.csdn.net/hongtao_liu/article/details/4964244

http://blog.csdn.net/hongtao_liu/article/details/5260739


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