1. 程式人生 > >linux I2C 裝置驅動學習筆記

linux I2C 裝置驅動學習筆記

一:I2C 概述 
         I2C是philips提出的外設匯流排.使用SCL時鐘線,SDA序列資料線這兩根訊號線就實現了裝置之間的資料互動,被非常廣泛地應用在CPU與EEPROM,實時鐘,小型LCD等裝置通訊中。 

二:在linux下的驅動思路 
   linux系統下編寫I2C驅動,有兩種方法:
   一是把I2C裝置當作一個普通的字元裝置來處理,用i2c-dev.c檔案提供的API,封裝裝置時序資料,直接操作i2c介面卡驅動對應的裝置檔案,實現與裝置的通訊。屬於使用者態驅動。
   二是利用linux下I2C子系統框架體系來實現。屬於核心態度驅動,使用者空間的應用程式直接操作從器件對應的裝置檔案,既可用通用的API實現與從器件的資料互動。
   
   以下分別說明兩種方式。
   
三:使用者態實現裝置驅動方式。


1. i2c_dev

        對於註冊的i2c介面卡,使用者空間也可以使用它們。在Linux核心程式碼檔案/include/linux/i2c-dev.c中實現了I2C介面卡裝置檔案的功能,針對每個介面卡生成一個主裝置號為89的裝置節點(次裝置號為0-255),I2c-dev.c並沒有針對特定的裝置而設計,只是提供了通用的read(),write(),和ioctl()等檔案操作介面,在使用者空間的應用層就可以借用這些介面訪問掛接在介面卡上的I2C裝置的儲存空間或暫存器,並控制I2C裝置的工作方式。

        i2c介面卡的裝置節點是/dev/i2c-x,其中x是數字,代表介面卡的編號。由於介面卡編號是動態分配的(和註冊次序有關),所以想了解哪一個介面卡對應什麼編號,可以檢視/sys/class/i2c-dev/目錄下的檔案內容。如:

[
[email protected]
/]# cat /sys/class/i2c-dev/i2c-0/name PNX4008-I2C0 [[email protected] /]# cat /sys/class/i2c-dev/i2c-1/name PNX4008-I2C1 [[email protected] /]# cat /sys/class/i2c-dev/i2c-2/name USB-I2C

如果我們想開啟第2個介面卡,剛好它的編號是1,對應的裝置節點是/dev/i2c-1。那麼可以用下面的方法開啟它:
int fd;
if ((fd = open("/dev/i2c-1",O_RDWR))< 0) {
    /* 錯誤處理 */
    exit(1);
}
說明:

開啟介面卡對應的裝置節點,i2c-dev為開啟的執行緒建立一個i2c_client但是這個i2c_client並不加到i2c_adapter的client連結串列當中。當用戶關閉裝置節點時,它自動被釋放

2.ioctl()

檢視include/linux/i2c-dev.h檔案,可以看到i2c-dev支援的IOCTL命令。如i2c-dev IOCTL命令
#define I2C_RETRIES                    0x0701     /*設定收不到ACK時的重試次數*/
#define I2C_TIMEOUT                 0x0702 /* 設定超時時限的jiffies */
#define I2C_SLAVE                       0x0703 /*設定從機地址 */
#define I2C_SLAVE_FORCE        0x0706  /* 強制設定從機地址 */
#define I2C_TENBIT                      0x0704 /*選擇地址位長:=0 for 7bit , != 0 for 10 bit */
#define I2C_FUNCS                      0x0705 /*獲取介面卡支援的功能 */
#define I2C_RDWR                        0x0707                                 /*Combined R/W transfer (one STOP only)  */
#define I2C_PEC                            0x0708                                  /* != 0 to use PEC with SMBus */
#define I2C_SMBUS                      0x0720                                   /*SMBus transfer  */

如:

