Headset驱动的设计与实现

        Android移植调试工作过程中,关于驱动一般需要熟悉内核机制、驱动架构、硬件基本操作,而实际驱动已经被厂商门编写好了,当然其中也有一些bug。这样很不利于驱动的学习,因为没有经历设计这一过程,知其然不知其所以然。腾讯面试时,就在这块被鄙视了!!

        因此这里重新梳理以前写过的Headset驱动,深究其原理、分析出驱动设计的一般过程。

        在Linux的真实设备驱动中,往往不会像无操作系统的驱动开发那样只需要对设备的行为进行抽象。当然最低层次必须考虑硬件的基本操作,其次需要关注驱动的架构,以及作为内核一部分要考虑到内核机制和用户空间需求。下面是Linux真实设备驱动的架构图:

        


        从这个角度来看,无操作系统驱动处在硬件操作层,而Linux的设备驱动需要关注整个架构。其中驱动核心层由Linux内核提供,因为需要给上层提供统一的接口;而其中的硬件操作层在实际工程中是需要我们来实现的,当然基本厂商会完成。自己不动手设计,是不利于学习的,因此这里从Headset驱动入手,来研究这幅图。Ps:改改寄存器、调试调试信号时序..这样算Linux驱动开发吗?实际还差很远,因为需要理解内核机制和框架的规定,需要一种从系统角度去考虑的大局观,才有可能去开发出一个好的设备驱动。

1.电路图研究、考虑基本的硬件操作

                            

        首先考虑该模块的供电情况,这里由VCCA_1V8电压供电,由PMU直接供电,不需要使用电压放大器或降压器。因此看到这里,模块供电的操作不需要我们管了,耳机硬件模块在系统运行阶段一直是上电的。

        分析J1101模块,它只支持iPhone类型的耳机。现在的四段耳机基本可以分为N型和I型,一种是Nokia型、一种是iphone型,像国内的手机厂商里面华为耳机是N型的,中兴手机是I型的。区别主要是四段耳机的四段对应不同的信号,N型的四段(从插头顶端开始)分别为“左声道、右声道、Mic麦克、GND接地”,而I型的四段分别为"左声道、右声道、GND接地、Mic麦克"。这里耳机座子,pin4对应第一节,pin3对应第二节,pin2对应第三节,pin1对应第四节 。

        下面分析耳机插入过程:

             插入前,pin4和pin5分开,hp_det被外部拉高

             插入后,pin4和pin5接触到耳机的同一头(第一节),被插头的外部阻抗拉低

             结论:插入后,HP_HET由高变低

        下面分析mic按下过程:

             mic按键看做两个脚,一脚为pin1,另一脚为gnd。当按下时pin1接地变为低,当放开就悬空电压有MICBIAS提供为高。

             结论:按下mic,HP_HOOK由高变低

       这里基本分析完了硬件原理图,其他的模块可能比这里更麻烦,但是只要耐心分析并且与硬件工程师沟通,弄懂硬件原理图基本不是难事。这里得到的基本信息是:耳机模块自动上电,耳机插入时HP_HET产生变为低电平,按下Mic时HP_HOOK变为低电平。

       问题一:按键抖动问题

       通常的按键都是机械弹性开关(耳机插入、Mic按键都是),由于弹性的作用,在按键的触点在断开和闭合的一瞬间,不会立马断开和闭合。断开和闭合到稳定的一段时间内,电平会出现一连串的抖动。这里的抖动属于无效数据,如果当做有效数据可能判断按键闭合多次,为了解决这个问题,需要进行消除抖动。抖动的长短由按键的机械特性决定,一般为5-10ms,而按键稳定闭合时间为零点几秒至数秒。

        可以采用软件消除抖动。

        1.从断开都闭合的过程为:稳定断开状态->抖动期->稳定闭合状态。为了屏蔽掉抖动状态中的无效数据,可以再进入抖动状态时(检测到第一个闭合状态)执行一个延时程序(5-10ms),等延时完之后再来检测一遍按键的状态。这样就在稳定断开和稳定闭合状态过程中消除了抖动。

        2.从闭合到断开的过程为:稳定闭合状态->抖动期->稳定断开状态。同样在检测到第一个断开状态时,执行一个延时调过抖动期,等延时完再对按键状态做一个检测。

        问题二:CPU效率问题

        为了提高CPU的工作效率,采用外部中断来实现对按键的处理,外部中断由中断控制器(NVIC)来控制,是可以屏蔽的中断。

        产生中断之后应该尽快通知CPU,通知完成后迅速释放掉CPU,让CPU来选择其下一步要处理的程序。否则CPU会因为中断处理程序的占用而无法及时相应其他的中断和处理其他的程序。

        问题三:扩展的连发功能

        耳机的mic上报功能,若mic按键在down状态下repeate较长时间,那么会被上层认为是长按。这里作为扩展功能,有时按键需要进行连发处理,即按住不动时连续发送按键,与长按有所不同。这里采用状态机来描述。{断开状态 、  抖动期 、  短按计数期  、 长按计数期 } ,这里对短按和长按做了区分,短按时间内若断开,直接进入断开状态;否则进入长按状态。


