gsensor驱动在系统中的层次如下图所示:
图中包含三个部分:hardware, driver, input:
n
n
n
实际使用时,驱动按照一定的时间间隔,通过数据总线,获取gsensor hardware采集到的数据,并按照操作系统的要求,将这些信息通过input core上报给操作系统。
2 Gsensor驱动设计要求
由gsensor驱动在系统中的层次,上有Input core,下有I2C,驱动需要通过I2C采集信息,并准确及时的上报数据至input core。驱动上报的数据,是被input core管理并被上层使用的,应符合input core和上层应用框架的要求;
2.1符合Input输入子系统的设计规范
接口:Gsensor驱动,在设计上,不应自行决定是否上报,上报频率等,应提供接口,供上层应用控制驱动的运行和数据上报:包括使能控制Enable, 上报时延delay等;通常通过sysfs文件系统提供,这部分实现,遵循标准的linux规范;
上报数据的方式:或者提供接口供上层访问(eg: ioctl),或者挂接在系统子系统上,使用系统子系统的接口,供上层使用(eg: input core);
读取数据的支持:应满足读取数据的要求,进行相应的配置;i2c总线为例,简要说明在tiny4412平台上,配置总线传输相关信息;
2.2 I2c总线的配置
要使用i2c总线进行数据传输,需注册i2c driver,创建i2c-client,以便使用i2c-adapter进行数据传输;
要成功注册i2c driver有两种方式:
n
n
需要说明的是,此两种方式可共存,目前2.6就是这样的;在共存时,以i2c_register_board_info信息为更高优先级,在i2c_register_board_info已经占用设备的前提下,内核发现设备被占用,不会执行detect, 因而不会有冲突。
3 gsensor模块硬件说明
n
n
n
3.1 gsensor硬件连接
Gsensor在硬件上,只有i2c连接,这些连接信息,需要事先告知驱动,从而从指定的设备上读取数据;这些连接信息,通过sysconfig1描述,在驱动中使用;
4.2:mma7660平台资源数据(gsensor的配置):
static struct mma7660_platform_data mma7660_pdata = {
.irq = IRQ_EINT(25),
.poll_interval= 100, // * @poll_interval: specifies how often the poll() method should be called.
//*Defaults to 500 msec unless overridden when registering the device.
.input_fuzz= 4,
.input_flat = 4,
};
static struct i2c_board_info smdk4x12_i2c_devs3[] __initdata = {
#ifdef CONFIG_SENSORS_MMA7660
{
I2C_BOARD_INFO("mma7660", 0x4c), //0x4c is in datasheet. is slave address
.platform_data = &mma7660_pdata,
},
#endif
};
4.3关键数据结构
4.3.1 i2c_driver
static const struct i2c_device_id mma7660_ids[] = {
{ "mma7660", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, mma7660_ids);
static struct i2c_driver i2c_mma7660_driver = {
.driver = {
.name = MMA7660_NAME,
},
.probe = mma7660_probe,
.remove = __devexit_p(mma7660_remove),
.suspend = mma7660_suspend,
.resume = mma7660_resume,
.id_table = mma7660_ids,
};
5 驱动流程解析
5.1模块加载
static int __init init_mma7660(void)
{
int ret;
ret = i2c_add_driver(&i2c_mma7660_driver);
printk(KERN_INFO "MMA7660 sensor driver registered.\n");
return ret;
}
static void __exit exit_mma7660(void)
{
i2c_del_driver(&i2c_mma7660_driver);
printk(KERN_INFO "MMA7660 sensor driver removed.\n");
}
调用i2c_add_driver开始向IIC注册driver,完成注册后将调用mma7660_probe()方法
static int __devinit mma7660_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct input_dev *idev;
int poll_interval = POLL_INTERVAL;
int input_fuzz = INPUT_FUZZ;
int input_flat = INPUT_FLAT;
int ret;
ret = i2c_check_functionality(adapter,
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA);
if (!ret) {
dev_err(&client->dev, "I2C check functionality failed\n");
return -ENXIO;
}
plat_data = (struct mma7660_platform_data *)client->dev.platform_data;
if (plat_data == NULL) {
dev_err(&client->dev, "lack of platform data!\n");
return -ENODEV;
}
/* Get parameters from platfrom data */
if (plat_data->poll_interval > 0)
poll_interval = plat_data->poll_interval;
if (plat_data->input_fuzz > 0)
input_fuzz = plat_data->input_fuzz;
if (plat_data->input_flat > 0)
input_flat = plat_data->input_flat;
if (mma7660_initialize(client) < 0) {
goto error_init_client;
}
ret = sysfs_create_group(&client->dev.kobj, &mma7660_group); ////创建sysfs接口
if (ret) {
dev_err(&client->dev, "create sysfs group failed!\n");
goto error_init_client;
}
/* register to hwmon device */
hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(hwmon_dev)) {
dev_err(&client->dev, "hwmon register failed!\n");
ret = PTR_ERR(hwmon_dev);
goto error_rm_dev_file;
}
/* input poll device register */
mma7660_idev = input_allocate_polled_device(); //申请一个新的input设备,即为一个input_dev申请内存空间
if (!mma7660_idev) {
dev_err(&client->dev, "alloc poll device failed!\n");
ret = -ENOMEM;
goto error_rm_hwmon_dev;
}
mma7660_idev->poll = mma7660_dev_poll; //相当于select函数
mma7660_idev->poll_interval = plat_data->poll_interval; //每个多长时间调用一次poll函数
//input_dev
idev = mma7660_idev->input;
idev->name = MMA7660_NAME;
idev->id.bustype = BUS_I2C;
idev->id.vendor = 0x12FA;
idev->id.product = 0x7660;
idev->id.version = 0x0100;`
idev->dev.parent = &client->dev;
//EV_ABS表示支持绝对值坐标,后面都是针对这些坐标的一些参数访问范围设置
//设置idev->evbit中的相应位让它支持绝对值坐标
//表示该设备所支持的事件。在这里将其EV_SYN置位,即所有设备都支持这个事件
//分别用来设置设备所产生的事件以及上报的按键值。Struct iput_dev中有两个成员,一个是evbit.一个是keybit,分别用表示设备所支持的动作和按键类型
set_bit(EV_ABS, idev->evbit);
set_bit(ABS_X, idev->absbit);
set_bit(ABS_Y, idev->absbit);
set_bit(ABS_Z, idev->absbit);
//表示支持绝对值x坐标,并设置它在坐标系中的最大值和最小值,以及干扰值和平焊位置等
//设置input设备支持的数据类型
//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); //表示z轴的范围是-512到512,数据误差范围(漂移最好通过其他传感器动态补偿)为input_fuzz,中心平滑位置(零偏一般静态补偿)为input_flat.
{
input_set_abs_params(button_dev, ABS_X, 0, 255, 4, ;
This setting would be appropriate for a joystick X axis, with the minimum of
0, maximum of 255 (which the joystick *must* be able to reach, no problem if
it sometimes reports more, but it must be able to always reach the min and
max values), with noise in the data up to +- 4, and with a center flat
position of size 8.
If you don't need absfuzz and absflat, you can set them to zero, which mean
that the thing is precise and always returns to exactly the center position
(if it has any).
这是Documentation里面对该函数的举例说明
}
ret = input_register_polled_device(mma7660_idev); //向Input子系统注册轮询设备
if (ret) {
dev_err(&client->dev, "register poll device failed!\n");
goto error_free_poll_dev;
}
/* register interrupt handle */
ret = request_irq(plat_data->irq, mma7660_interrupt,
IRQF_TRIGGER_FALLING, MMA7660_NAME, idev);
if (ret) {
dev_err(&client->dev, "request irq (%d) failed %d\n", plat_data->irq, ret);
goto error_rm_poll_dev;
}
dev_info(&client->dev, "MMA7660 device is probed successfully.\n");
#if 0
set_mod(1);
#endif
return 0;
error_rm_poll_dev:
input_unregister_polled_device(mma7660_idev);
error_free_poll_dev:
input_free_polled_device(mma7660_idev);
error_rm_hwmon_dev:
hwmon_device_unregister(hwmon_dev);
error_rm_dev_file:
sysfs_remove_group(&client->dev.kobj, &mma7660_group);
error_init_client:
mma7660_client = NULL;
return 0;
}
驱动框架:
-构建i2c_dricer
-将i2c_driver注册到系统中
-实现i2c_driver中的probe函数
-在probe中注册input_polled_dev
-实现input_polled_dev中的poll函数
-在poll函数中读取加速度传感器的数值并提交给上层
-在poll开启工作队列,每隔一段时间读取加速度传感器的数值并提交给上层
-s
5.2.2 初始化工作队列
先提一个问题,为什么要创建工作队列?在前面的介绍中我们知道,sensor传感器获取数据后,将数据传给controller的寄存器中,供主控去查询读取数据。所以这里创建的工作队列,就是在一个工作者线程,通过IIC不断的去查询读取controller上的数据。
工作队列的作用就是把工作推后,交由一个内核线程去执行,更直接的说就是如果写了一个函数,而现在不想马上执行它,想在将来某个时刻去执行它,那用工作队列准没错.大概会想到中断也是这样,提供一个中断服务函数,在发生中断的时候去执行,没错,和中断相比,工作队列最大的好处就是可以调度可以睡眠,灵活性更好。
上面代码中我们看到DECLARE_WORK(mma7660_work, mma7660_worker);,其实是一个宏的定义,在include/linux/workqueue.h中。mma7660_worker()就是我们定义的功能函数,用于查询读取Sensor数据的,并上报Input子系统,代码如下:
static void mma7660_worker(struct work_struct *work)
{
int bafro, pola, shake, tap;
int val = 0;
mma7660_read_tilt(mma7660_client, &val);
/* TODO: report it ? */
bafro = val & 0x03;
if (bafro != (last_tilt & 0x03)) {
printk("%s\n", mma7660_bafro[bafro]);
}
pola = (val >> 2) & 0x07;
if (pola != ((last_tilt >> 2) & 0x07)) {
printk("%s\n", mma7660_pola[pola]);
}
shake = (val >> 5) & 0x01;
if (shake && shake != ((last_tilt >> 5) & 0x01)) {
printk("Shake\n");
}
tap = (val >> 7) & 0x01;
if (tap && tap != ((last_tilt >> 7) & 0x01)) {
printk("Tap\n");
}
/* Save current status */
last_tilt = val;
}
我们调用INIT_DELAYED_WORK()宏初始化了工作队列之后,那么什么时候将执行我们定义的功能函数mma7660_worker()呢?那么只需要调用schedule_work()即可执行功能函数。
在驱动设计中,我们在Sensor使能函数中调用schedule_work()开始启动工作队列。
5.2.3向input系统注册
Gsensor作为一个输入设备,按照linux设计标准,需要将Gsensor驱动注册到Input子系统中,注册代码如下:
mma7660_idev = input_allocate_polled_device(); //申请一个新的input设备,即为一个input_dev申请内存空间
if (!mma7660_idev) {
dev_err(&client->dev, "alloc poll device failed!\n");
ret = -ENOMEM;
goto error_rm_hwmon_dev;
}
mma7660_idev->poll = mma7660_dev_poll;
mma7660_idev->poll_interval = plat_data->poll_interval;
//input_dev
idev = mma7660_idev->input;
idev->name = MMA7660_NAME;
idev->id.bustype = BUS_I2C;
idev->id.vendor = 0x12FA;
idev->id.product = 0x7660;
idev->id.version = 0x0100;`
idev->dev.parent = &client->dev;
//EV_ABS表示支持绝对值坐标,后面都是针对这些坐标的一些参数访问范围设置
//设置idev->evbit中的相应位让它支持绝对值坐标
//表示该设备所支持的事件。在这里将其EV_SYN置位,即所有设备都支持这个事件
set_bit(EV_ABS, idev->evbit);
set_bit(ABS_X, idev->absbit);
set_bit(ABS_Y, idev->absbit);
set_bit(ABS_Z, idev->absbit);
//表示支持绝对值x坐标,并设置它在坐标系中的最大值和最小值,以及干扰值和平焊位置等
//设置input设备支持的数据类型
// 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);
ret = input_register_polled_device(mma7660_idev); //向Input子系统注册轮询设备
if (ret) {
dev_err(&client->dev, "register poll device failed!\n");
goto error_free_poll_dev;
}
就是由上面的代码,完成了Gsensor驱动向Input子系统注册,又三个步骤:
n
n
n
5.2.4 创建sysfs 接口
为什么要创建sysfs接口?在驱动层创建了sysfs接口,HAL层通过这些sysfs接口,对Sensor进行操作,如使能、设置delay等。
5.2.4.1 sysfs接口函数的建立SENSOR_DEVICE_ATTR
说道sysfs接口,就不得不提到函数宏
#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)方法:
n
n
当然_ATTR不是独生子女,他还有一系列的姊妹__ATTR_RO宏只有读方法,__ATTR_NULL等等:
n
n
n
n
对于DEVICE_ATTR(_name, _mode, _show, _store)的四个参数,分别是名称、权限位、读函数、写函数。其中读函数和写函数是读写功能函数的函数名。
如果你完成了DEVICE_ATTR函数宏的填充,下面就需要创建接口了。例如如下:
1)
|
2)
3)
5.2.4.2 bma250驱动sysfs接口建立
Bma250驱动sysfs接口建立,按照5.2.4.1上面介绍的三个步骤来实现。
n
mma7660_show_regs, mma7660_write_reg, 0);
static SENSOR_DEVICE_ATTR(x_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 0);
static SENSOR_DEVICE_ATTR(y_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 1);
static SENSOR_DEVICE_ATTR(z_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 2);
static SENSOR_DEVICE_ATTR(all_axis_g, S_IRUGO, mma7660_show_xyz_g, NULL, 0);
static SENSOR_DEVICE_ATTR(tilt_status, S_IRUGO, mma7660_show_tilt, NULL, 0);
static struct attribute* mma7660_attrs[] = {
&sensor_dev_attr_registers.dev_attr.attr,
&sensor_dev_attr_x_axis_g.dev_attr.attr,
&sensor_dev_attr_y_axis_g.dev_attr.attr,
&sensor_dev_attr_z_axis_g.dev_attr.attr,
&sensor_dev_attr_all_axis_g.dev_attr.attr,
&sensor_dev_attr_tilt_status.dev_attr.attr,
NULL
};
static const struct attribute_group mma7660_group = {
.attrs = mma7660_attrs,
};
n
static const struct attribute_group mma7660_group = { .attrs= mma7660_attrs, };
|
n
在mma7660的初始化函数probe中——mma7660_probe(),调用:
ret = sysfs_create_group(&client->dev.kobj, &mma7660_group);
到此,完成了sysfs接口的创建,我们可以在根文件系统中看到/sys/class/input/input1/device目录,在该目录下我们可以看到多个节点,其中就包含了registers,tilt_status,y_axis_g,x_axis_g,z_axis_g,all_axis_g。我们以registers为例子,可以有两种方法完成对Gsensor的使能工作:
1)
$cd /sys/class/input/input3/device $echo 1 > registers |
将1写到enable节点,那么将“1”作为参数调用到驱动的mma7660_write_reg()方法,完成对Gsensor的设置工作。
2)
char buffer[20]; int len = sprintf(buffer, "%dn", 1); fd =open(“/sys/class/input/input1/device/registers”, O_RDWR); write(fd, value, len) |
在/sys/class/i2c-adapter/i2c-3/3-004c下也可找到其应用层接口
5.3 读取并上报数据
static ssize_t mma7660_show_regs(struct device *dev,struct device_attribute *attr, char *buf)
{
int reg, val;
int i, len = 0;
for (reg = 0; reg < 0x0b; reg++) {
val = i2c_smbus_read_byte_data(mma7660_client, reg);
len += sprintf(buf + len, "REG: 0x%02x = 0x%02x ...... [ ", reg, val);
for (i = 7; i >= 0; i--) {
len += sprintf(buf + len, "%d", (val >> i) & 1);
if ((i % 4) == 0) {
len += sprintf(buf + len, " ");
}
}
len += sprintf(buf + len, "]\n");
}
return len;
}
static ssize_t mma7660_write_reg(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int reg, val;
int ret;
ret = sscanf(buf, "%x %x", ®, &val);
if (ret == 2) {
if (reg >= 0 && reg <= 0x0a) {
i2c_smbus_write_byte_data(mma7660_client, reg, val);
val = i2c_smbus_read_byte_data(mma7660_client, reg);
printk("REG: 0x%02x = 0x%02x\n", reg, val);
}
}
return count;
}
/*-----------------------------------------------------------------------------
* Input interfaces
*/
static void mma7660_report_abs(void)
{
int axis[3];
int i;
for (i = 0; i < 3; i++) {
mma7660_read_xyz(mma7660_client, i, &axis[i]);//读取数据
}
input_report_abs(mma7660_idev->input, ABS_X, axis[0]); //上报数据
input_report_abs(mma7660_idev->input, ABS_Y, axis[1]);
input_report_abs(mma7660_idev->input, ABS_Z, axis[2]);
input_sync(mma7660_idev->input); //同步用于告诉input core子系统报告结束
//printk("3-Axis ... %3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}
static void mma7660_dev_poll(struct input_polled_dev *dev)
{
mma7660_report_abs();
}
6 驱动调试
1)确保硬件各个管脚的连接顺序正确;
2)上电,测试各个管脚信号的电压正常,只有在保证硬件正常的情况下,进行软件驱动调试,方可保证驱动能够正常工作(该处最容易被很多软件开发人员忽视,务必注意,方可节省大部分时间)
3)将串口打印信息打开,串口打印信息设置:在打包工具中的 crane-win-v2wbootbootfslinux目录下的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) { } |
若以上两种方法都不能正确进行i2c传输,则打开i2c传输打印,查看传输打印状态, 在编译服务器上,目录为workspaceexdroidlicheeli
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退出保存即可。进行修改后,需要重新编译打包之后才能生效。