G-sensor驱动分析
重力传感器代码分析
重力传感器驱动的功能,主要是向HAL层提供IOCTRL接口,并通过input设备上报数据。芯片实际数据的读取是采用i2c协议读取原始数据,并且作为i2c设备挂载在系统上工作的。
1、调用关系
采用模块化的编程方式,一下介绍函数的调用关系。
module_init(aac_MMAxxxxx_init);
module_exit(aac_MMAxxxxx_exit);
模块中定义了驱动初始化和退出函数,具体实现如下
static int __init aac_MMAxxxxxFC_init(void)
{
int ret;
if ((ret = i2c_add_driver(&aac_MMAxxxxxFC_i2c_driver))) {
printk(KERN_WARNING "aac_MMAxxxxxFC_init failed. /n");
return ret;
}
return ret;
}
static void __exit aac_MMAxxxxxFC_exit(void)
{
i2c_del_driver(&aac_MMAxxxxxFC_i2c_driver);
}
调用i2c_add_driver函数将aac_MMAxxxxxFC_i2c_driver驱动添加实现了初始化函数,exit函数则调用i2c_del_driver函数将aac_MMAxxxxxFC_i2c_driver驱动删除。
对于aac_MMAxxxxxFC_i2c_driver驱动结构体,由6个参数实现该结构体。Driver用模块名来填充,probe、remove、suspend、resume分别用相对应的函数来填充。Id_table用aac_ MMAxxxxxFC_id来填充索引表。实现代码如下:
static struct i2c_driver aac_MMAxxxxxFC_i2c_driver = {
.driver = {
.name = MMAxxxxx_MODULE_NAME,
},
.probe = aac_MMAxxxxxFC_probe,
.remove = aac_MMAxxxxxFC_remove,
.id_table = aac_MMAxxxxxFC_id,
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = mmaxxxxx_suspend,
.resume = mmaxxxxx_resume,
#endif
};
1.1、 对于结构体中索引表aac_MMAxxxxxFC_id数组,具体实现如下:
static const struct i2c_device_id aac_MMAxxxxxFC_id[] = {
{ MMAxxxxx_MODULE_NAME, 0 },
{ }
};
1.2、 MMAxxxxx_MODULE_NAME表示驱动名。在头文件中定义了具体名字"mmaxxxxx"。
1.3、 aac_MMAxxxxxFC_probe函数是i2c驱动寻找设备的经典实现,这里将具体分析下实现过程。实现思路是首先注册i2c功能函数类型,然后分配misc设备空间并注册,接下来分配输入设备空间并注册,注意将misc设备获取数据传给input设备数据中。最后创建工作队列,实现位置信息数据处理。
具体代码如下:
1.3.1调用i2c_check_functionality,函数返回我们需要类型的i2c适配器
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
ret = -ENODEV;
goto exit0;
}
并且将入口函数参数client赋值给静态全局变量g_client
g_client = client;
1.3.2调用kzalloc函数给空结构体赋值,空结构体的意义在于寻址。调用sysfs_create_group函数将设备体创建到mmaxxxxx_attr_group组,主要为调试使用,关于具体调试文件系统将在文章后面章节介绍。调用misc_register函数注册misc设备mmaxxxxx_misc_device。
mmaxxxxx_misc_data = kzalloc(sizeof(struct mmaxxxxx_data), GFP_KERNEL);
if (!mmaxxxxx_misc_data) {
ret = -ENOMEM;
goto exit1;
}
//init sysfs entry
ret = sysfs_create_group(&client->dev.kobj, &mmaxxxxx_attr_group);
if (ret)
goto exit2;
//misc_register
ret = misc_register(&mmaxxxxx_misc_device);
if (ret < 0) {
dev_err(&client->dev, "mmaxxxxx_device register failed/n");
goto exit3;
}
1.3.3调用input_allocate_device函数给input设备分配空间
//input_allocate
input = input_allocate_device();
if (!input) {
ret = -ENOMEM;
printk("input device allocate failed/n");
goto exit4;
}
对input各个属性项目填充,name、phys表示映射的物理端口、id.bustype、id.vendor、id.product、
input->name = "mmaxxxxx";
input->phys = "mmaxxxxx/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
//evbit选择了事件类型,absbit表示了绝对值的数据
input->evbit[0] = BIT(EV_ABS);
input->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_Z);
//将input赋值给全局变量g_input_dev设备。
g_input_dev = input;
ret = input_register_device(g_input_dev);
if (ret) {
printk("unable to register input polled device /n");
goto exit5;
}
//把misc设备数据赋值给输入设备系统数据
input_set_drvdata(g_input_dev, mmaxxxxx_misc_data);
1.3.4调用init_MUTEX函数将信号量申请为互斥体信号,在分别在mmaxxxxx_misc_ioctl、asensor_thread、mmaxxxxx_resume函数中调用,确保时间能够安全赋值。
然后创建工作队列asensor_wq,调用INIT_DELAYED_WORK函数asensor_thread数据处理函数添加到工作任务asensor_delayed_work当中。注意,此时工作队列并没有开始工作,需要mmaxxxxx_misc_ioctl接到HAL层的控制信息时才打开端口,进行工作。
init_MUTEX(&sem_thread);
asensor_wq = create_workqueue("MMAxxxxxFC_workqueue");
if (!asensor_wq) {
printk("can't create a workqueue/n");
ret = -1;
goto exit6;
}
INIT_DELAYED_WORK(&asensor_delayed_work, asensor_thread);
1.4、 aac_MMAxxxxxFC_remove函数是作为probe函数的反过程实现的,主要是取消工作队列和释放相关资源。
具体代码实现如下
ret = cancel_delayed_work(&asensor_delayed_work);
if(ret == 0){
flush_workqueue(asensor_wq);
}
destroy_workqueue(asensor_wq);
asensor_wq = NULL;
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&early_suspend);
#endif
//clean input
mmaxxxxx_input_cleanup();
//sysfs
sysfs_remove_group(&client->dev.kobj, &mmaxxxxx_attr_group);
//misc
misc_deregister(&mmaxxxxx_misc_device);
1.5、 mmaxxxxx_suspend和mmaxxxxx_resume函数主要是睡眠和唤醒时作用,关掉外设电源并释放占用的相关资源。唤醒时实现反过程。
static int mmaxxxxx_suspend(struct i2c_client *ic, pm_message_t mesg)
{
printk("########mmaxxxxx_suspend");
cancel_delayed_work_sync(&asensor_delayed_work);
aac_MMAxxxxxFC_close_client();
return 0;
}
static int mmaxxxxx_resume(struct i2c_client *ic)
{
printk("########mmaxxxxx_resume");
aac_MMAxxxxxFC_init_client();
down_interruptible(&sem_thread);
time_delay.tv_sec = 0;
time_delay.tv_usec = read_interval;
up(&sem_thread);
if(1 == thread_flag)
{
printk("########resume!/n");
queue_delayed_work(asensor_wq, &asensor_delayed_work, timeval_to_jiffies(&time_delay));
}
return 0;
}
2. 特殊处理函数
2.1 misc控制函数
主要作用是处理HAL层的IOCTL命令,起到打开、关闭的任务。
首先定义了混杂设备结构体mmaxxxxx_misc_device,该结构体体由3个field组成,第一个表示misc设备的此设备号,第二个为misc设备的名字,第三个为misc操作结构体。操作结构体由我们自行定义。
static struct miscdevice mmaxxxxx_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mmaxxxxx",
.fops = &mmaxxxxx_misc_fops,
};
然后定义Misc操作结构体,该结构体由3个field组成,第一个表示所有者,属性固定为本模块,即THIS_MODULE。第二个表示打开函数,处理数据信息,第三个表示控制函数,处理misc设备的相关控制命令。
static const struct file_operations mmaxxxxx_misc_fops = {
.owner = THIS_MODULE,
.open = mmaxxxxx_misc_open,
.ioctl = mmaxxxxx_misc_ioctl,
};
作为传感器输入设备,打开函数使用的也是数据流,所以定位数据没有意义。这种情况下,不能简单不声明lseek操作,因为默认方法是允许定位的。默认定位的方法是调用lseek函数在数据区往上或往下定位数据。在open方法中调用nonseekable_open()时,它会通知内核设备不支持lseek。
函数实现如下:
static int mmaxxxxx_misc_open(struct inode *inode, struct file *file)
{
int err;
err = nonseekable_open(inode, file);
if (err < 0)
return err;
file->private_data = mmaxxxxx_misc_data;
return 0;
}
这里注意的是文件的私有数据赋值对象为mmaxxxxx_misc_data,是一个空结构体变量。难道也仅仅是为了寻址么?
Ioctl函数作为misc设备核心的操作函数,主要作用是通过HAL层中相关command字的控制,给应用层提供了控制方法,最终实现设备体的状态获取,延时,激活,关闭,如匹配字不符合,则控制参数有误退出。
Ioctl函数中主要包括的控制命令为MMAxxxxx_IOCTL_GET_STATE、MMAxxxxx_IOCTL_SET_DELAY、MMAxxxxx_IOCTL_ENABLE、MMAxxxxx_IOCTL_DISABLE几个命令。
具体可参考代码。如MMAxxxxx_IOCTL_GET_STATE中主要通过copy_to_user将线程标示位赋值给参数argp,从而获取状态。其他几个具体参考代码。
static int mmaxxxxx_misc_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int32_t interval;
switch( cmd )
{
case MMAxxxxx_IOCTL_GET_STATE:
{
if(copy_to_user(argp, &thread_flag, sizeof(thread_flag)))
return -EFAULT;
//printk("MMAxxxxx_IOCTL_GET_STAT/n");
break;
}
case MMAxxxxx_IOCTL_ENABLE:
{
aac_MMAxxxxxFC_init_client();
thread_flag = 1;
down_interruptible(&sem_thread);
time_delay.tv_sec = 0;
time_delay.tv_usec = read_interval;
up(&sem_thread);
queue_delayed_work(asensor_wq, &asensor_delayed_work, timeval_to_jiffies(&time_delay));
//printk("MMAxxxxx_IOCTL_ENABLE/n");
break;
}
}
}
在MMAxxxxx_IOCTL_ENABLE控制命令下,通过原子操作定义了延时的时间,将工作任务asensor_delayed_work添加到工作队列asensor_wq中,这样就循环开始了该工作。
2.2 工作函数asensor_thread
工作函数中主要是通过i2c线读取相关的输出数据。I2c读取的方式这里不再详述,这里主要通过调用i2c_smbus_read_i2c_block_data函数,读取连续三个地址的数值,通过数据处理,根据硬件相关的贴片方式,输出正确的xyz结果。
处理过的结果用自定义的结构体保存。
struct _mmaxxxxx_data{
int x_data;
int y_data;
int z_data;
};
这里有个需要处理的地方就是有些芯片灵敏度过高,可以通过滤波算法进行相关的去抖动处理。具体参考后续文章。
3 调试信息控制文件接口
这里通过static int g_print = 0来实现是否输出打印信息,介绍相关知识之前,需要先了解linux内核中sys文件系统的介绍。sysfs 属性的功能只能靠阅读源代码来理解。在内核中, sysfs 属性一般是由 __ATTR 系列的宏来声明的,如对设备的使用 DEVICE_ATTR ,对总线使用 BUS_ATTR ,对驱动使用 DRIVER_ATTR ,对类别(class)使用 CLASS_ATTR, 这四个高级的宏来自于 <include/linux/device.h>, 都是以更低层的来自 <include/linux/sysfs.h> 中的 __ATTR/__ATRR_RO 宏实现;因此我们在内核源码树中相应位置 drivers/scsi/ 找到这几个宏的使用情况,可以得到在 drivers/scsi/scsi_sysfs.c 中。
下面通过代码介绍DEVICE_ATTR的添加过程。
3.1定义控制变量
static int g_print = 0;
3.2定义mmaxxxxx_show_print函数
功能主要是将g_print打印到内存当中。
static ssize_t mmaxxxxx_show_print(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d/n", g_print);
}
3.3定义mmaxxxxx_store_print函数
功能主要是获取buf中存在的控制值。
static ssize_t mmaxxxxx_store_print(struct device *dev,
struct device_attribute *attr, char *buf,size_t count)
{
unsigned long val = simple_strtoul(buf, NULL, 10);
//adjust_light(val);
//ggg=val;
g_print = val;
return count;
}
3.4填充设备属性DEVICE_ATTR
static DEVICE_ATTR(print, S_IWUSR | S_IRUGO,mmaxxxxx_show_print, mmaxxxxx_store_print);
其中有四个参数,分别表示是称、权限位、读函数、写函数。
3.5属性数组mmaxxxxx_attributes
主要是填充设备属性位置。
static struct attribute *mmaxxxxx_attributes[] = {
&dev_attr_print.attr,
NULL
};
3.6将属性数组加到属性组(group)里。
static const struct attribute_group mmaxxxxx_attr_group = {
.attrs = mmaxxxxx_attributes,
};
至此完成了属性组的添加工作,通过adb连接去硬件系统中对应的文件为sys/devices/i2c-0/x-xxxx/print,x-xxxx对应的是芯片的地址线。
4 总结
文章中采用标准模块化得方法,调用内核函数,将i2c模块挂载到内核系统当中,并通过misc设备留接口给上层提供调用。在模块工作过程中,通过i2c读函数获取了实时的位置信息,并通过input设备将数据上报给用户层。
G-sensor驱动分析的更多相关文章
- linux i2c驱动架构-dm368 i2c驱动分析
linux i2c驱动架构-dm368 i2c驱动分析 在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...
- 基于input子系统的sensor驱动调试(一)
要想弄明白世界的本质,就要追根溯源:代码也是一样的道理: 最近调试几个sensor驱动,alps sensor驱动.compass sensor驱动.G-sensor驱动都是一样的架构: 一.基于in ...
- Linux SD/MMC/SDIO驱动分析_转
转自:Linux SD/MMC/SDIO驱动分析 https://www.cnblogs.com/cslunatic/p/3678045.html#3053341 一.SD/MMC/SDIO概念 ...
- linux驱动学习(八) i2c驱动架构(史上最全) davinc dm368 i2c驱动分析【转】
转自:http://blog.csdn.net/ghostyu/article/details/8094049 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 预备知识 lin ...
- RK30SDK开发板驱动分析(二):DDR频率配置
在内核配置界界面,我们可以很容易的配置DDR的频率,300M OR 600M, so easy! 那么它是如何起作用的呢? 回想 RK30SDK开发板驱动分析(一) 末尾提到MACHINE_START ...
- Linux UART驱动分析
1. 介绍 8250是IBM PC及兼容机使用的一种串口芯片; 16550是一种带先进先出(FIFO)功能的8250系列串口芯片; 16550A则是16550的升级版本, 修复了FIFO相关BUG, ...
- [tty与uart]3.tty驱动分析
转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...
- linux的串口驱动分析
1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver 驱动 • UART端口结构: struct uart_port 串口 • UART相关操作函数结构: st ...
- linux内核SPI总线驱动分析(一)(转)
linux内核SPI总线驱动分析(一)(转) 下面有两个大的模块: 一个是SPI总线驱动的分析 (研究了具体实现的过程) 另一个是SPI总线驱动的编写(不用研究具体的实现过程) ...
- Mini2440 DM9000 驱动分析(一)
Mini2440 DM9000 驱动分析(一) 硬件特性 Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系: PW_RST 连接到复位按键,复位按键按下,低电平 ...
随机推荐
- java io流缓冲理解
bufferedinputstream和bufferedoutputstream:这两个类是在inputstream和outputstream的基础上增加了一个buffer的缓冲区,从而使数据不直接写 ...
- Sina App Engine(SAE)入门教程(1)
此教程只针对刚接触SAE的小白用户,资深码农.高手请绕道.首先还是一个经典的实例,hello sae. 创建应用 在注册完账号之后,需要到 http://sae.sina.com.cn/?m=myap ...
- SqlDataAdapter用法
SqlDataAdapter和SqlCommand区别: SqlCommand就是是命令了,可以用它来执行SQL命令: SqlDataAdapter就是数据适配器了,它是用于在数据源和数据集之间通讯的 ...
- python 下划线的使用(转载:安生犹梦 新浪博客)
Python 用下划线作为变量前缀和后缀指定特殊变量. _xxx 不能用'from module import *'导入 __xxx__ 系统定义名字 __xxx 类中的私有变量名 核 ...
- 在浏览器控制台输出内容 console.log(string);
在浏览器控制台中写如数据 1添加 <script type="text/javascript">djConfig = { isDebug: true };< ...
- shell 编程基础(1)---初识shellscript
shellscript 是linux下强大的系统管理工具,可以通过bash命令和管道命令直接在linux系统上进行编程,所写的脚本不需要编译就可以执行,对于系统管理而言十分方便. #!/bin/bas ...
- android下activity中多个listview只允许主界面滚动
之前发现了自己的APP在处理两个listview时产生的一个bug.当两个listview中的item数量多出手机屏幕时,listview不能显示完全.一开始觉得只要加一个scrollview就可以了 ...
- BZOJ 2326 数学作业(矩阵)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2326 题意:定义Concatenate(1,N)=1234567……n.比如Concat ...
- 注意:C++中double的表示是有误差的
注意:C++中double的表示是有误差的,直接通过下面的例子看一下 #include<iostream> using namespace std; int main() { double ...
- Webbrowser模拟百度一下子点击事件
Webbrowser模拟百度一下点击事件新建一个form,有一个button和一个webbrowser控件.然后webbrowser一开始加载的就是百度主页.然后在文本框里输入点东西,如何做到点击bu ...