Linux下I2C驱动分析(三)
2015-12-17 13:50
501 查看
分析了两天的I2C驱动,发现每次解决一个问题的时候都会带来新的问题,当大致读完MMA7660驱动程序的时候发现,作为一个字符设备I2C驱动,并不存在有open,close等接口,而我们知道,在Linux的世界里设备即文件,也就是操作设备就相当于读写文件,而在一个简单的字符设备里总会实现一个file_operation的结构体以实现用户层的调用,那么当我们打开一个I2C设备的时候open在哪里呢?
从框架开始说起,在网上寻找I2C框架的时候,我在很多地方看到一张图
既然大家都喜欢拿图说事,那么我也尝试着拿这个图说事,也许会有偏差和错误,所以还望看到这博客的各位指正。首先我们要知道,我们写的驱动(或者说移植的驱动,其实我目前做的也是移植)到底属于那一层呢?答案是图中的driver驱动层,但为什么呢?硬件实验控制层所说的硬件控制指的是Soc上I2C控制器的控制,而这一部分一般是Soc生产商做好了的,毕竟他们最懂Soc上I2C应该怎么跑,而我们只要把连接在Soc的I2C外设的一些特性告诉硬件控制代码,它就能根据需要让外设工作起来。
回到这张图,很明显可以知道用户程序最开始接触到的是client,也就是之前我说到的设备,其实这个设备是被封装过的,封装之后很合适的挂载在内核虚拟的I2C总线上,并且和同样封装过的i2c_driver进行绑定(具体可以查看昨天提到的这两个结构体/article/9015187.html,这两个结构体相互保存了彼此的信息),在Linux下操作一个设备,都是在/dev目录下打开对应的设备,前两天的内容虽然创建了client,但client->dev中并没有内容,这个时候用户还没办法操作设备,于是probe帮我们做了这么一件事(在MMA中probe函数内容很多,暂时移除一些和I2C框架没什么关系的内容和一些对理解I2C框架不重要错误处理,更方便阅读)
= hwmon_device_register(&client->dev);将client->dev注册成一个hwmon设备(G-sensor属于hardware monitor设备类),这样会在sys/class目录下创建文件夹,可以暴露给用户空间(暂时不做过多分析)。然后便是mma7660_idev = input_allocated_polled_device();自动创建一个轮询设备,并在之后把轮询函数mma7660_dev_poll注册进了mma7660_idev,然后又对一个输入子设备idev进行一些初始化工作并嵌入到了mma_idev->input(这些都是指针操作,只要传一个地址就行了),最后将mma7660_idev作为一个设备注册进内核,这个时候,这样一个轮询的输入设备就被创建完毕了,input->open,input->close都在这里实现了。
i2c_algorithm *algo
总结:作为I2C的client,如果框架图这样,它注册在I2C的虚拟总线上(这部分属于i2c-core实现的内容),工作在内核空间,它向上提供了open,close和poll接口,向下提供了adapter,继而实现硬件传输,而驱动程序中的probe函数,很重要的一个功能就是实现了这样一个client。
从框架开始说起,在网上寻找I2C框架的时候,我在很多地方看到一张图
既然大家都喜欢拿图说事,那么我也尝试着拿这个图说事,也许会有偏差和错误,所以还望看到这博客的各位指正。首先我们要知道,我们写的驱动(或者说移植的驱动,其实我目前做的也是移植)到底属于那一层呢?答案是图中的driver驱动层,但为什么呢?硬件实验控制层所说的硬件控制指的是Soc上I2C控制器的控制,而这一部分一般是Soc生产商做好了的,毕竟他们最懂Soc上I2C应该怎么跑,而我们只要把连接在Soc的I2C外设的一些特性告诉硬件控制代码,它就能根据需要让外设工作起来。
回到这张图,很明显可以知道用户程序最开始接触到的是client,也就是之前我说到的设备,其实这个设备是被封装过的,封装之后很合适的挂载在内核虚拟的I2C总线上,并且和同样封装过的i2c_driver进行绑定(具体可以查看昨天提到的这两个结构体/article/9015187.html,这两个结构体相互保存了彼此的信息),在Linux下操作一个设备,都是在/dev目录下打开对应的设备,前两天的内容虽然创建了client,但client->dev中并没有内容,这个时候用户还没办法操作设备,于是probe帮我们做了这么一件事(在MMA中probe函数内容很多,暂时移除一些和I2C框架没什么关系的内容和一些对理解I2C框架不重要错误处理,更方便阅读)
static int mma7660_probe(struct i2c_client *client,const struct i2c_device_id *id) { int result; struct input_dev *idev; struct i2c_adapter *adapter; struct mma7660_data_s* data = &mma7660_data; mma7660_i2c_client = client; adapter = to_i2c_adapter(client->dev.parent); result = i2c_check_functionality(adapter,I2C_FUNC_SMBUS_BYTE |I2C_FUNC_SMBUS_BYTE_DATA); assert(result); hwmon_dev = hwmon_device_register(&client->dev); mma7660_idev = input_allocate_polled_device(); mma7660_idev->poll = mma7660_dev_poll; mma7660_idev->poll_interval = POLL_INTERVAL; mma7660_idev->poll_interval_max = POLL_INTERVAL_MAX; idev = mma7660_idev->input; idev->name = MMA7660_DRV_NAME; idev->id.bustype = BUS_I2C; idev->evbit[0] = BIT_MASK(EV_ABS); mutex_init(&data->interval_mutex); mutex_init(&data->init_mutex); input_set_abs_params(idev, ABS_X, -512, 512, INPUT_FUZZ, INPUT_FLAT); input_set_abs_params(idev, ABS_Y, -512, 512, INPUT_FUZZ, INPUT_FLAT); input_set_abs_params(idev, ABS_Z, -512, 512, INPUT_FUZZ, INPUT_FLAT); result = input_register_polled_device(mma7660_idev); mma7660_idev->input->close(mma7660_idev->input); result = sysfs_create_group(&mma7660_idev->input->dev.kobj, &mma7660_attribute_group); data->client = client; data->pollDev = mma7660_idev; i2c_set_clientdata(client, data); return result; }代码中有一句hwmon_dev
= hwmon_device_register(&client->dev);将client->dev注册成一个hwmon设备(G-sensor属于hardware monitor设备类),这样会在sys/class目录下创建文件夹,可以暴露给用户空间(暂时不做过多分析)。然后便是mma7660_idev = input_allocated_polled_device();自动创建一个轮询设备,并在之后把轮询函数mma7660_dev_poll注册进了mma7660_idev,然后又对一个输入子设备idev进行一些初始化工作并嵌入到了mma_idev->input(这些都是指针操作,只要传一个地址就行了),最后将mma7660_idev作为一个设备注册进内核,这个时候,这样一个轮询的输入设备就被创建完毕了,input->open,input->close都在这里实现了。
int input_register_polled_device(struct input_polled_dev *dev) { struct input_dev *input = dev->input; int error; input_set_drvdata(input, dev); INIT_DELAYED_WORK(&dev->work, input_polled_device_work); if (!dev->poll_interval) dev->poll_interval = 500; if (!dev->poll_interval_max) dev->poll_interval_max = dev->poll_interval; input->open = input_open_polled_device; input->close = input_close_polled_device; error = input_register_device(input); if (error) return error; error = sysfs_create_group(&input->dev.kobj, &input_polldev_attribute_group); if (error) { input_unregister_device(input); return error; } input_get_device(input); return 0; }最后再利用i2c_set_clientdata(client,data)将这个输入设备和client关联起来,现在从上层open到client再到driver都已经联系了起来,但实际操作的硬件的部分呢?,其实这一个部分也与client有关,在client下有一个adapter的结构体,而adapter里包含struct
i2c_algorithm *algo
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 *); };其实这个结构体里已经保存有操作硬件的函数指针,这里有一个遗憾,我始终没能很好的找出这几个回调函数是在哪个地方注册的,但可以知道的是当用户需要发送某个设置时(其实轮询不会一直发送控制,只会在需要初始化的时候发送一次控制信息),通过client一直往下调用最后执行adapter->algo->(master_xfer(adap,msg,num));利用回调函数调用最底层代码实现真正的硬件发送。
总结:作为I2C的client,如果框架图这样,它注册在I2C的虚拟总线上(这部分属于i2c-core实现的内容),工作在内核空间,它向上提供了open,close和poll接口,向下提供了adapter,继而实现硬件传输,而驱动程序中的probe函数,很重要的一个功能就是实现了这样一个client。
相关文章推荐
- 在CentOS上安装Java环境
- linux下的C编程学习记录
- Linux权限——用户权限修改
- linux交换分区
- fedora21客户端登录centos7服务器创建ssh无密码
- Linux权限——文件权限修改
- Linux环境变量的设置和查看
- 初始化配置CentOS7
- linux /proc/loadavg(平均负载)
- linux /proc/loadavg(平均负载)
- linux /proc/loadavg(平均负载)
- Linux GCC常用命令
- CentOS 6.5 搭建 Java 开发环境详解
- linux如何安装搜狗输入法
- Linux SSL 双向认证 浅解
- Linux中opendir的函数结构体详解
- centos svn服务器搭建
- Install Skype 4.3 on Fedora 21-20-CentOS-RHEL-SL7-6.6
- linux安装scikit-learn
- 我眼中的Linux设备树(一 概述)