a.  設定重試次數
ioctl(fd, I2C_RETRIES,m);
這句話設定介面卡收不到ACK時重試的次數為m。預設的重試次數為1。
b.  設定超時
ioctl(fd, I2C_TIMEOUT,m);
設定SMBus的超時時間為m,單位為jiffies。
c.  設定從機地址
ioctl(fd, I2C_SLAVE,addr);
ioctl(fd, #defineI2C_SLAVE_FORCE, addr);
在呼叫read()和write()函式之前必須設定從機地址。這兩行都可以設定從機的地址,區別是第二行無論核心中是否已有驅動在使用這個地址都會成功,第一行則只在該地址空閒的情況下成功。由於i2c-dev建立的i2c_client不加入i2c_adapter的client列表,所以不能防止其它執行緒使用同一地址,也不能防止驅動模組佔用同一地址。
d.  設定地址模式
ioctl(file,I2C_TENBIT,select)
如果select不等於0選擇10位元地址模式,如果等於0選擇7位元模式,預設7位元。只有介面卡支援I2C_FUNC_10BIT_ADDR,這個請求才是有效的。

e.  獲取介面卡功能ioctl(file,I2C_FUNCS,(unsignedlong *)funcs)

3.與從器件的通訊資料幀。

i2c傳送或者接收一次資料都以資料包 struct i2c_msg 封裝

struct i2c_msg {  
    __u16 addr;     // 從機地址  
    __u16 flags;    // 標誌  
#define I2C_M_TEN   0x0010  // 十位地址標誌  
#define I2C_M_RD    0x0001  // 接收資料標誌  
    __u16 len;      // 資料長度  
    __u8 *buf;      // 資料指標  
};  

其中addr為從機地址;flags則是這次通訊的標誌,傳送資料為0,接收資料則為 I2C_M_RD;len為此次通訊的資料位元組數;buf 為傳送或接收資料的指標。在裝置驅動中我們通常呼叫 i2c-core 定義的介面 i2c_master_send 和 i2c_master_recv 來發送或接收一次資料。

---------注意 i2c_master_send 和 i2c_master_recv 都是對 i2c_transfer的封裝。

------具體呼叫時的差異.------

int i2c_master_send(struct i2c_client *client,const char *buf ,int count)  
{  
    int ret;  
    struct i2c_adapter *adap=client->adapter; // 獲取adapter資訊  
    struct i2c_msg msg; // 定義一個臨時的資料包  

    msg.addr = client->addr; // 將從機地址寫入資料包  
    msg.flags = client->flags & I2C_M_TEN; // 將從機標誌併入資料包  
    msg.len = count; // 將此次傳送的資料位元組數寫入資料包  
    msg.buf = (char *)buf; // 將傳送資料指標寫入資料包  

    ret = i2c_transfer(adap, &msg, 1); // 呼叫平臺介面傳送資料  

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes 
       transmitted, else error code. */  
    return (ret == 1) ? count : ret; // 如果傳送成功就返回位元組數  
}  
EXPORT_SYMBOL(i2c_master_send); 
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)  
{  
    struct i2c_adapter *adap=client->adapter; // 獲取adapter資訊  
    struct i2c_msg msg; // 定義一個臨時的資料包  
    int ret;  

    msg.addr = client->addr; // 將從機地址寫入資料包  
    msg.flags = client->flags & I2C_M_TEN; // 將從機標誌併入資料包  
    msg.flags |= I2C_M_RD; // 將此次通訊的標誌併入資料包  
    msg.len = count; // 將此次接收的資料位元組數寫入資料包  
    msg.buf = buf;  

    ret = i2c_transfer(adap, &msg, 1); // 呼叫平臺介面接收資料  

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes 
       transmitted, else error code. */  
    return (ret == 1) ? count : ret; // 如果接收成功就返回位元組數  
}  
EXPORT_SYMBOL(i2c_master_recv); 
於是。讀一個暫存器的介面可以按照如下方式封裝:
static int read_reg(struct i2c_client *client, unsigned char reg, unsigned char *data)  
{  
    int ret;  

    struct i2c_msg msgs[] = {  
        {  
            .addr = client->addr,  
            .flags = 0,  
            .len = 1,  
            .buf = &reg, // 暫存器地址  
        },  
        {  
            .addr = client->addr,  
            .flags = I2C_M_RD,  
            .len = 1,  
            .buf = data, // 暫存器的值  
        },  
    };  

    ret = i2c_transfer(client->adapter, msgs, 2); // 這裡 num = 2,通訊成功 ret = 2  
    if (ret < 0)  
        tp_err("%s error: %d\n", __func__, ret);  

    return ret;  
}  
等價於:
static unsigned char read_reg(struct i2c_client *client, unsigned char reg)  
{  
    unsigned char buf;  

    i2c_master_send(client, &reg, 1); // 傳送暫存器地址  
    i2c_master_recv(client, &buf, 1); // 接收暫存器的值  

    return buf;  

}

