I2C设备驱动的编写(二)

来源:互联网 发布:淘宝一元云购 编辑:程序博客网 时间:2024/04/28 03:05

来自:http://blog.chinaunix.net/uid-27041925-id-3630925.html

作者:曹老师,华清远见嵌入式学院讲师。

前面我们说了如何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);



0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 聚星输了很多钱怎么办 弹力运动裤被烟烧了个洞怎么办 生完宝宝胯宽怎么办 黑色纯棉裤子洗的发白怎么办 金盾保险柜密码忘了怎么办 装修好的房子漏水怎么办 刚装修的房子墙面开裂怎么办 刚装修的房子有味道怎么办 代销产品规格填写不完整怎么办 我的信息被泄露怎么办 进入不良网站手机发信息怎么办 发不良信息被停机了怎么办 手机qq登录显示被冻结怎么办 qq账户被冻结了怎么办 qq钱包账户被永久冻结怎么办 怎么办转让费还没给我 收钱吗不能抵用红包怎么办 红包抵扣被关了怎么办 天猫客户给差评怎么办 淘宝闪电退款有纠纷怎么办呀 手机屏幕右下角出现彩色点怎么办 在超市买到假货怎么办 天猫超市多发货怎么办 天猫中不小心取消退款了怎么办 天猫超市写错了怎么办 二维码收付款不到红包怎么办 天猫优惠劵过期了怎么办 天猫购物津贴用不完怎么办 天猫上买的大件东西实物不符怎么办 天猫上面料成分与实物不符怎么办 闲鱼发货与实物不符怎么办 天猫超市买贵了怎么办 天猫超市里购买的东西退货怎么办 淘宝店上传的图片不清楚怎么办 微信图片打印出来不清楚怎么办 微信图片打印不清楚怎么办 淘宝上传商品视频不清楚怎么办 手机安装器没了怎么办 我不做直播换工作怎么办 天猫发票被投诉怎么办 天猫机顶盒闪退怎么办