1 Gsensor驅動概述
本文以Bma250驅動爲例子,詳細介紹Gsensor設計的一個模板。
gsensor驅動在系統中的層次如下圖所示:
圖中包含三個部分:hardware,driver, input:
n Hardware:其實我們可以認爲Gsensor也是一個I2C設備。整個Gsensor芯片分爲兩部分,一個是sensor傳感器,另一個是controller控制器,用於將sensor掛載在linux系統的I2C上。驅動程序則通過I2C與Gsensor做通信。
n GsensorDriver:是駐留於操作系統中,爲gsensor hardware服務的一個內核模塊;它將gsensor hardware採集到的原始數據,進行降噪,濾波,獲得當前平板的空間狀態,並按照操作系統的要求,將這些信息通過input core上報給操作系統。
n Input core: 是linux爲簡化設備驅動程序開發,而開發的一個內核子系統;發給input core的數據將提供給操作系統使用。
實際使用時,驅動按照一定的時間間隔,通過數據總線,獲取gsensor hardware採集到的數據,並按照操作系統的要求,將這些信息通過input core上報給操作系統。
2Gsensor驅動設計要求
由gsensor驅動在系統中的層次,上有Input core,下有I2C,驅動需要通過I2C採集信息,並準確及時的上報數據至input core。驅動上報的數據,是被input core管理並被上層使用的,應符合input core和上層應用框架的要求;
2.1符合Input輸入子系統的設計規範
n 接口:Gsensor驅動,在設計上,不應自行決定是否上報,上報頻率等,應提供接口,供上層應用控制驅動的運行和數據上報:包括使能控制Enable,上報時延delay等;通常通過sysfs文件系統提供,這部分實現,遵循標準的linux規範;
n 上報數據的方式:或者提供接口供上層訪問(eg:ioctl),或者掛接在系統子系統上,使用系統子系統的接口,供上層使用(eg: input core);
n 讀取數據的支持:應滿足讀取數據的要求,進行相應的配置;本文以i2c總線爲例,簡要說明在A1x平臺上,配置總線傳輸相關信息;
2.2I2c總線的配置
要使用i2c總線進行數據傳輸,需註冊i2c driver,創建i2c-client,以便使用i2c-adapter進行數據傳輸;
要成功註冊i2cdriver有兩種方式:
n 使用i2c_register_board_info:此方式,需要在系統啓動時,進行相關信息的註冊,不利於模塊化開發,現已不推薦;目前,在2.6內核上,還支持此方式,在3.0上已不再支持;
n 使用detect方式:在模塊加載時,進行檢測,在條件成立時,註冊i2c設備相關信息,創建i2c-client,並註冊i2c driver,執行probe操作;
需要說明的是,此兩種方式可共存,目前2.6就是這樣的;在共存時,以i2c_register_board_info信息爲更高優先級,在i2c_register_board_info已經佔用設備的前提下,內核發現設備被佔用,不會執行detect, 因而不會有衝突。
3gsensor模塊硬件說明
n gsensor硬件,負責獲取gsensor傳感器所處的空間狀態信息,存放於fifo中,供主控使用,不同的硬件平臺,數據準備好後,告知主控的方式及主控獲取數據的方式略有不同。
n 告知主控的方式:gsensor作爲傳感器,本身無法區分哪些數據是應該上報的,哪些數據是無效的,它只能接受主控的控制,以主控主動查詢爲主;
n 主控獲取數據的方式:通過ahb, i2c,spi,usb等方式獲取都是可能的。以下以一種典型的硬件連接爲例,描述gsensor 傳感器,gsensor ic,主控之間的連接關係;
3.1gsensor硬件連接
Gsensor在硬件上,只有i2c連接,這些連接信息,需要事先告知驅動,從而從指定的設備上讀取數據;這些連接信息,通過sysconfig1描述,在驅動中使用;
4 驅動設計
4.1 支持模組列表
在A1x平臺上支持Gsensor列表如下:
支持的 模組 |
Chip ID 寄存器 |
Chip ID值 |
I2C地址 |
I2C設備註冊 名稱 |
unuse_name |
bma250 |
0x00 |
3 |
0x18 |
bma250 |
bma250 |
bma222 |
0x00 |
3 |
0x08 |
bma250 |
bma222 |
bma150 |
0x00 |
2 |
0x38 |
bma250 |
bma150 |
kxtik-1004 |
0x0f |
0x05 |
0x0f |
kxtik |
kxtik |
kxtj9-1005 |
0x0f |
0x08 |
0x0f |
kxtik |
kxtik |
dmard06 |
0x0f |
0x06 |
0x1c |
dmard06 |
dmard06 |
mma7660 |
無 |
無 |
0x4c |
mma7660 |
mma7660 |
mma8452 |
0x0d |
0x2a |
SA0 = 0: 0x1c |
mma8452 |
mma8452c |
SA0 = 1: 0x1d |
Mma8452d |
||||
afa750 |
0x37 |
0x3d or 0x3c |
0x3d |
afa750 |
afa750 |
mxc6225 |
無 |
無 |
0x15 |
mxc622x |
mxc622x |
4.2Gsensor配置
在A1x的方案中,Gsensor的配置在sys_config1.fex文件中:
[gsensor_para] gsensor_used =1 //是否使用gsensor gsensor_name = "bma250" //名稱 gsensor_twi_id =1 //使用哪組I2C gsensor_twi_addr =0x18 // I2C設備地址(7位地址) |
4.3 關鍵數據結構
4.3.1 i2c_driver
static struct i2c_driverbma250_driver = { .class = I2C_CLASS_HWMON, .driver = { .owner =THIS_MODULE, .name = SENSOR_NAME, }, .id_table =bma250_id, .probe = bma250_probe, .remove = bma250_remove, .address_list = u_i2c_addr.normal_i2c, }; |
在驅動程序中,靜態初始化i2c_driver結構體給bma250_driver變量,該變量完成gsensor驅動的主要工作,匹配設備名,設備的偵測等,文件操作結構體如上所示。
4.3.2 bma250_data
struct bma250_data{ struct i2c_client *bma250_client; atomic_t delay; atomic_t enable; unsigned char mode; struct input_dev *input; struct bma250acc value; struct mutex value_mutex; struct mutex enable_mutex; struct mutex mode_mutex; struct delayed_work work; struct work_struct irq_work; #ifdefCONFIG_HAS_EARLYSUSPEND struct early_suspend early_suspend; #endif }; |
代表了gsensor驅動所需要的信息的集合,用於幫助實現對採樣信息的處理。
4.3.3 delayed_work
struct delayed_work{ struct work_struct work; struct timer_list timer; }; |
Delayed_work一般用來觸發定時的操作,在定時時間到後,完成指定操作,通過不斷的觸發定時操作,實現按照一定頻率的執行指定操作;
4.3.4 bma250acc
struct bma250acc{ s16 x, y, z; } ; |
用於記錄採樣時獲得的信息。
5 驅動流程解析
5.1模塊加載
static struct i2c_driverbma250_driver = { .class = I2C_CLASS_HWMON, .driver = { .owner =THIS_MODULE, .name = SENSOR_NAME, }, .id_table =bma250_id, .probe =bma250_probe, //註冊完成時調用 .remove = bma250_remove, .address_list = u_i2c_addr.normal_i2c, //IIC地址 }; static int __initBMA250_init(void) { if(gsensor_fetch_sysconfig_para()){ //解析sys_config1.fex文件 printk("%s: err.\n", __func__); return -1; }
bma250_driver.detect = gsensor_detect; ret = i2c_add_driver(&bma250_driver); //開始向IIC註冊 } static void __exitBMA250_exit(void) { i2c_del_driver(&bma250_driver); } module_init(BMA250_init); module_exit(BMA250_exit); |
內核加載驅動模塊的時候將調用到BMA250_init()方法
n 初始化了i2c_driver結構體給bma250_driver變量,將用於將設備註冊到IIC。關鍵在於結構體中的probe()方法,註冊完成的時候將調用
n 調用gsensor_fetch_sysconfig_para()解析sys_config1.fex文件,讀取到IIC的地址,並賦值給u_i2c_addr.normal_i2c。
n 調用i2c_add_driver開始向IIC註冊driver,完成註冊後將調用bm250_probe()方法
5.2bma250初始化工作-probe
static intbma250_probe(struct i2c_client *client, const struct i2c_device_id *id) { int err = 0; int tempvalue; struct bma250_data *data; …… data = kzalloc(sizeof(struct bma250_data), GFP_KERNEL);//爲bma250_data結構體申請內存
tempvalue = 0; tempvalue = i2c_smbus_read_word_data(client,BMA250_CHIP_ID_REG);
if ((tempvalue&0x00FF) == BMA250_CHIP_ID){ printk(KERN_INFO "Bosch Sensortec Device detected!\n" \ "BMA250 registered I2C driver!\n"); } else if ((tempvalue&0x00FF) == BMA150_CHIP_ID){ printk(KERN_INFO "Bosch Sensortec Device detected!\n" \ "BMA150 registered I2C driver!\n"); } …… i2c_set_clientdata(client, data);//將設備驅動的私有數據連接到設備client中 data->bma250_client = client; mutex_init(&data->value_mutex); mutex_init(&data->mode_mutex); mutex_init(&data->enable_mutex); bma250_set_bandwidth(client, BMA250_BW_SET); bma250_set_range(client, BMA250_RANGE_SET);
INIT_DELAYED_WORK(&data->work,bma250_work_func);//創建工作隊列 bma_dbg("bma: INIT_DELAYED_WORK\n"); atomic_set(&data->delay,BMA250_MAX_DELAY); atomic_set(&data->enable,0); err = bma250_input_init(data); //向Input子系統註冊 …... err = sysfs_create_group(&data->input->dev.kobj,//創建sysfs接口 &bma250_attribute_group); } |
在bma250_probe函數中,主要完成了以下幾件事:
n 爲驅動私有數據結構體bma250_data分配內存空間
n 讀取IIC chip id
n 將設備驅動的私有數據(bma250_data)連接到設備client(i2c_client)中
n 創建工作隊列
n 將bma250驅動註冊到linux input子系統
n 創建sysfs接口
下面對以上這些工作做詳細解釋,分配數據內存空間就不講了
5.2.1 讀取IICchip id
在4.1的列表中,我們可以看到bma250的chip ID寄存器爲0x00,chip ID的值爲3。而上面代碼有兩個宏的定義:
#defineBMA250_CHIP_ID_REG 0x00 #defineBMA250_CHIP_ID 3 |
調用IIC接口i2c_smbus_read_word_data()讀取IIC上bma250的chip id,返回的值tempvalue=3的時候,說明是正確的!
5.2.2 初始化工作隊列
先提一個問題,爲什麼要創建工作隊列?在前面的介紹中我們知道,sensor傳感器獲取數據後,將數據傳給controller的寄存器中,供主控去查詢讀取數據。所以這裏創建的工作隊列,就是在一個工作者線程,通過IIC不斷的去查詢讀取controller上的數據。
工作隊列的作用就是把工作推後,交由一個內核線程去執行,更直接的說就是如果寫了一個函數,而現在不想馬上執行它,想在將來某個時刻去執行它,那用工作隊列準沒錯.大概會想到中斷也是這樣,提供一箇中斷服務函數,在發生中斷的時候去執行,沒錯,和中斷相比,工作隊列最大的好處就是可以調度可以睡眠,靈活性更好。
上面代碼中我們看到INIT_DELAYED_WORK(&data->work,bma250_work_func),其實是一個宏的定義,在include/linux/workqueue.h中。bma250_work_func()就是我們定義的功能函數,用於查詢讀取Sensor數據的,並上報Input子系統,代碼如下:
static voidbma250_work_func(struct work_struct *work) { struct bma250_data *bma250 = container_of((struct delayed_work*)work, struct bma250_data, work); static struct bma250acc acc; unsigned long delay =msecs_to_jiffies(atomic_read(&bma250->delay));//延時時間
bma250_read_accel_xyz(bma250->bma250_client,&acc); //讀取Sensor數據 input_report_abs(bma250->input, ABS_X,acc.x); input_report_abs(bma250->input, ABS_Y,acc.y); input_report_abs(bma250->input, ABS_Z,acc.z); bma_dbg("acc.x %d, acc.y %d, acc.z %d\n", acc.x, acc.y,acc.z); input_sync(bma250->input); mutex_lock(&bma250->value_mutex); bma250->value = acc; mutex_unlock(&bma250->value_mutex); schedule_delayed_work(&bma250->work,delay); //設定delay時間後再次執行這個函數 } |
我們調用INIT_DELAYED_WORK()宏初始化了工作隊列之後,那麼什麼時候將執行我們定義的功能函數bma250_work_func()呢?那麼只需要調用schedule_delayed_work()即可在delay時間後執行功能函數。
在驅動設計中,我們在Sensor使能函數中調用schedule_delayed_work()開始啓動工作隊列,調用cancel_delayed_work_sync()停止工作隊列。而我們在上面的功能函數bma250_work_func()中也調用了schedule_delayed_work(),這樣功能函數將被重複調用,也就可以按照一個設定的頻率查詢讀取Sensor數據了。然後通過input系統提供的接口函數input_report_abs(),向input系統報告新的數據。
5.2.3向input系統註冊
Gsensor作爲一個輸入設備,按照linux設計標準,需要將Gsensor驅動註冊到Input子系統中,註冊代碼如下:
static intbma250_input_init(struct bma250_data *bma250) { struct input_dev *dev; int err;
dev = input_allocate_device(); if (!dev) return -ENOMEM; dev->name = SENSOR_NAME; dev->id.bustype = BUS_I2C;
input_set_capability(dev, EV_ABS, ABS_MISC); input_set_abs_params(dev, ABS_X,ABSMIN_2G, ABSMAX_2G, 0, 0); input_set_abs_params(dev, ABS_Y, ABSMIN_2G, ABSMAX_2G, 0,0); input_set_abs_params(dev, ABS_Z, ABSMIN_2G, ABSMAX_2G, 0,0); input_set_drvdata(dev, bma250); err = input_register_device(dev);
bma250->input = dev; } |
就是由上面的代碼,完成了Gsensor驅動向Input子系統註冊,又三個步驟:
n 申請一個新的input設備,即爲一個input_dev申請內存空間
n 設置input設備支持的數據類型
n 向input系統註冊
5.2.4 創建sysfs接口
爲什麼要創建sysfs接口?在驅動層創建了sysfs接口,HAL層通過這些sysfs接口,對Sensor進行操作,如使能、設置delay等。
5.2.4.1 sysfs接口函數的建立DEVICE_ATTR
說道sysfs接口,就不得不提到函數宏 DEVICE_ATTR,原型在<include/linux/device.h> :
#define DEVICE_ATTR(_name,_mode, _show, _store) \ struct device_attributedev_attr_##_name = __ATTR(_name, _mode, _show, _store) |
函數宏DEVICE_ATTR內封裝的是__ATTR(_name,_mode,_show,_stroe)方法:
n _show:表示的是讀方法,
n _stroe表示的是寫方法。
當然_ATTR不是獨生子女,他還有一系列的姊妹__ATTR_RO宏只有讀方法,__ATTR_NULL等等:
n 對設備的使用 DEVICE_ATTR
n 對驅動使用 DRIVER_ATTR
n 對總線使用 BUS_ATTR
n 對類別 (class) 使用 CLASS_ATTR
對於DEVICE_ATTR(_name,_mode, _show, _store)的四個參數,分別是名稱、權限位、讀函數、寫函數。其中讀函數和寫函數是讀寫功能函數的函數名。
如果你完成了DEVICE_ATTR函數宏的填充,下面就需要創建接口了。例如如下:
1)
staticDEVICE_ATTR(polling, S_IRUGO |S_IWUSR, show_polling, set_polling); |
2) 當你想要實現的接口名字是polling的時候,需要實現結構體structattribute *dev_attrs[]。其中成員變量的名字必須是&dev_attr_polling.attr。然後再封裝:
static struct attribute_group dev_attr_grp ={
.attrs =dev_attrs,
};
3) 在利用sysfs_create_group(&pdev->dev.kobj,&dev_attr_grp);創建接口
通過以上簡單的三個步驟,就可以在adb shell 終端查看到接口了。當我們將數據 echo 到接口中時,在上層實際上完成了一次 write 操作,對應到 kernel ,調用了驅動中的 “store”。同理,當我們cat 一個 接口時則會調用 “show” 。到這裏,只是簡單的建立了 android 層到 kernel 的橋樑,真正實現對硬件操作的,還是在 "show" 和 "store" 中完成的。
5.2.4.2 bma250驅動sysfs接口建立
Bma250驅動sysfs接口建立,按照5.2.4.1上面介紹的三個步驟來實現。
n 調用宏DEVICE_ATTR完成對功能函數的註冊
static DEVICE_ATTR(delay,S_IRUGO|S_IWUSR|S_IWGRP, bma250_delay_show, bma250_delay_store); static DEVICE_ATTR(enable,S_IRUGO|S_IWUSR|S_IWGRP, bma250_enable_show, bma250_enable_store);
static struct attribute*bma250_attributes[] = { &dev_attr_delay.attr, &dev_attr_enable.attr, NULL }; |
對於bma250Gsensor,我們需要用到的接口是對Gsensor使能和設置delay。設置delay的功能函數——讀、寫分別是bma250_delay_show、bma250_delay_store。使能的功能函數——讀寫分別是bma250_enable_show、bma250_enable_store。這裏提到的四個函數,是需要在Gsensor驅動中實現的。
n 封裝bma250_attributes數據結構
static structattribute_group bma250_attribute_group = { .attrs = bma250_attributes }; |
n 真正創建接口
在bma250的初始化函數probe中——bma250_probe(),調用:
errsysfs_create_group(&data->input->dev.kobj,&bma250_attribute_group); |
到此,完成了sysfs接口的創建,我們可以在根文件系統中看到/sys/class/input/input3/目錄,在該目錄下我們可以看到多個節點,其中就包含了enable和delay。我們以enable爲例子,可以有兩種方法完成對Gsensor的使能工作:
1) 直接使用shell命令
$cd/sys/class/input/input3 $echo 1 >enable |
將1寫到enable節點,那麼將“1”作爲參數調用到驅動的bma250_enable_store()方法,完成對Gsensor的使能工作。
2) 代碼寫設備節點
char buffer[20]; int len = sprintf(buffer,"%d\n", 1); fd =open(“/sys/class/input/input3/enable”, O_RDWR); write(fd, value,len) |
5.3 讀取並上報數據
在Android的HAL層,通過對/sys/class/input/input3/enable節點的寫操作,使能Gsensor。調用到的方法是bma250_enable_store():
static ssize_tbma250_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { …… bma250_set_enable(dev,data); …… return count; } static voidbma250_set_enable(struct device *dev, int enable) { struct i2c_client *client = to_i2c_client(dev); struct bma250_data *bma250 = i2c_get_clientdata(client); int pre_enable =atomic_read(&bma250->enable);
mutex_lock(&bma250->enable_mutex); if (enable) { if (pre_enable ==0) { bma250_set_mode(bma250->bma250_client, BMA250_MODE_NORMAL); schedule_delayed_work(&bma250->work, msecs_to_jiffies(atomic_read(&bma250->delay))); atomic_set(&bma250->enable,1); }
}…… } |
我們看到了,在使能函數中,調用了schedule_delayed_work()開始工作隊列,於是調用了功能函數bma250_work_func()
static voidbma250_work_func(struct work_struct *work) { struct bma250_data *bma250 = container_of((struct delayed_work*)work, struct bma250_data, work); static struct bma250acc acc; unsigned long delay =msecs_to_jiffies(atomic_read(&bma250->delay));
bma250_read_accel_xyz(bma250->bma250_client,&acc);//讀取數據 input_report_abs(bma250->input, ABS_X,acc.x); //上報數據 input_report_abs(bma250->input, ABS_Y,acc.y); input_report_abs(bma250->input, ABS_Z,acc.z); bma_dbg("acc.x %d, acc.y %d, acc.z %d\n", acc.x, acc.y,acc.z); input_sync(bma250->input); mutex_lock(&bma250->value_mutex); bma250->value = acc; mutex_unlock(&bma250->value_mutex); schedule_delayed_work(&bma250->work,delay); //繼續開始下一個工作隊列 } |
在上面代碼中,調用bma250_read_accel_xyz()方法讀取Gsensor的三個數據,然後調用Input系統的接口函數input_report_abs()進行數據的上報。可以看到,當讀取一次數據後,繼續調用schedule_delayed_work()開始下一個工作隊列,由此,功能函數bma250_work_func()將會按照一個的頻率被執行。
那麼對於HAL層,將通過/dev/input/event3設備節點讀取到Gsensor數據。到此,Gsensor驅動的工作流程完畢。應該很好理解吧!
6 驅動調試
1)確保硬件各個管腳的連接順序正確;
2)上電,測試各個管腳信號的電壓正常,只有在保證硬件正常的情況下,進行軟件驅動調試,方可保證驅動能夠正常工作(該處最容易被很多軟件開發人員忽視,務必注意,方可節省大部分時間)
3)將串口打印信息打開,串口打印信息設置:在打包工具中的crane-win-v2\wboot\bootfs\linux目錄下的params和paramsr兩個文件中的語句的最後加入loglevel=9即可。gsensor驅動中所有的打印信息打開,查看驅動程序的配置信息讀取狀態以及I2C的初始化狀態。
4)查看probe是否成功,如probe不成功,根據打印信息定位驅動的運行情況,是因爲什麼原因導致失敗。
5)當probe成功之後,gsensor沒反應,查看打印信息,是否enable,確保enable。
6)查看i2c通信狀態,當串口打印信息顯示i2C通信失敗時,主要有以下兩個原因,一是硬件上的,各個信號線接觸不良,所以出現通信失敗時,檢查各引腳接觸情況和電壓情況。二是因爲I2C的地址不正確導致,因爲i2C地址爲7位地址,所以可能是因爲在配置的時候沒有移位或者是主控IC有多個I2C地址,導致地址不匹配。在已知i2C地址的情況下,可以通過嘗試的方法,進行I2C地址的匹配;在不知道I2C地址的情況下,可以通過掃描的方法查看在哪一個地址時,有應答,即可知道I2C通信地址,在將正確的地址填寫sysconfig配置文件中即掃描i2c地址的示例代碼如下所示:
static int goodix_iic_test(struct i2c_client *client) { struct i2c_msg msg; int ret=-1; uint8_t data[0]; int i; for(i =0; i<256;i++) { msg.flags = !I2C_M_RD;//寫消息 msg.addr = i; msg.len = 1; msg.buf =data;
ret=i2c_transfer(client->adapter,&msg,1); if(ret == 1) { printk("IIC TEST OK addr = %x\n",i); break; } mdelay(1000); } return ret; }
|
若以上兩種方法都不能正確進行i2c傳輸,則打開i2c傳輸打印,查看傳輸打印狀態,在編譯服務器上,目錄爲workspace\exdroid\lichee\linux-2.6.36上,輸入命令:
make ARCH=arm menuconfig ,選擇Device Drivers->I2Csupport->I2C Hardware Bussupport->SUN4I_IIC_PRINT_TRANSFER_INFO,輸入Y進入bus num id(accepatableinput:0,1,2)(new),輸入數值,,若希望打印信息,數值對應相應的IIC號,gsensorIC用的是第二組,因此選擇數值爲2,若不希望打印信息,輸入N退出保存即可。進行修改後,需要重新編譯打包之後才能生效。
7)可使用adb工具查看驅動是否加載以及gsensor是否有反應,adb工具需要安裝設備對應的驅動。使用adb shell工具查看驅動是否存在於機器中以及驅動是否已經加載,以及gsensor之後是否有反應。同時可以作爲簡單的調試工具,修改好的驅動PUSH到機器中,重啓系統之後即可運行新的驅動,不用重新打包(配置文件內容除外)。使用到的命令如下所示:
adbshell 登錄設備的shell adb push xx.ko/drv 將觸摸驅動通過adb工具push到機器中 cd /drv 進入drv目錄 ls *.ko 查看機器中已經有了那些驅動 lsmod 查看系統中已經加載了那些模塊 rmmod ** 卸載驅動(注:不用加後綴) insmod **.ko 加載驅動 getevent 查看系統中已經註冊了那些input設備(當觸摸有效時,觸摸屏幕,會有相應的打印信息) |
參考:
工作隊列:http://blog.csdn.net/zchill/article/details/7076561
SYsfs接口:http://blog.csdn.net/manshq163com/article/details/7848714