------------------------------------

4. i2c-dev使用例程


#include <stdio.h>   
#include <linux/types.h>   
#include <fcntl.h>   
#include <unistd.h>   
#include <stdlib.h>   
#include <sys/types.h>   
#include <sys/ioctl.h>   
#include <errno.h>   
#include <assert.h>   
#include <string.h>   
#include <linux/i2c.h>   
#include <linux/i2c-dev.h>   
   
int main()  
{  
         intfd, ret;  
         unsignedchar rdwr_addr = 0x42;   /* e2prom 讀寫地址 */  
         unsignedchar device_addr = 0x50; /* e2prom 裝置地址 */  
         unsignedchar data = 0x12;  /* 向e2prom寫的資料 */  
         structi2c_rdwr_ioctl_data e2prom_data;  
   
         fd= open("/dev/i2c/0", O_RDWR);  
         if(fd < 0) {  
                   perror("openerror");  
                   exit(1);  
         }  
   
         e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \  
                                               sizeof(structi2c_msg));  
         if(e2prom_data.msgs == NULL) {  
                   perror("mallocerror");  
                   exit(1);  
         }  
   
         ioctl(fd,I2C_TIMEOUT, 1); /* 設定超時 */  
         ioctl(fd,I2C_RETRIES, 2); /* 設定重試次數 */  
   
          
         /*向e2prom的rdwr_addr地址寫入資料data*/  
         e2prom_data.nmsgs= 1;  
         e2prom_data.msgs[0].len= 2;  
         e2prom_data.msgs[0].addr= device_addr;  
         e2prom_data.msgs[0].flags= 0;     /* write */  
   
          
         e2prom_data.msgs[0].buf= (unsigned char *)malloc(2);  
         e2prom_data.msgs[0].buf[0]= rdwr_addr;    /* write address */  
         e2prom_data.msgs[0].buf[1]= data;      /* write data */  
   
         ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);  
         if(ret < 0) {  
                   perror("writedata error");  
                   exit(1);  
         }  
         printf("writedata: %d to address: %#x\n", data, rdwr_addr);  
         data= 0;  /* be zero*/  
   
   
         /*從e2prom的rdwr_addr地址讀取資料存入buf*/  
         e2prom_data.nmsgs= 2;  
         e2prom_data.msgs[0].len= 1;  
         e2prom_data.msgs[0].addr= device_addr;  
//      e2prom_data.msgs[0].flags= 0;     /* write */   
         e2prom_data.msgs[0].buf= &rdwr_addr;  
   
         e2prom_data.msgs[1].len= 1;  
         e2prom_data.msgs[1].addr= device_addr;  
         e2prom_data.msgs[1].flags= 1;     /* read */  
         e2prom_data.msgs[1].buf= &data;  
   
         ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);  
         if(ret < 0) {  
                   perror("readerror");  
                   exit(1);  
         }  
         printf("read  data: %d from address: %#x\n", data,rdwr_addr);  
          
         free(e2prom_data.msgs);  
         close(fd);  
   
         return0;  
}
四:核心態實現裝置驅動方式。

         畫了一個流程圖框圖 大致概括了 Linux i2c 子系統驅動框架。通常只需要實現i2c裝置驅動層。i2c控制器驅動層大部分情況核心已經提供。

