实例解析linux内核I2C体系结构(2)

作者:刘洪涛,华清远见嵌入式学院讲师。

四、在内核里写i2c设备驱动的两种方式

前文介绍了利用/dev/i2c-0在应用层完成对i2c设备的操作,但很多时候我们还是习惯为i2c设备在内核层编写驱动程序。目前内核支持两种编写i2c驱动程序的方式。下面分别介绍这两种方式的实现。这里分别称这两种方式为“Adapter方式(LEGACY)”和“Probe方式(new style)”。

(1) Adapter方式(LEGACY)

(下面的实例代码是在2.6.27内核的pca953x.c基础上修改的,原始代码采用的是本文将要讨论的第2种方式,即Probe方式)

●    构建i2c_driver

static struct i2c_driver pca953x_driver = {
                .driver = {
                                    .name= "pca953x", //名称
                                },
                .id= ID_PCA9555,//id号
                .attach_adapter= pca953x_attach_adapter, //调用适配器连接设备
                .detach_client= pca953x_detach_client,//让设备脱离适配器
        };

●    注册i2c_driver

static int __init pca953x_init(void)
        {
                return i2c_add_driver(&pca953x_driver);
        }
        module_init(pca953x_init);

●    attach_adapter动作

执行i2c_add_driver(&pca953x_driver)后会,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:

static int pca953x_attach_adapter(struct i2c_adapter *adapter)
        {
                return i2c_probe(adapter, &addr_data, pca953x_detect);
                /*
                adapter:适配器
                addr_data:地址信息
                pca953x_detect:探测到设备后调用的函数
                */
        }

地址信息addr_data是由下面代码指定的。
        /* Addresses to scan */
        static unsigned short normal_i2c[] = {0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END};
        I2C_CLIENT_INSMOD;

注意:normal_i2c里的地址必须是你i2c芯片的地址。否则将无法正确探测到设备。而I2C_ CLIENT_INSMOD是一个宏,它会利用normal_i2c构建addr_data。

●    构建i2c_client,并注册字符设备驱动

i2c_probe在探测到目标设备后,后调用pca953x_detect,并把当时的探测地址address作为参数传入。

static int pca953x_detect(struct i2c_adapter *adapter, int address, int kind)
        {
                struct i2c_client *new_client;
                struct pca953x_chip *chip; //设备结构体
                int err = 0,result;
                dev_t pca953x_dev=MKDEV(pca953x_major,0);//构建设备号,根据具体情况设定,这里我只考虑了normal_i2c中只有一个地址匹配的情况。
                if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_WORD_DATA))//判定适配器能力
                goto exit;
                if (!(chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL))) {
                        err = -ENOMEM;
                        goto exit;
                }
                /****构建i2c-client****/
                chip->client=kzalloc(sizeof(struct i2c_client),GFP_KERNEL);
                new_client = chip->client;
                i2c_set_clientdata(new_client, chip);
                new_client->addr = address;
                new_client->adapter = adapter;
                new_client->driver = &pca953x_driver;
                new_client->flags = 0;
                strlcpy(new_client->name, "pca953x", I2C_NAME_SIZE);
                if ((err = i2c_attach_client(new_client)))//注册i2c_client
                goto exit_kfree;
                if (err)
                goto exit_detach;
                if(pca953x_major)
                {
                        result=register_chrdev_region(pca953x_dev,1,"pca953x");
                }
                else{
                        result=alloc_chrdev_region(&pca953x_dev,0,1,"pca953x");
                        pca953x_major=MAJOR(pca953x_dev);
                }
                if (result < 0) {
                        printk(KERN_NOTICE "Unable to get pca953x region, error %d/n", result);
                        return result;
                }
                pca953x_setup_cdev(chip,0); //注册字符设备,此处不详解
                return 0;
                exit_detach:
                i2c_detach_client(new_client);
        exit_kfree:
                kfree(chip);
        exit:
                return err;
        }

i2c_check_functionality用来判定设配器的能力,这一点非常重要。你也可以直接查看对应设配器的能力,如

static const struct i2c_algorithm smbus_algorithm = {
                .smbus_xfer= i801_access,
                .functionality= i801_func,
        };
        static u32 i801_func(struct i2c_adapter *adapter)
        {
                        return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
                    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
                I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK
                                | (isich4 ? I2C_FUNC_SMBUS_HWPEC_CALC : 0);
        }

●    字符驱动的具体实现

struct file_operations pca953x_fops = {
                .owner = THIS_MODULE,
                .ioctl= pca953x_ioctl,
                .open= pca953x_open,
                .release =pca953x_release,
        };

