linux Gsensor驱动(bma250为例子)
2013-09-26 14:17
330 查看
1 Gsensor 驱动概述
本文以Bma250驱动为例子,详细介绍Gsensor设计的一个模板。gsensor驱动在系统中的层次如下图所示:
图中包含三个部分:hardware, driver, input:
n Hardware:其实我们可以认为Gsensor也是一个I2C设备。整个Gsensor芯片分为两部分,一个是sensor传感器,另一个是controller控制器,用于将sensor挂载在linux系统的I2C上。驱动程序则通过I2C与Gsensor做通信。
n Gsensor
Driver:是驻留于操作系统中,为gsensor hardware服务的一个内核模块;它将gsensor
hardware采集到的原始数据,进行降噪,滤波,获得当前平板的空间状态,并按照操作系统的要求,将这些信息通过input core上报给操作系统。
n Input
core: 是linux为简化设备驱动程序开发,而开发的一个内核子系统;发给input
core的数据将提供给操作系统使用。
实际使用时,驱动按照一定的时间间隔,通过数据总线,获取gsensor hardware采集到的数据,并按照操作系统的要求,将这些信息通过input
core上报给操作系统。
2 Gsensor驱动设计要求
由gsensor驱动在系统中的层次,上有Inputcore,下有I2C,驱动需要通过I2C采集信息,并准确及时的上报数据至input
core。驱动上报的数据,是被input core管理并被上层使用的,应符合input
core和上层应用框架的要求;
2.1符合Input输入子系统的设计规范
驱动,在设计上,不应自行决定是否上报,上报频率等,应提供接口,供上层应用控制驱动的运行和数据上报:包括使能控制Enable, 上报时延delay等;通常通过sysfs文件系统提供,这部分实现,遵循标准的linux规范;n 上报数据的方式:或者提供接口供上层访问(eg:
ioctl),或者挂接在系统子系统上,使用系统子系统的接口,供上层使用(eg: input core);
2c总线为例,简要说明在A1x平台上,配置总线传输相关信息;
2c总线的配置
要使用i2c总线进行数据传输,需注册i2cdriver,创建i2c-client,以便使用i2c-adapter进行数据传输;
要成功注册i2c driver有两种方式:
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, 因而不会有冲突。
模块硬件说明
n gsensor硬件,负责获取gsensor传感器所处的空间状态信息,存放于fifo中,供主控使用,不同的硬件平台,数据准备好后,告知主控的方式及主控获取数据的方式略有不同。n 告知主控的方式:gsensor作为传感器,本身无法区分哪些数据是应该上报的,哪些数据是无效的,它只能接受主控的控制,以主控主动查询为主;
n 主控获取数据的方式:通过ahb,
i2c, spi,usb等方式获取都是可能的。以下以一种典型的硬件连接为例,描述gsensor 传感器,gsensor
ic, 主控之间的连接关系;
硬件连接
在硬件上,只有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.2 Gsensor配置
在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_driver bma250_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, }; |
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; #ifdef CONFIG_HAS_EARLYSUSPEND struct early_suspend early_suspend; #endif }; |
4.3.3 delayed_work
struct delayed_work { struct work_struct work; struct timer_list timer; }; |
4.3.4 bma250acc
struct bma250acc{ s16 x, y, z; } ; |
5 驱动流程解析
5.1模块加载
static struct i2c_driver bma250_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 __init BMA250_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 __exit BMA250_exit(void) { i2c_del_driver(&bma250_driver); } module_init(BMA250_init); module_exit(BMA250_exit); |
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.2 bma250初始化工作-probe
static int bma250_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); } |
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的chipID寄存器为0x00,chip
ID的值为3。而上面代码有两个宏的定义:
#define BMA250_CHIP_ID_REG 0x00 #define BMA250_CHIP_ID 3 |
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 void bma250_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时间后再次执行这个函数 } |
在驱动设计中,我们在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 int bma250_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; } |
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_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) |
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)
static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR, show_polling, set_polling); static struct attribute *dev_attrs[] = { &dev_attr_polling.attr, NULL, }; |
attribute *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 }; |
Gsensor,我们需要用到的接口是对Gsensor使能和设置delay。设置delay的功能函数——读、写分别是bma250_delay_show、bma250_delay_store。使能的功能函数——读写分别是bma250_enable_show、bma250_enable_store。这里提到的四个函数,是需要在Gsensor驱动中实现的。
n 封装bma250_attributes数据结构
static struct attribute_group bma250_attribute_group = { .attrs = bma250_attributes }; |
在bma250的初始化函数probe中——bma250_probe(),调用:
err sysfs_create_group(&data->input->dev.kobj,&bma250_attribute_group); |
1) 直接使用shell命令
$cd /sys/class/input/input3 $echo 1 > enable |
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_t bma250_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { …… bma250_set_enable(dev,data); …… return count; } static void bma250_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); } }…… } |
static void bma250_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); //继续开始下一个工作队列 } |
那么对于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; } |
make ARCH=arm menuconfig ,选择Device
Drivers->I2C support->I2C Hardware Bus support->SUN4I_IIC_PRINT_TRANSFER_INFO,输入Y进入bus
num id(accepatable input:0,1,2)(new),输入数值,,若希望打印信息,数值对应相应的IIC号,gsensorIC用的是第二组,因此选择数值为2,若不希望打印信息,输入N退出保存即可。进行修改后,需要重新编译打包之后才能生效。
7)可使用adb工具查看驱动是否加载以及gsensor是否有反应,adb工具需要安装设备对应的驱动。使用adb
shell工具查看驱动是否存在于机器中以及驱动是否已经加载,以及gsensor之后是否有反应。同时可以作为简单的调试工具,修改好的驱动PUSH到机器中,重启系统之后即可运行新的驱动,不用重新打包(配置文件内容除外)。使用到的命令如下所示:
adb shell 登录设备的shell adb push xx.ko /drv 将触摸驱动通过adb工具push到机器中 cd /drv 进入drv目录 ls *.ko 查看机器中已经有了那些驱动 lsmod 查看系统中已经加载了那些模块 rmmod ** 卸载驱动(注:不用加后缀) insmod **.ko 加载驱动 getevent 查看系统中已经注册了那些input设备(当触摸有效时,触摸屏幕,会有相应的打印信息) |
相关文章推荐
- linux Gsensor驱动(bma250为例子)
- linux Gsensor驱动(bma250为例子)
- linux Gsensor驱动(bma250为例子)
- linux Gsensor驱动(bma250为例子)
- linux Gsensor驱动(bma250为例子)
- linux Gsensor驱动(bma250为例子)
- 【ZT】linux Gsensor驱动(bma250为例子)
- linux Gsensor驱动(bma250为…
- inux Gsensor驱动(bma250为例子)
- LinuxI2C驱动--从两个访问eeprom的例子开始
- Linux I2C驱动分析与实现--例子
- Linux驱动中completion接口浅析(wait_for_complete例子,很好)
- 简单linux驱动小例子
- Linux 驱动(driver)新建 类(class)例子
- Linux 设备驱动的第一个例子:Hello World
- 从一个简单的sensor驱动看linux输入子系统框架。
- linux 内核驱动编程 简单例子 与_IO, _IOR, _IOW, _IOWR 宏解析
- linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)
- Linux 驱动(driver)新建 类(class)例子
- LINUX 产生PPM 驱动例子