編寫驅動需要完成的工作
     編寫具體的I2C驅動時,工程師需要處理的主要工作如下:
  1. 提供I2C介面卡的硬體驅動,探測,初始化I2C介面卡(如申請I2C的I/O地址和中斷號),驅動CPU控制的I2C介面卡從硬體上產生。
  2. 提供I2C控制的algorithm, 用具體介面卡的xxx_xfer()函式填充i2c_algorithm的master_xfer指標,並把i2c_algorithm指標賦給i2c_adapter的algo指標。
  3. 註冊I2C裝置板級資訊。該資訊會在i2c_adapter註冊時賦給i2c_client結構體。
  4. 實現I2C裝置驅動中的i2c_driver,在probe函式中完成i2c裝置所對應的具體驅動結構註冊。
  5. 在實現I2C裝置所對應型別的具體驅動操作API(注:i2c_driver只是實現裝置與匯流排的掛接)。
上面的工作中前2個屬於I2C匯流排驅動,後面3個屬於I2C裝置驅動。 通常只需要實現i2c裝置驅動。 例項:根據開發客戶驅動程式步驟實現對i2c裝置at24c02的讀寫操作。

(1)板級資訊註冊,系統會根據該資訊生成對應的i2c_client結構。

static struct i2c_board_info i2c_devices[] __initdata = {  
  
    {I2C_BOARD_INFO("24c02", 0x50), },  
  
     {}  
  
}; 
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices)); 
(2) 裝置驅動實現
#include <linux/kernel.h>  
#include <linux/module.h>  
#include <linux/fs.h>  
#include <linux/slab.h>  
#include <linux/init.h>  
#include <linux/list.h>  
#include <linux/i2c.h>  
#include <linux/i2c-dev.h>  
#include <linux/smp_lock.h>  
#include <linux/jiffies.h>  
#include <asm/uaccess.h>  
#include <linux/delay.h>  
  
  
#define DEBUG 1  
#ifdef DEBUG  
#define dbg(x...) printk(x)  
#else   
#define dbg(x...) (void)(0)  
#endif  
  
#define I2C_MAJOR 89  
#define DEVICE_NAME "at24c02"  
static struct class *my_dev_class;  
static struct i2c_client *my_client;  
static struct i2c_driver my_i2c_driver;  
  
  
static struct i2c_device_id my_ids[] = {  
    {"24c01",0x50},  
    {"24c02",0x50},  
    {"24c08",0x50},  
    {}  
};  
  
MODULE_DEVICE_TABLE(i2c,my_ids);  
  
static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)  
{  
    int res;  
    struct device *dev;  
  
    dbg("probe:name = %s,flag =%d,addr = %d,adapter = %d,driver = %s\n",client->name,  
         client->flags,client->addr,client->adapter->nr,client->driver->driver.name );  
  
    dev = device_create(my_dev_class, &client->dev,  
                     MKDEV(I2C_MAJOR, 0), NULL,  
                     DEVICE_NAME);  
    if (IS_ERR(dev))  
    {  
        dbg("device create error\n");  
        goto out;  
    }  
    my_client = client;  
      
    return 0;  
out:  
    return -1;  
}  
static int  my_i2c_remove(struct i2c_client *client)  
{  
  
    dbg("remove\n");  
    return 0;  
}  
  
static ssize_t at24c02_read(struct file *fd, char *buf, ssize_t count, loff_t *offset)  
{  
    char *tmp;  
    int ret;  
    char data_byte;  
    char reg_addr = 0,i;  
    struct i2c_client *client = (struct i2c_client*) fd->private_data;  
    struct i2c_msg msgs[2];  
  
    dbg("read:count = %d,offset = %ld\n",count,*offset);  
    tmp = kmalloc(count,GFP_KERNEL);  
  
    if (!tmp)  
    {  
        dbg("malloc error in read function\n");  
        goto out;  
    }  
  
    reg_addr = *offset;  
    msgs[0].addr = client->addr;  
    msgs[0].flags = client->flags & (I2C_M_TEN|I2C_CLIENT_PEC) ;  
    msgs[0].len = 1;  
    msgs[0].buf = (char *)®_addr;  
      
    msgs[1].addr= client->addr;  
    msgs[1].flags = client->flags & (I2C_M_TEN|I2C_CLIENT_PEC);  
    msgs[1].flags |= I2C_M_RD;  
    msgs[1].len = count;  
    msgs[1].buf = (char*)tmp;  
  
    ret = i2c_transfer(client->adapter,&msgs,2);  
    if (ret != 2)  
        goto out;  
    if (copy_to_user(buf, tmp, count))  
        goto out;  
      
    kfree(tmp);  
    return count;  
out:  
    kfree(tmp);  
    return -1;    
      
}  
  
  
static int at24c02_ioctl(struct file *fd, unsigned int cmd, unsigned long arg)  
{  
    dbg("ioctl code ...\n");  
    return 0;  
}  
  