字符设备驱动本身没有什么好说的,这里主要想说一下,如何在驱动中调用i2c设配器帮我们完成数据传输。

目前设配器主要支持两种传输方法:smbus_xfer和master_xfer。一般来说,如果设配器支持了master_xfer那么它也可以模拟支持smbus的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。

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);

master_xfer中的参数设置,和前面的用户空间编程一致。现在只是要在驱动中构建相关的参数然后调用i2c_transfer来完成传输既可。

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)

smbus_xfer中的参数设置及调用方法如下:

static int pca953x_write_reg(struct pca953x_chip *chip, int reg, uint16_t val)
        {
                int ret;
                ret = i2c_smbus_write_word_data(chip->client, reg << 1, val);
                if (ret < 0) {
                                dev_err(&chip->client->dev, "failed writing register/n");
                                        return -EIO;
                                }
                return 0;
        }

上面函数完成向芯片的地址为reg的寄存器写一个16bit的数据。i2c_smbus_write_word_data的实现如下:

s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 command, u16 value)
        {
                union i2c_smbus_data data;
                data.word = value;
                return i2c_smbus_xfer(client->adapter,client->addr,client->flags,
                                                                        I2C_SMBUS_WRITE,command,
                                                                        I2C_SMBUS_WORD_DATA,&data);
        }

从中可以看出smbus传输一个16位数据的方法。其它操作如:字符写、字符读、字读、块操作等,可以参考内核的i2c-core.c中提供的方法。

●    注销i2c_driver

static void __exit pca953x_exit(void)
        {
                i2c_del_driver(&pca953x_driver);
        }
        module_exit(pca953x_exit);

●    detach_client动作

顺序调用内核中注册的适配器来断开我们注册过的i2c设备。此过程通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:

static int pca953x_detach_client(struct i2c_client *client)
        {
                int err;
                struct pca953x_chip *data;
                if ((err = i2c_detach_client(client)))//断开i2c_client
                return err;
                data=i2c_get_clientdata(client);
                cdev_del(&(data->cdev));
                unregister_chrdev_region(MKDEV(pca953x_major, 0), 1);
                kfree(data->client);
                kfree(data);
                return 0;
        }

(2) Probe方式(new style)

●    构建i2c_driver

和LEGACY方式一样,也需要构建i2c_driver,但是内容有所不同。

static struct i2c_driver pca953x_driver = {
                .driver = {
                        .name= "pca953x",
                        },
                        .probe= pca953x_probe, //当有i2c_client和i2c_driver匹配时调用
                        .remove= pca953x_remove,//注销时调用
                        .id_table= pca953x_id,//匹配规则
        };

●    注册i2c_driver

static int __init pca953x_init(void)
        {
                return i2c_add_driver(&pca953x_driver);
        }
        module_init(pca953x_init);

在注册i2c_driver的过程中,是将driver注册到了i2c_bus_type的总线上。此总线的匹配规则是:

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                                                                                                const struct i2c_client *client)
        {
                while (id->name[0]) {
                        if (strcmp(client->name, id->name) == 0)
                                return id;
                        id++;
                }
                return NULL;
        }

可以看出是利用i2c_client的名称和id_table中的名称做匹配的。本驱动中的id_table为

static const struct i2c_device_id pca953x_id[] = {
                { "pca9534", 8, },
                { "pca9535", 16, },
                { "pca9536", 4, },
                { "pca9537", 4, },
                { "pca9538", 8, },
                { "pca9539", 16, },
                { "pca9554", 8, },
                { "pca9555", 16, },
                { "pca9557", 8, },
                { "max7310", 8, },
                { }
        };

看到现在我们应该会有这样的疑问,在Adapter模式中,i2c_client是我们自己构造出来的,而现在的i2c_client是从哪来的呢?看看下面的解释

●    注册i2c_board_info

对于Probe模式,通常在平台代码中要完成i2c_board_info的注册。方法如下:

static struct i2c_board_info __initdata test_i2c_devices[] = {
                {
                        I2C_BOARD_INFO("pca9555", 0x27),//pca9555为芯片名称,0x27为芯片地址
                        .platform_data = &pca9555_data,
                }, {
                        I2C_BOARD_INFO("mt9v022", 0x48),
                        .platform_data = &iclink[0], /* With extender */
                }, {
                        I2C_BOARD_INFO("mt9m001", 0x5d),
                        .platform_data = &iclink[0], /* With extender */
                },
        };
        i2c_register_board_info(0, test_i2c_devices,ARRAY_SIZE(test_i2c_devices)); //注册

