分析了两天的I2C驱动,发现每次解决一个问题的时候都会带来新的问题,当大致读完MMA7660驱动程序的时候发现,作为一个字符设备I2C驱动,并不存在有open,close等接口,而我们知道,在Linux的世界里设备即文件,也就是操作设备就相当于读写文件,而在一个简单的字符设备里总会实现一个file_operation的结构体以实现用户层的调用,那么当我们打开一个I2C设备的时候open在哪里呢?
从框架开始说起,在网上寻找I2C框架的时候,我在很多地方看到一张图
既然大家都喜欢拿图说事,那么我也尝试着拿这个图说事,也许会有偏差和错误,所以还望看到这博客的各位指正。首先我们要知道,我们写的驱动(或者说移植的驱动,其实我目前做的也是移植)到底属于那一层呢?答案是图中的driver驱动层,但为什么呢?硬件实验控制层所说的硬件控制指的是Soc上I2C控制器的控制,而这一部分一般是Soc生产商做好了的,毕竟他们最懂Soc上I2C应该怎么跑,而我们只要把连接在Soc的I2C外设的一些特性告诉硬件控制代码,它就能根据需要让外设工作起来。
回到这张图,很明显可以知道用户程序最开始接触到的是client,也就是之前我说到的设备,其实这个设备是被封装过的,封装之后很合适的挂载在内核虚拟的I2C总线上,并且和同样封装过的i2c_driver进行绑定(具体可以查看昨天提到的这两个结构体http://blog.csdn.net/ouchao0727/article/details/50327447,这两个结构体相互保存了彼此的信息),在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。