static ssize_t at24c02_write(struct file *fd, char *buf, ssize_t count, loff_t *offset)  
{  
    int ret,i;  
    char *tmp;  
    int errflg;  
    struct i2c_msg msg;  
    struct i2c_client *client = (struct i2c_client*) fd->private_data;  
    char tmp_data[2];  
  
    dbg("write:count = %d,offset = %ld\n",count,*offset);  
    tmp = kmalloc(count, GFP_KERNEL);  
    if (!tmp)  
        goto out;  
    if (copy_from_user(tmp, buf, count))  
        goto out;  
    msg.addr = client->addr;  
    msg.flags = client->flags & (I2C_M_TEN | I2C_CLIENT_PEC);  
    for (i = 0; i < count; i++) {  
        msg.len = 2;  
        tmp_data[0] = *offset + i;  
        tmp_data[1] = tmp[i];  
        msg.buf = tmp_data;  
        ret = i2c_transfer(client->adapter,&msg,1);  
        if (ret != 1)  
            goto out;  
        msleep(1);  
    }   
    kfree(tmp);  
  
    return ((ret == 1) ? count:ret);  
out:  
    kfree(tmp);  
    return -1;  
      
}  
static int at24c02_open(struct inode *inode, struct file *fd)  
{  
  
    fd->private_data =(void*)my_client;  
    return 0;  
  
}  
  
static int at24c02_release(struct inode *inode, struct file *fd)  
{  
    dbg("release\n");  
    fd->private_data = NULL;  
      
    return 0;     
  
}  
  
static const struct file_operations i2c_fops = {  
    .owner = THIS_MODULE,  
    .open   = at24c02_open,  
    .read  = at24c02_read,  
    .write = at24c02_write,  
    .unlocked_ioctl = at24c02_ioctl,  
    .release = at24c02_release,  
};  
  
static struct i2c_driver my_i2c_driver = {  
    .driver = {  
        .name = "i2c_demo",  
        .owner = THIS_MODULE,  
    },  
    .probe = my_i2c_probe,  
    .remove = my_i2c_remove,  
    .id_table = my_ids,  
};  
  
static int __init my_i2c_init(void)  
{  
    int res;  
      
      
    res = register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);  
    if (res)  
    {  
        dbg("register_chrdev error\n");  
        return -1;  
    }  
    my_dev_class = class_create(THIS_MODULE, DEVICE_NAME);  
    if (IS_ERR(my_dev_class))  
    {  
        dbg("create class error\n");  
        unregister_chrdev(I2C_MAJOR, DEVICE_NAME);  
        return -1;  
    }  
    return i2c_add_driver(&my_i2c_driver);  
}  
  
static void __exit my_i2c_exit(void)  
{  
    unregister_chrdev(I2C_MAJOR, DEVICE_NAME);  
    class_destroy(my_dev_class);  
    i2c_del_driver(&my_i2c_driver);  
      
}  
  
MODULE_AUTHOR("itspy<[email protected]>");  
MODULE_DESCRIPTION("i2c client driver demo");  
MODULE_LICENSE("GPL");  
module_init(my_i2c_init);  
module_exit(my_i2c_exit);  

參考部落格:

http://blog.csdn.net/bingqingsuimeng/article/details/7937898

http://blog.csdn.net/onetwothreef/article/details/50275237

http://blog.chinaunix.net/uid-27041925-id-3671725.html  等等。