i2c_client就是在注册过程中构建的。但有一点需要注意的是i2c_register_board_info并没有EXPORT_SYMBOL给模块使用。

●    字符驱动注册

在Probe方式下,添加字符驱动的位置在pca953x_probe中。

static int __devinit pca953x_probe(struct i2c_client *client,const struct i2c_device_id *id)
        {
                        ……
                        /****字符设备驱动注册位置****/
                        ……
                        return 0;
        }

●    注销i2c_driver

static void __exit pca953x_exit(void)
        {
                i2c_del_driver(&pca953x_driver);
        }
        module_exit(pca953x_exit);

●    注销字符设备驱动

在Probe方式下,注销字符驱动的位置在pca953x_remove中。

static int __devinit pca953x_remove (struct i2c_client *client)
        {
                ……
                /****字符设备驱动注销的位置****/
                ……
                return 0;
        }

●    I2C设备的数据交互方法(即:调用适配器操作设备的方法)和Adapter方式下相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/434701.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

制作完整的java可执行文件

帮教务处的老师做了一个小软件&#xff0c;所以学习了一下制作java可执行文件&#xff0c;在此分享一下。 说明&#xff1a;因为是做完很长一段时间后再截的图&#xff0c;可能有点纰漏&#xff0c;大体应该没什么问题。 我的eclipse工程文件目录: bin | images&#xff08;放图…

I2C设备驱动编写,struct i2c_device_id,struct i2c_driver,i2c_add_driver,i2c_register_board_info

我的理念&#xff1a;简单实用即可&#xff0c;不要搞一堆源码出来&#xff0c;结果让人看了以后还不知道怎么用&#xff0c;看我的&#xff1a; 1、在arch/arm/mach-xxx/ 自己的平台文件里添加i2c信息&#xff0c;美其名曰&#xff1a;i2c_board_info 例如&#xff1a; stat…

ajax中async_小猿圈web前端之ajax的同步和异步有怎样的区别?

对于ajax我们应该知道ajax是主要用来在前端页面中向服务器后端请求数据&#xff0c;ajax中根据async的值不同分为同步&#xff08;async false&#xff09;和异步&#xff08;async true&#xff09;两种执行方式&#xff0c;那么&#xff0c;ajax的同步和异步请求两种方式有…

[恢]hdu 2012

2011-12-12 05:27:32 地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid2012 题意&#xff1a;中文题。 mark&#xff1a;wa了一次&#xff0c;超2的。自己以为聪明地写了一个预处理&#xff0c;结果忘记把表达式写进去了&#xff0c;IsPrme的参数直接写了个i&#…

m_pMainWnd = dlg错误解决方法

这两天在写MFC程序的时候经常碰到这种情况&#xff0c;开始真的很让我速手无策呀。在网上的我看到有两种可以解决这种问题的方法&#xff1a;一是&#xff0c;直接把这句话注释掉&#xff0c;然后你再运行也就可以通过了。还有一种就是&#xff0c;你重新‘全部重建‘&#xff…

mysql存储引擎的区别_Mysql的两种存储引擎以及区别

一、Mysql的两种存储引擎1、MyISAM&#xff1a;①不支持事务&#xff0c;但是整个操作是原子性的(事务具备四种特性&#xff1a;原子性、一致性、隔离性、持久性)②不支持外键&#xff0c;支持表锁&#xff0c;每次所住的是整张表MyISAM的表锁有读锁和写锁(两个锁都是表级别)&a…

带给你灵感的3D街画艺术设计

3D街头艺术画已在16世纪以来意大利文艺复兴时期的Madonnari画家创造了令人惊叹的壁画来装饰豪华别墅的内墙。3D艺术也可以跟踪它的航线&#xff0c;。这里有一些新的图像&#xff0c;这将使你想知道它是如何可能的使东西是如此逼真&#xff0c;3D设计们不要错过 1。 &#xff0…

[vc]如何对radio按钮分组

如何使用多组&#xff1f;多组和一组是一样的使用&#xff0c;只要搞清楚哪个是哪一组的就行了。再为对话框添加Radio3和Radio4。很简单&#xff0c;先为这些RadioButton排个顺序&#xff0c;就是排列他们的TABORDER。在对话框资源面板上CtrlD&#xff0c;然后按你自己的理想顺…

[原]2011年度生活三层总结

一年了。 想到自己从开始没有目标&#xff0c;误打误撞的来到了提高班到现在的成长。我是多么的幸运&#xff0c;幸运的来到廊坊师范&#xff08;现在都要称之为母校了&#xff09;&#xff0c;幸运的来到了提高班&#xff0c;遇到了米老师&#xff0c;在此感谢。 一年了。改变…