3.Linux中断系统*

        参考《Linux的中断系统》,我对Linux的中断系统作了简要分析,主要是流程和关键问题。     

        问题一:为什么中断不能休眠

                 参考 http://bbs.chinaunix.net/thread-2288808-1-1.html 问题较复杂,没分析清楚

        问题二:中断为什么不可重入

                没分析完全,主要是指上半部,大概原因是同种中断重入增加了底层编程复杂度。

        问题三:中断有无优先级

                 Linux中断没有优先级,因为其目标不是为了实现实时系统。

        问题四:上半部和下半步问题

               下半部存在的根本原因在于提高系统吞吐率,而不是实时性。如果一个中断不分段而又太长,采用实时处理,必然造成其他设备无法被及时响应。

               本质原因是因为Linux的分时特性,吞吐率越高越好,cpu资源需要被各个模块共享,不应该让其被某一个模块单独占用太久。

        问题五:下半部怎么就提高了实时性

               Linux中断系统不是实时的,准确来说是提高了实时响应能力,由于Linux中断没有优先级的关系,因此必须快速响应各种中断,以便各类中断能即使响应。

        问题六:下半部选用哪种方式实现

               易用性考虑,工作队列最简单、接下来是tasklet、最后是软中断。同时考虑到需要延时消抖,采用延时队列也比较合适。

4.工作队列

        CPU工作者线程 ->  工作队列 -> work ->workhandler

        在Linux中断的下半部中,可以考虑将任务交给内核工作队列来处理。

        参考 Linux内核工作队列

5.input子系统的研究

        本着“够用”的开发原则,这里暂时不研究input子系统,后续深入研究。下面给出基本的使用方法。

        使用input子系统的一般流程为:input_allocate_device()申请一个input_dev设备――>初始化该input_dev――>input_register_device()向子系统注册该设备――>中断时input_event()向子系统报告事件。

        1.申请input_dev设备,并初始化

struct input_dev *input_dev = input_allocate_device();
if(!input_dev) {
    ret = -ENOMEM;
    goto failed;
}

input_dev->name = xx;
input_dev->open = xx;
input_dev->close = xx;
input_dev->dev.parent = xx;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;

        2.将设备注册到input子系统中

ret = input_register_device();

        3.中断中向子系统报告事件

input_report_key(input_dev, KEY_CODE, HOOK_DOWN_IN_ANDROID);
input_sync(input_dev);

        4.释放掉设备

ret = input_free_device();

6.Linux定时器

        动态定时器是使用软件来实现的,它的用法很简单,实现部分可以稍作研究(哈希表+双链表实现, 内存使用一定不错,类似cache的管理)。关于Linux定时器子系统,这一部分比较复杂,可以在本驱动完成之后深入研究。   

        参考Linux内核定时器

7.内核同步问题

        



