本文以Bma250驱动为例子,详细介绍Gsensor设计的一个模板。
gsensor驱动在系统中的层次如下图所示:
图中包含三个部分:hardware, driver, input:
1, Hardware:其实我们可以认为Gsensor也是一个I2C设备。整个Gsensor芯片分为两部分,一个是sensor传感器,另一个是controller控制器,用于将sensor挂载在linux系统的I2C上。驱动程序则通过I2C与Gsensor做通信。
2,Gsensor Driver:是驻留于操作系统中,为gsensor hardware服务的一个内核模块;它将gsensor hardware采集到的原始数据,进行降噪,滤波,获得当前平板的空间状态,并按照操作系统的要求,将这些信息通过input core上报给操作系统。
3,Input core: 是linux为简化设备驱动程序开发,而开发的一个内核子系统;发给input core的数据将提供给操作系统使用。
实际使用时,驱动按照一定的时间间隔,通过数据总线,获取gsensor hardware采集到的数据,并按照操作系统的要求,将这些信息通过input core上报给操作系统。
由gsensor驱动在系统中的层次,上有Input core,下有I2C,驱动需要通过I2C采集信息,并准确及时的上报数据至input core。驱动上报的数据,是被input core管理并被上层使用的,应符合input core和上层应用框架的要求;
2.1符合Input输入子系统的设计规范
1, 接口:Gsensor驱动,在设计上,不应自行决定是否上报,上报频率等,应提供接口,供上层应用控制驱动的运行和数据上报:包括使能控制Enable, 上报时延delay等;通常通过sysfs文件系统提供,这部分实现,遵循标准的linux规范;
2,上报数据的方式:或者提供接口供上层访问(eg: ioctl),或者挂接在系统子系统上,使用系统子系统的接口,供上层使用(eg: input core);
3,读取数据的支持:应满足读取数据的要求,进行相应的配置;本文以i2c总线为例,简要说明在A1x平台上,配置总线传输相关信息;
2.2 I2c总线的配置
要使用i2c总线进行数据传输,需注册i2c driver,创建i2c-client,以便使用i2c-adapter进行数据传输;
要成功注册i2c driver有两种方式:
1,使用i2c_register_board_info:此方式,需要在系统启动时,进行相关信息的注册,不利于模块化开发,现已不推荐;目前,在2.6内核上,还支持此方式,在3.0上已不再支持;
2, 使用detect方式:在模块加载时,进行检测,在条件成立时,注册i2c设备相关信息,创建i2c-client,并注册i2c driver,执行probe操作;
需要说明的是,此两种方式可共存,目前2.6就是这样的;在共存时,以i2c_register_board_info信息为更高优先级,在i2c_register_board_info已经占用设备的前提下,内核发现设备被占用,不会执行detect, 因而不会有冲突。
1, gsensor硬件,负责获取gsensor传感器所处的空间状态信息,存放于fifo中,供主控使用,不同的硬件平台,数据准备好后,告知主控的方式及主控获取数据的方式略有不同。
2, 告知主控的方式:gsensor作为传感器,本身无法区分哪些数据是应该上报的,哪些数据是无效的,它只能接受主控的控制,以主控主动查询为主;
3, 主控获取数据的方式:通过ahb, i2c, spi,usb等方式获取都是可能的。以下以一种典型的硬件连接为例,描述gsensor 传感器,gsensor ic, 主控之间的连接关系;
3.1 gsensor硬件连接
Gsensor在硬件上,只有i2c连接,这些连接信息,需要事先告知驱动,从而从指定的设备上读取数据;这些连接信息,通过sysconfig1描述,在驱动中使用;
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,
};
在驱动程序中,静态初始化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;
#ifdef CONFIG_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.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);
内核加载驱动模块的时候将调用到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.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);
}
在bma250_probe函数中,主要完成了以下几件事:
下面对以上这些工作做详细解释,分配数据内存空间就不讲了
5.2.1 读取IICchip id
在4.1的列表中,我们可以看到bma250的chip ID寄存器为0x00,chip ID的值为3。而上面代码有两个宏的定义:
#define BMA250_CHIP_ID_REG 0x00
#define BMA250_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 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时间后再次执行这个函数
}
我们调用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 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;
}
就是由上面的代码,完成了Gsensor驱动向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)
函数宏DEVICE_ATTR内封装的是__ATTR(_name,_mode,_show,_stroe)方法:
当然_ATTR不是独生子女,他还有一系列的姊妹__ATTR_RO宏只有读方法,__ANULL>
用户评论(共0条评论)
挑选商品 > 确认购买 > 网上支付/货到付款 > 验货满意 > 点评商品