Linux下I2C驅動分析(三)

分析了兩天的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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章