您的位置:首页 > 其它

I2C设备驱动的编写(二)

2017-01-12 18:52 260 查看
http://blog.csdn.net/bingqingsuimeng/article/details/8482224

前面我们说了如何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 struct i2c_board_info i2c_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);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: