linux下 i2c设备驱动开发(2)

转载:http://blog.chinaunix.net/uid-26552187-id-3531086.html

I2C总线是有Philips公司开发的,它是一种比较简单的总线,接线简单:只有两根线数据线(SCL)和时钟线(SDA),控制简单。所以一些封装较小的器件多使用I2C总线,常见的使用I2C总线的设备有EEPROM、rtc及一些传感器。这里我们介绍下基于linux的I2C设备驱动的编写。

I2C设备驱动的编写有多种方式:

一种是直接操作CPU的I2C控制器,正对于某一个设备写一个字符驱动,这种驱动相对来说比较直接,不需要太依赖于内核相关配置,但是这类设备驱动依赖CPU,可移植性较差。

一种是基于linux内核I2C子系统完成设备驱动的编写,一般内核会继承相关CPU的控制器驱动即使没有也可以通过技术支持可以获得,所以我们只需要使用linux下I2C子系统提供的相关接口来构建我们的设备驱动就行了。这样我们的设备驱动并不依赖于某一个特定的CPU,可移植性较好。
在写驱动之前我们先了解下I2C总线中几个比较重要的概念:

1、 地址

I2C总线上可以连接多个相同或不同的设备,总线怎么样才能知道数据应该发送到那个设备呢,这里需要一个地址来唯一的标识一个设备。I2C设备地址有7位地址和10位地址,那么这个地址是怎么来的呢,其实这个地址我们可以通过相关的芯片手册获得,这里通过一个EEPROM和一个温度传感器来说明。
EEPROM(AT24C02/04/08/16)芯片手册上有如下说明:

再结合原理图

在通过芯片手册我们可以知道EEPROM的地址的前四位为1010,通过原理图A0/A1/及NC的状态我们可以知道后三位为000,这样我们就知道这个EEPROM在I2C总线上的地址为7’b1010000。

同样我们可以通过如下内容知道温度传感器的地址为7’b1001000

芯片手册

原理图

2、 时序

不同的I2C设备有不同的时序,我们也可以说是不同的协议,我们需要了解一些时序相关的东西,我们发送数据是什么时候开始什么时候结束,怎么发送都由这个时序决定。

开始/停止

完整时序图:

现在的CPU多数都有I2C控制器,我们不需要太关心具体时序的实现,这些都由控制器去完成,并且内核已经集成多数CPU的I2C控制器驱动,我们写设备驱动就是按照I2C子系统的要求,为它提供需要的数据即可。

I2C子系统下设备驱动有两种模式,一种是用户模式设备驱动这种驱动依赖I2C子系统中的i2c-dev这个驱动,我们需要在应用程序去封装数据,这需要应用程序的开发人员具备相当的硬件基础,另外一种是普通的设备驱动。分别看下这两种方法的具体实现过程。

一、用户态模式驱动实现:

相关结构体:

struct i2c_msg {
                __u16 addr; /* slave address */
                __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 */
                __u16 len; /* msg length */
                __u8 *buf; /* pointer to msg data */
        };
                struct i2c_rdwr_ioctl_data {
                struct i2c_msg *msgs; /* pointers to i2c_msgs */
                __u32 nmsgs; /* number of i2c_msgs */
        };

上面就是我们向底层传递的结构,我们需要把我们的时序封装成这样的结构然后传递下去就行了。

AT24c04时序