8.Linux驱动设计

        真实的Linux驱动采用分层的架构,上面描述过,即 设备->设备操作层->驱动核心层->上层接口。首先考虑“设备->设备操作层”的实现,这里是用外部中断来实现的,设备的操作层需要实现GPIO引脚的初始化、中断的配置,采用内核定时器消除抖动;其次要考虑“驱动核心层”的内容,这里驱动核心层是一个input子系统,因此有必要稍作研究;

        1.总体结构图

        

        两个中断源hp_det、hook,它们均采用延时队列来处理下半部工作,同时延时队列对其进行消抖处理。在耳机插入后,由于插头会碰触到mic引脚可能会造成中断发出,因此在耳机稳定插入前(为out状态时)mic中断需要关闭,当耳机稳定插入后的一段时间再打开mic中断,使mic可以正常使用。这里耳机中断产生后,采用定时器来打开mic中断。当耳机拔出时,删除定时器,并禁止mic中断。这里的定时器的作用是为了消除无效mic中断(包括抖动)。

       其中拔插耳机,和按键的闭合都会产生中断,这里使用的方法是在中断处理中,改变当前中断的触发类型。若在donw时产生了中断,那么久在中断处理中将改中断设置成为up时产生中断。

        2.中断的注册

        确定GPIO具体引脚,其中HP_DET对应引脚为GPIO0_B4,HP_HOOK对应引脚为GPIO1_B2。同时根据上面对硬件的分析,确定了GPIO0_B4和GPIO1_B2为低电平时会触发中断。中断触发过程可以参考3.Linux中断系统。关于GPIO的学习,可以参考GPIO接口解析 g​p​i​o​子​系​统以及mdelay函数

gpio_request(RK30_PIN0_PB4, "headset_det");              //申请一个GPIO引脚,

iomux_set(GPIO0_B4);                                     //GPIO引脚复用设置
gpio_direction_input(RK30_PIN0_PB4);                     //设置输入、输出方向
gpio_pull_updown(RK30_PIN0_PB4, 1);                      //GPIO上拉
mdelay(50);                                              //延时

usigned int headset_irq = gpio_to_irq(RK30_PIN0_PB4);
request_irq(headset_irq, headset_interrupt, IRQF_TRIGGER_LOW, "headset_irq", NULL);
enable_irq_wake(headset_irq);<span style="white-space:pre">				</span> //加入中断唤醒功能
//=======================================================================================

gpio_request(RK30_PIN1_PB2, "hook_det");                 //申请一个GPIO引脚,

iomux_set(GPIO1_B2);                                     //GPIO引脚复用设置
gpio_direction_input(RK30_PIN1_PB2);                     //设置输入、输出方向
gpio_pull_updown(RK30_PIN1_PB2, 1);                      //GPIO上拉
mdelay(50);                                              //延时

usigned int hook_irq = gpio_to_irq(RK30_PIN1_PB2);
request_irq(hook_irq, hook_interrupt, IRQF_TRIGGER_LOW, "hook_irq", NULL);
disable_irq(hook_irq);
//=======================================================================================

static irqreturn_t headset_interrupt(int irq, void *dev_id)
{
	DBG("---headset_interrupt---\n");	

	return IRQ_HANDLED;
}

static irqreturn_t hook_interrupt(int irq, void *dev_id)
{
	DBG("---hook_interrupt---\n");	

	return IRQ_HANDLED;
}

        3.延时队列消抖

struct delay_work hook_delay_work;

static void Hook_work(struct work_struct *work) {

}

INIT_DELAYED_WORK(&hook_delay_work, Hook_work);
schedule_delayed_work(&hook_delay_work, msecs_to_jiffies(100));

        4.内核定时器消抖

struct timer_list headset_timer;
static void head_timer_handler(unsigned long arg) {

}

setup_timer(headset_timer, headset_timer_handler, (unsigned long) arg);
add_timer(&headset_timer);
del_timer(&headset_timer);

        5.扩展的连发功能设计(耳机驱动不需要,纯属好玩,不过也值得学习)

        

         这里任然采用延时队列消除抖动,采用中断和定时器的方式,来处理状态转移。当进入短按状态时,进行一次上报,并启动定时器准备进入长按状态。若此时输入一个up中断,那么立即取消定时器;若此时没有up中断产生,那么会进入长按状态,在定时器中循环上报,直到产生一个up中断来取消定时器。

9.Linux驱动实现

        1.驱动模块


        2.总线的使用


        3.延时处理


        4.状态转移

        

10.Linux驱动总结





       

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值