jdbc存储过程mysql_JDBC对MySQL数据库存储过程的调用

1、什么是存储过程存储过程(英文&#xff1a;Stored Procedure)是在大型数据库系统中&#xff0c;为了完成特定功能而编写的一组的SQL语句集。存储过程经编译存储在数据库中&#xff0c;用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。2、与一般SQL语…

vi快捷键

u 撤销你刚才做的动作 ctrlr 恢复你刚才撤销的动作 复制粘贴 1. 将光标移动到将要复制的行处&#xff0c;按yy进行复制当前行&#xff08;按nyy复制n行&#xff09;&#xff0c;再移动到粘贴位置&#xff0c;按p进行粘贴。 2. 将光标移到复制的…

【转】:andriod 开发环境工具下载地址(2)

http://www.cnblogs.com/oudi/archive/2011/06/30/2094674.htmlandriod 开发环境工具下载地址注意内容1. jdk:http://www.oracle.com/technetwork/java/javase/downloads/index.html2.eclipse:http://www.eclipse.org/downloads/ 3.andrioid sdk 下载地址:http://developer.and…

hp laser103 属性没有配置项_哦?在hp打印机面板上就可以更改打印机ip地址

修改打印机IP的方法有很多但都没有直接从打印机控制面板上修改方便过瘾虽然有些机器不支持但是惠普大部分机器还是可以的今天我们就以 LaserJet M227 系列打印机为例hp官方为大家介绍一下具体的设置方法步骤一&#xff1a;打印配置报告查看有效IP地址如果机器是2行控制面板 1.在…

vb连接odbc连接mysql数据库_vb6通过ODBC连接mysql数据库

在尝试连接数据库之前, 需要简单说明一下连接数据库的两种最常见的方式: 一种是通过建立 DSN 来连接, 这种比较麻烦, 程序移植性差; 我一般采取利用 ADODB 连接数据库的方式, 这种比较灵活方便, 不需额外工作, 方便程序移植.下面的例子就是通过 ADODB 连接 MySQL 数据库:首先, …

ABS_MT_TOUCH_MAJOR和ABS_MT_WIDTH_MAJOR

通常的多点触摸周期可以描述为&#xff1a;ABS_MT_*|SYN_MT_REPORT|ABS_MT_*|SYN_MT_REPORT|SYN_REPORT有些ABS_MT事件是必须实现的&#xff0c;如ABS_MT_POSITION_X和ABS_MT_POSITION_Y.而某些事件&#xff0c;如ABS_MT_TOUCH_MAJOR和ABS_MT_WIDTH_MAJOR是否需要实现则取决于具…

Java 笔记

1 词语 JRE —— Java运行时环境&#xff08;Java Runtime Environment&#xff09; JVM —— Java虚拟机 JDT —— Java的开发环境&#xff0c;可以方便的对代码进行开发及调试功能 SDK —— 软件开发工具包&#xff08;Software Development Kit&#xff09; IDE —— 集成开…

Windows修改注册表按键映射

Scancode Map 注册表项来实现键盘按键映射。 位于注册表的&#xff1a; HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout\ 下新建一个二进制值项&#xff1a; "Scancode Map" 这个项目可实现对键盘按键的映射。这里映射的意思可理解为“替换”…

python去掉最高分和最低分_去掉一个最高分,去掉一个最低分求平均值(trimmean)...

如下图&#xff1a;演讲比赛&#xff0c;要求去掉一个最高分&#xff0c;去掉一个对低分后求平均值。当然这个太简单了&#xff0c;我们可以用max求出最大值&#xff0c;用min求出最小值&#xff0c;然后sum求出数据总和&#xff0c;用(总和-最大值-最小值)/(数据总个数-2)。思…

Android的.apk软件安装到sd卡的方法

对于Android 2.2开始的软件可以装到到SD上的支持&#xff0c;对于adb命令而言也有了改进&#xff0c;对于Android 2.2固件中的pm命令开支持安装选项参数&#xff0c;这里新增了参数setInstallLocation&#xff0c;这里我们可以用adb通过shell命令执行linux的pm添加一些参数即可…

[Oracle整理]CASE-END

说明&#xff1a;本内容是工作用到的知识点整理&#xff0c;来自工作中和网络。 代码于Oracle9上测试。 作用: 1可用来进行数据资料行转列的功能 2可用来对数据进行判断&#xff0c;类似decode&#xff0c;但CASE语句在处理范围条件的时候会显得非常灵活。如果只是需要匹配少量…