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/目录下的文件内容。如:

[root@zlg /]# cat /sys/class/i2c-dev/i2c-0/name
PNX4008-I2C0
[root@zlg /]# cat /sys/class/i2c-dev/i2c-1/name
PNX4008-I2C1
[root@zlg /]# 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  等等。

猜你喜欢

转载自blog.csdn.net/lingfeng5/article/details/73312728