转化为消息结构为:
        e2prom_data.nmsgs=2;
        (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
        (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
        (e2prom_data.msgs[0]).flags=0; //write
        (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
        (e2prom_data.msgs[0]).buf[0]=0x0; //e2prom数据地址
        (e2prom_data.msgs[1]).len=1; //读出的数据
        (e2prom_data.msgs[1]).addr=0x50; // e2prom 设备地址
        (e2prom_data.msgs[1]).flags=I2C_M_RD; //read
        (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
        (e2prom_data.msgs[1]).buf[0]=0; //初始化读缓冲

这里我们封装了两个消息,在这个时序中操作模式改变了,所以我们必须封装为两个时序,如果操作模式不变封装一个消息就可以了比如如下时序:

e2prom_data.nmsgs=1;
        (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
        (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
        (e2prom_data.msgs[0]).flags=0; //write
        (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(1);
        (e2prom_data.msgs[0]).buf[0]=0x0; //e2prom数据地址

接着我们可以看看别的设备的时序大家可以发现大同小异!

我们把刚才封装的消息通过ioctl发下去就能够完成数据的读写了。例程如下:

 #include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
int main()
{
    int fd,ret;
    struct i2c_rdwr_ioctl_data e2prom_data;
    fd=open("/dev/i2c-0",O_RDWR);
            if(fd<0)
            {
                    perror("open error");
            }
    e2prom_data.nmsgs=2; 
    e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
    if(!e2prom_data.msgs)
    {
            perror("malloc error");
            exit(1);
    }
    ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
    ioctl(fd,I2C_RETRIES,2);/*重复次数*/
    sleep(1);

    e2prom_data.nmsgs=2;
    (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
    (e2prom_data.msgs[0]).addr=0x48;	// e2prom 设备地址
    (e2prom_data.msgs[0]).flags=0;	//write
    (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
    (e2prom_data.msgs[0]).buf[0]=0x0;	//e2prom数据地址
    (e2prom_data.msgs[1]).len=2;	//读出的数据
    (e2prom_data.msgs[1]).addr=0x48;	// e2prom 设备地址
    (e2prom_data.msgs[1]).flags=I2C_M_RD;//read
    (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(2);//存放返回值的地址。
    (e2prom_data.msgs[1]).buf[0]=0;	//初始化读缓冲
    (e2prom_data.msgs[1]).buf[1]=0;	//初始化读缓冲
    ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
    if(ret<0)
    {
            perror("ioctl error2");
    }
    printf("%x",(e2prom_data.msgs[1]).buf[0]);
    printf("%x\n",(e2prom_data.msgs[1]).buf[1]);

    close(fd);
    return 0;
}


前面我们说了如何I2C用户模式驱动,这种驱动基于I2C子系统,但是他对于应用程序开发人员的要求较高,需要应用程序开发人员了解硬件的一些东西,比如时序,地址等等,而多数时候应用程序开发人员是按照操作文件的方法操作设备,所以我们更希望用一些更简单的接口去访问。也就是我们今天的内容——基于I2C子系统的字符驱动。

二、基于I2C子系统的字符驱动(内核态

I2C子系统的代码分为三部分如图:

Host:主机控制器驱动

Device:设备驱动代码

Core: 核心代码,提供设备与控制器的接口

一、主机控制器驱动

Linux下主机控制器驱动,大多数是BSP提供的,这里不多说,简单说下它主要干的活。I2C主机控制器在内核里又叫适配器,用结构i2c_adapter描述。

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 */
        ……
        };

struct i2c_algorithm 提供设备访问控制器的接口,定义如下:

struct i2c_algorithm {
        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);
        u32 (*functionality) (struct i2c_adapter *);
        };

其中master_xfer就是我们给设备端提供的接口,这部分内容按照芯片手册中寄存器的操作实现数据的收发。

最终我们将i2c_adapter注册到系统中,使用如下函数:
        int i2c_add_numbered_adapter(struct i2c_adapter *);

二、核心代码

这部分就不说了,刚才我们介绍的函数全部是核心代码提供,它主要是提供标准的统一的接口。

三、设备代码

基于I2C的字符驱动的编写首先我们需要了解几个特定的结构。

1、i2c_bus_type

i2c总线结构定义了一些总线相关的方法,这里我们关系的是i2c_driver和i2c_client的配备规则,为什么匹配呢,i2c_client携带硬件信息,而i2c_driver只负责操作设备而不管操作的是那个设备它需要的硬件信息有i2c_client提供,所以需要i2c_client和i2c_driver协同操作,而一个系统中i2c_driver和i2c_client都可能有多个,如何得到自己的另一半就是我所说的匹配规则,i2c_bus_type的匹配规则定义如下:

struct bus_type i2c_bus_type = {
        .name = "i2c",
        .match = i2c_device_match,
        .probe = i2c_device_probe,
        .remove = i2c_device_remove,
        .shutdown = i2c_device_shutdown,
        .pm = &i2c_device_pm_ops,
        };

static int i2c_device_match(struct device *dev, struct device_driver *drv)
        {
                struct i2c_client *client = i2c_verify_client(dev);
                struct i2c_driver *driver;
                if (!client)
                        return 0;
                /* Attempt an OF style match */
                if (of_driver_match_device(dev, drv))
                        return 1;
                driver = to_i2c_driver(drv);
                /* match on an id table if there is one */
                if (driver->id_table)
                        return i2c_match_id(driver->id_table, client) != NULL;
                return 0;
        }

我们发现i2c总线的匹配规则是id或name两种,id优先级高

2、板级结构:

struct i2c_board_info {
       
 char type[I2C_NAME_SIZE];        //芯片类型,其实也就是名字,用来匹配
        unsigned short flags;        //标志位,一些特定的标志
        unsigned short addr;        //地址,从设备地址,不包括读写位
        void *platform_data;        //用来传递一些私有数据
        struct dev_archdata *archdata;        //同上
        struct device_node *of_node;
        int        irq;
        };

板子上没有一个I2C的设备,我们就要注册一个这样的结构体,到内核里边,这部分代码一般添加在平台代码里边,注册函数如下:

i2c_register_board_info(int busnum, struct i2c_board_info const *info,unsigned n);
        busnum 现在很多CPU有多条I2C总线,这个参数表示第几条总线
        info 是一个结构体数据,表示我们要注册的I2C设备
        n 表示我们注册了几个I2C设备

通过上面函数就能把设备注册到系统中。结构如图:

3、i2c_client

这个结构我们不需要操作,是操作系统即核心代码自动完成,这个过程其实是在注册i2c_adapter的时候完成的。即在函数i2c_add_numbered_adapter中完成,最终i2c_client携带者i2c_board_info和i2c_adapter的信息。

4、i2c_driver

这部分代码主要负责注册i2c_driver和匹配相应的i2c_client。I2c_driver定义如下:

struct i2c_driver {
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);
        struct device_driver driver;
        const struct i2c_device_id *id_table;
        ……
        };

注册函数如下:
        int i2c_add_driver(struct i2c_driver *driver);

这个函数负责注册i2c_driver并匹配i2c_client,当匹配到了对于的i2c_client,probe函数被执行,并且i2c_client被以参数的形式传递过来。我们可以通过i2c_client提供的硬件信息和操作接口操作我们想要的设备。

5、数据传输

数据传输结构:

struct i2c_msg {
        __u16 addr; /* slave address */
        __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_NOSTART        0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
        #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 */
        };

消息的封装与上节用户模式驱动相似,封装好消息使用如下函数提交给核心代码,最终通过控制器驱动发送给具体的设备。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
        adap 适配器,由client->adapter获得。
        msgs 消息
        num 消息个数

通过上面内容,就可以构建我们的基于linux下i2c子系统的设备驱动程序了,例程如下:

平台代码添加:

static structi2c_board_infoi2c_devs0[] __initdata = {
                {I2C_BOARD_INFO("lm75", 0x48),},
        };
        i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));

驱动代码:

#include < linux/module.h>
 #include < linux/kernel.h>
 #include < linux/init.h>
 #include < linux/fs.h>
 #include < linux/cdev.h>
 #include < linux/i2c.h>
 #include < linux/slab.h>
 #include < asm/uaccess.h>
 MODULE_LICENSE ("GPL");
 #define LM75_REG_CONF	        0x01
 static const u8 LM75_REG_TEMP[3] = {
 0x00,	        /* input */
 0x03,	        /* max */
 0x02,	        /* hyst */
 };
 struct lm75_data 
 {
         u16 temp[3]; /* Register values,
         0 = input
         1 = max
         2 = hyst */
 };
 static int lm75_major = 250;
 static int lm75_minor = 0;
 static int number_of_devices = 1;
 static dev_t devno = 0;
 static struct cdev cdev;
 static struct i2c_client *new_client;
 struct lm75_data *data;
 static int lm75_read_value(struct i2c_client *client)
 {
         struct i2c_msg msgs[2];
         int status;
         char buf1[2];
         char buf2[2];
         msgs[0].len = 1;
         msgs[0].addr = client->addr; // lm75 设备地址
         msgs[0].flags = 0;//write
         msgs[0].buf = buf1;
         msgs[0].buf[0] = LM75_REG_TEMP[0];
         msgs[1].len = 2;//读出的数据
         msgs[1].addr = client->addr;// lm75 设备地址 
         msgs[1].flags = I2C_M_RD;//read
         msgs[1].buf = buf2;//存放返回值的地址。
         status = i2c_transfer(client->adapter, msgs, 2);
         if(status < 0)
                 return status;
         printk("1 = %2x %2x\n", buf2[0], buf2[1]);
         return (buf2[0] << 8) | buf2[1];
 }
 static ssize_t lm75_read(struct file *file, char __user *buff, size_t count, loff_t *offset) 
 {
         int status;
         status = lm75_read_value(new_client);
         if(status < 0)
                 {
                         return status;
                 }
         printk("status = %x\n", status);
         if(copy_to_user(buff, (char *)&status, sizeof(status)))
                 return -EFAULT;
         return 0;
         }
 static int lm75_open(struct inode *inode, struct file *file)
 {
         return 0;
 }
 static int lm75_release(struct inode *inode, struct file *file)
 {
         return 0;
 }
 static struct file_operations lm75_fops = {
         .owner = THIS_MODULE,
         .read	= lm75_read,
         .open = lm75_open,
         .release	= lm75_release,
 };
 static int lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
 {
         int ret = 0;
         new_client = client;
         devno = MKDEV(lm75_major, lm75_minor);
         ret = register_chrdev_region(devno, number_of_devices, "lm75");
         if(ret)
                 {
                         printk("failed to register device number\n");
                         goto err_register_chrdev_region;
                 }
         cdev_init(&cdev, &lm75_fops);
         cdev.owner = THIS_MODULE;
         ret = cdev_add(&cdev, devno, number_of_devices);
         if(ret)
                 {
                         printk("failed to add device\n");
                         goto err_cdev_add;
                 }
         return 0;
         err_cdev_add:
         unregister_chrdev_region(devno, number_of_devices);
         err_register_chrdev_region:
         kfree(data);
         return ret;
 }
 static int lm75_remove(struct i2c_client *client)
 {
         cdev_del(&cdev);
         unregister_chrdev_region(devno, number_of_devices);
         return 0;
 }
 enum lm75_type {        /* keep sorted in alphabetical order */
         lm75,
         lm75a,
 };
 static const struct i2c_device_id lm75_ids[] = {
         { "lm75", lm75, },
         { "lm75a", lm75a, },
         { /* LIST END */ }
 };
 static struct i2c_driver lm75_driver = {
         .driver = {
                 .name = "lm75",
                 },
         .probe = lm75_probe,
         .remove = lm75_remove,
         .id_table	= lm75_ids,
 };
 static int __init s5pc100_lm75_init(void)
 {
         return i2c_add_driver(&lm75_driver);
 }
 static void __exit s5pc100_lm75_exit(void)
 {
         i2c_del_driver(&lm75_driver);
 }
 module_init(s5pc100_lm75_init);
 module_exit(s5pc100_lm75_exit); 


猜你喜欢

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