文章目录前言一、设备树及原理图二、accdet机制三、mt6357-accdet.c 代码分析3.1 mtk_accdet_irq_handler_thread代码分析3.2 eint_work_callback分析3.3 accdet_work_callback3.4 dis_micbias_timerhandler3.5 dis_micbias_work_callback3.6 delay_init_timerhandler和delay_init_work_callback如有错误请指正谢谢前言有关mt6357的accdet相关的一些mode及一些配置的设置我这里还不是很清楚只能先按照代码简单的分析如涉及到寄存器相关的修改还需要仔细斟酌。一、设备树及原理图mt6357.dtsipmic_accdet:pmic_accdet{compatiblemediatek,mt6357-accdet;accdet-namemt63xx-accdet;accdet-mic-vol6;accdet-plugout-debounce1;accdet-mic-mode1;headset-mode-setting0x5000x50010x1f00x8000x8000x200x44;headset-eint-level-pol4;headset-use-ap-eint0;headset-eint-num0;headset-eint-trig-mode0;headset-key-mode0;headset-three-key-threshold080220400;headset-three-key-threshold-CDD0121192600;headset-four-key-threshold058121192400;io-channelspmic_auxadc AUXADC_ACCDET;io-channel-namespmic_accdet;nvmempmic_efuse;nvmem-namesmt63xx-accdet-efuse;statusokay;};原理图如下注意红框里的两个电阻需要去除不然插入4段耳机micbias1打开accdet也不会得到约0.7V的分压即AB 比较器会一直是00 而不是01即不会检测到4段耳机。二、accdet机制三、mt6357-accdet.c 代码分析probe函数staticintaccdet_probe(structplatform_device*pdev){/* register pmic interrupt *///这个中断是当耳机插入后不管三段还是四段耳机都会触发后续会中断分析mtk_accdet_irq_handler_thread这个中断函数retdevm_request_threaded_irq(pdev-dev,accdet-accdet_irq/*accdet-accdet_eint0、accdet-accdet_eint1*/,NULL,mtk_accdet_irq_handler_thread,IRQF_TRIGGER_HIGH|IRQF_ONESHOT,ACCDET_IRQ,accdet);/* setup timer 这个是插入耳机后开启micbias电压(约2.5V)micbias定时6s是否要关闭的定时器中断*/timer_setup(micbias_timer,dis_micbias_timerhandler,0);micbias_timer.expiresjiffiesMICBIAS_DISABLE_TIMER;//这个定时器执行的delay_init_timerhandler里面主要是对一些配置进行初始化如micbias的输出电压设置timer_setup(accdet_init_timer,delay_init_timerhandler,0);accdet_init_timer.expiresjiffiesACCDET_INIT_WAIT_TIMER;//accdet_work_callback这个func后面会详细分析主要是对3段还是4段耳机的判断具体是查看AB比较INIT_WORK(accdet-accdet_work,accdet_work_callback);//dis_micbias_work_callback这个func是micbias关闭的具体函数上面定时器到时会对这个线程func进行唤醒INIT_WORK(accdet-dis_micbias_work,dis_micbias_work_callback);//这个是耳机的插入拔出检测后面会进行分析INIT_WORK(accdet-eint_work,eint_work_callback);}根据上述probe函数我们列出了一些我们感兴趣的函数mtk_accdet_irq_handler_thread3.1eint_work_callback3.2accdet_work_callback3.3dis_micbias_timerhandler3.4dis_micbias_work_callback3.5delay_init_timerhandler3.6这个不是重点如果需要修改初始化的内容需要改动到这些地方3.1 mtk_accdet_irq_handler_thread代码分析插入耳机后不论三段还是四段耳机会触发这个中断staticirqreturn_tmtk_accdet_irq_handler_thread(intirq,void*data){//直接分析这个accdet_irq_handle();returnIRQ_HANDLED;}voidaccdet_irq_handle(void){u32 eintID0;u32 irq_status0,acc_sts0,eint_sts0;unsignedintmoisture_vol0;//下面这些是获取一些状态eintIDget_triggered_eint();irq_statusaccdet_read(ACCDET_IRQ_ADDR);acc_stsaccdet_read(ACCDET_MEM_IN_ADDR);eint_stsaccdet_read(ACCDET_EINT0_MEM_IN_ADDR);if((irq_statusACCDET_IRQ_MASK_SFT)(eintID0)){printk(%s zzh \n,__func__);clear_accdet_int();//accdet_queue_work这个函数很重要//主要是会将accdet_work_callback函数列入工作队列//详细分析见3.3//accdet_work_callback这个func主要是判断是通过AB判断 是3段耳机 还是4段耳机 然后上报给Android系统是否带麦的图标就是这里触发的accdet_queue_work();clear_accdet_int_check();}elseif(eintID!NO_PMIC_EINT){//一般会先走这个中断pr_info(%s() zzh IRQ:0x%x, eint-%s trig. cur_eint_state:%d\n,__func__,irq_status,(eintIDPMIC_EINT0)?0:((eintIDPMIC_EINT1)?1:BI),accdet-cur_eint_state);/* check EINT0 status */accdet-eint_idaccdet_read_bits(ACCDET_EINT0_MEM_IN_ADDR,ACCDET_EINT0_MEM_IN_SFT,ACCDET_EINT0_MEM_IN_MASK);eint_polarity_reverse(eintID);clear_accdet_eint(eintID);clear_accdet_eint_check(eintID);//然后会执行到这个func触发pmic_eint_queue_work(eintID);}else{pr_notice(%s no interrupt detected!\n,__func__);}}staticintpmic_eint_queue_work(inteintID){intret0;if(HAS_CAP(accdet-data-caps,ACCDET_PMIC_EINT0)){if(eintIDPMIC_EINT0){if(accdet-cur_eint_stateEINT_PLUG_IN){accdet_set_debounce(eint_state000,ACCDET_EINT0_DEB_IN_256);accdet_set_debounce(accdet_state011,cust_pwm_deb-debounce3);accdet-cur_eint_stateEINT_PLUG_OUT;}else{accdet_set_debounce(eint_state000,ACCDET_EINT0_DEB_OUT_012);if(accdet-eint_id!M_PLUG_OUT){//插入会走这里这个状态会被改变为EINT_PLUG_INaccdet-cur_eint_stateEINT_PLUG_IN;//这个就是开启了micbias电压的定时器定时6smod_timer(micbias_timer,jiffiesMICBIAS_DISABLE_TIMER);}}//这里就会将eint_work_callback函数排入工作队列具体这一段做了什么见3.2retqueue_work(accdet-eint_workqueue,accdet-eint_work);}elsepr_notice(%s invalid EINT ID!\n,__func__);}//还有一些其他类似的判断就不看了这里只看核心的部分。。。returnret;}3.2 eint_work_callback分析staticvoideint_work_callback(structwork_struct*work){if(accdet-cur_eint_stateEINT_PLUG_IN){//耳机插入就走这个判断mutex_lock(accdet-res_lock);//这个标志位应该是处理事件的标志位在多个线程中都会有使用accdet-eint_sync_flagtrue;mutex_unlock(accdet-res_lock);__pm_wakeup_event(accdet-timer_lock,jiffies_to_msecs(7*HZ));printk(%s zzh EINT_PLUG_IN\n,__func__);//这个初始化应该是一些范围值的初始化accdet_init();...enable_accdet(0);}else{//耳机拔出就走这个判断printk(%s zzh EINT_PLUG_OUT\n,__func__);mutex_lock(accdet-res_lock);accdet-eint_sync_flagfalse;accdet-thing_in_flagfalse;mutex_unlock(accdet-res_lock);del_timer_sync(micbias_timer);/* disable accdet_sw_en0 */accdet_clear_bits(ACCDET_CMP_PWM_IDLE_ADDR,ACCDET_CMP_PWM_IDLE_SFT,0x7,0x7);disable_accdet();//这个会上报一个事件给到Androidheadset_plug_out();//这个是修改极性的函数详细见极性相关的部分accdet_for_plug_highlow();}if(HAS_CAP(accdet-data-caps,ACCDET_AP_GPIO_EINT))enable_irq(accdet-gpioirq);}总的来说这个工作函数主要是做一些耳机插入初始化和耳机拔出上报Android系统层的工作3.3 accdet_work_callbackstaticvoidaccdet_work_callback(structwork_struct*work){//获取先前的cable 类型u32 pre_cable_typeaccdet-cable_type;__pm_stay_awake(accdet-wake_lock);//这个func会对耳机的类型进行判断check_cable_type();mutex_lock(accdet-res_lock);if(accdet-eint_sync_flag){if(pre_cable_type!accdet-cable_type){printk(%s zzh pre_cable_type ! accdet-cable_type \n,__func__);//这个就是上报耳机是否带麦的事件具体看if里的判断//同时如果检测到是带耳机的事件HEADSET_MIC 1会把micbias的disable定时器给关掉send_status_event(accdet-cable_type,1);}else{printk(%s zzh pre_cable_type accdet-cable_type \n,__func__);}}mutex_unlock(accdet-res_lock);__pm_relax(accdet-wake_lock);}/* * 关于三四段耳机的插入判断主要是在这个func里进行判断重要 * 如果是三段耳机这个函数会执行一次加上之前的总共触发两次如果是4段耳机这个函数会执行两次中断加上之前的总共触发3次 */staticinlinevoidcheck_cable_type(void){u32 cur_AB0;cur_ABaccdet_read(ACCDET_MEM_IN_ADDR)ACCDET_STATE_MEM_IN_OFFSET;cur_ABcur_ABACCDET_STATE_AB_MASK;accdet-button_status0;printk(%s zzh accdet-accdet_status %d, cur_AB 0x%x, eint_sync_flag %d \n,__func__,accdet-accdet_status,cur_AB,accdet-eint_sync_flag);//最开始accdet-accdet_status PLUG_OUTcur_AB ACCDET_STATE_AB_00//如果是4段耳机且accdet端的电压符合0.4V~1.7V之间会跑第二次//此时accdet_status HOOK_SWITCHcur_AB ACCDET_STATE_AB_01switch(accdet-accdet_status){casePLUG_OUT:if(cur_ABACCDET_STATE_AB_00){mutex_lock(accdet-res_lock);if(accdet-eint_sync_flag){//是不带耳机的accdet-cable_typeHEADSET_NO_MIC;//accdet_status类型是HOOK_SWITCHaccdet-accdet_statusHOOK_SWITCH;}elsepr_notice(accdet hp has been plug-out\n);mutex_unlock(accdet-res_lock);}elseif(...){...}...caseMIC_BIAS:if(cur_ABACCDET_STATE_AB_00){mutex_lock(accdet-res_lock);if(accdet-eint_sync_flag){accdet-button_status1;accdet-accdet_statusHOOK_SWITCH;//这个func应该就是上报耳机的按键事件的func未确认这里就不分析了multi_key_detection(cur_AB);}elsepr_notice(accdet hp has been plug-out\n);mutex_unlock(accdet-res_lock);}elseif(...){...}...caseHOOK_SWITCH:if(cur_ABACCDET_STATE_AB_00){}elseif(cur_ABACCDET_STATE_AB_01){mutex_lock(accdet-res_lock);if(accdet-eint_sync_flag){multi_key_detection(cur_AB);//第二次会走这个到这里就会触发上报了因为cable_type与pre_cable_type已经不一样了accdet-accdet_statusMIC_BIAS;accdet-cable_typeHEADSET_MIC;}elsepr_notice(accdet hp has been plug-out\n);mutex_unlock(accdet-res_lock);}elseif(...){...}...}}3.4 dis_micbias_timerhandler在3.1小节中的pmic_eint_queue_work中会触发这个定时器具体的内容会在6s后执行但是见3.3小节中的accdet_work_callback中的send_status_event(accdet-cable_type, 1);1表示的是HEADSET_MIC在send_status_event中会把这个定时器给关掉即当检测到是带麦的耳机后会执行send_status_event(accdet-cable_type, 1)此时会把该定时器关掉micbias的电压。这里看一下是如何disable这个micbias的staticvoiddis_micbias_timerhandler(structtimer_list*t){intret0;//这里会将一个工作函数列入工作队列中这个工作函数我们会在3.5小节讲解retqueue_work(accdet-dis_micbias_workqueue,accdet-dis_micbias_work);if(!ret)pr_notice(Error: %s (%d)\n,__func__,ret);}3.5 dis_micbias_work_callback这里主要是disable_accdet控制寄存器关闭micbiasstaticvoiddis_micbias_work_callback(structwork_struct*work){u32 cur_AB0,eintID0;printk(%s zzh \n,__func__);/* check EINT0 status, if plug out, * not need to disable accdet here */eintIDaccdet_read_bits(ACCDET_EINT0_MEM_IN_ADDR,ACCDET_EINT0_MEM_IN_SFT,ACCDET_EINT0_MEM_IN_MASK);if(eintIDM_PLUG_OUT){pr_notice(%s Plug-out, no dis micbias\n,__func__);return;}/* if modify_vref_volt called, not need to dis micbias again */if(dis_micbias_donetrue){pr_notice(%s modify_vref_volt called\n,__func__);return;}//这里再次检查这个AB 比较器是否为 带麦耳机插入cur_ABaccdet_read(ACCDET_MEM_IN_ADDR)ACCDET_STATE_MEM_IN_OFFSET;cur_ABcur_ABACCDET_STATE_AB_MASK;/* if 3pole disable accdet * if 20k 4pole, disable accdet will disable accdet * plug out interrupt. The behavior will same as 3pole */if((accdet-cable_typeHEADSET_NO_MIC)||(cur_ABACCDET_STATE_AB_00)||(cur_ABACCDET_STATE_AB_11)){/* disable accdet_sw_en0 * disable accdet_hwmode_en0 *//* setting pwm idle; */accdet_clear_bits(ACCDET_CMP_PWM_IDLE_ADDR,ACCDET_CMP_PWM_IDLE_SFT,0x7,0x7);printk(%s zzh \n,__func__);disable_accdet();}}3.6 delay_init_timerhandler和delay_init_work_callbackstaticvoiddelay_init_timerhandler(structtimer_list*t){intret0;retqueue_work(accdet-delay_init_workqueue,accdet-delay_init_work);if(!ret)pr_notice(Error: %s (%d)\n,__func__,ret);}staticvoiddelay_init_work_callback(structwork_struct*work){//下面这几个函数都是probe执行时 设置一些配置accdet_init();accdet_init_debounce();accdet_init_once();pr_info(%s() done\n,__func__);}如有错误请指正谢谢