mtk-accdet学习文档

硬件和框图

Headset accdet常用宏:

在accdet.c和accdet_custome.h为了实现一些功能有用到很多宏开关,把目前相应的宏开关介绍如下 :
1: ACCDET_EINT
是否启用了外部中断来侦测是否有耳机插入和拔出

2: ACCDET_MULTI_KEY_FEATURE
是否支持使用A/D来侦测key,这里要注意的是即使耳机只有一个key若是在89的平台也需打开此宏 开关

3: ACCDET_LOW_POWER
当插入三段耳机6s后自动关闭micbias,达到省电的目的
以上三个宏可以看成一个宏

4: ACCDET_28v_MODE
在我们内部有一个switch是针对外部耳机是用2.8还是1.9V的切换开关,美标的是2.8V, 国标的是 1.9V

5: ACCDET_PIN_RECOGNIZATION
美标的插孔识别国标的耳机,国标的耳机识别美标的插孔,目前这个功能还没有实现,此宏不能打开

6: ACCDET_SHORT_PLUGOUT_DEBOUNCE
ACCDET_SHORT_PLUGOUT_DEBOUNCE_CN 25
拔出耳机后有时候图标会再弹出后在消失, 主要解决类似bug

7: ACCDET_PIN_SWAP
美标的插孔识别国标的耳机,这个时候需要借助accdet的一个上拉电阻,当有这种情形的时候AB一 直为0,达到检测到的目的,当然也有误判的时候, 4段耳机按住按键插入后会有误判

Probe分析:

Accdet probe 中所做的事:
1. 客制化参数(ACCDET PWM设置,按键检测相关配置参数),通过全局变量获取。

  1. ret = switch_dev_register(&accdet_data); 注册SWITCH设备。

  2. 注册字符设备,关注其IOCTL接口:

    switch(cmd)
    {
        case ACCDET_INIT :
        break;
    
        case SET_CALL_STATE :
            call_status = (int)arg;
            ACCDET_DEBUG("[Accdet]accdet_ioctl : CALL_STATE=%d \n", call_status);
            break;
    
        case GET_BUTTON_STATUS :
            ACCDET_DEBUG("[Accdet]accdet_ioctl : Button_Status=%d (state:%d)\n", button_status, accdet_data.state); 
            return button_status;
    
        default:
        ACCDET_DEBUG("[Accdet]accdet_ioctl : default\n");
        break;
    }
    
  3. 注册 input设备,定义按键。

    kpd_accdet_dev = input_allocate_device();
    //define multi-key keycode
    __set_bit(EV_KEY, kpd_accdet_dev->evbit);
    __set_bit(KEY_CALL, kpd_accdet_dev->keybit);
    __set_bit(KEY_ENDCALL, kpd_accdet_dev->keybit);
    __set_bit(KEY_NEXTSONG, kpd_accdet_dev->keybit);
    __set_bit(KEY_PREVIOUSSONG, kpd_accdet_dev->keybit);
    __set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit);
    __set_bit(KEY_STOPCD, kpd_accdet_dev->keybit);
    __set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit);
    __set_bit(KEY_VOLUMEUP, kpd_accdet_dev->keybit);
    __set_bit(KEY_VOICECOMMAND, kpd_accdet_dev->keybit);
    
  4. 初始化 处理线程:

    accdet_workqueue = create_singlethread_workqueue("accdet");
    INIT_WORK(&accdet_work, accdet_work_callback);
    
  5. 初始化 WAKELOCK

    wake_lock_init(&accdet_suspend_lock, WAKE_LOCK_SUSPEND, "accdet wakelock");
    wake_lock_init(&accdet_irq_lock, WAKE_LOCK_SUSPEND, "accdet irq wakelock");
    wake_lock_init(&accdet_key_lock, WAKE_LOCK_SUSPEND, "accdet key wakelock");
    wake_lock_init(&accdet_timer_lock, WAKE_LOCK_SUSPEND, "accdet timer wakelock");
    
  6. 注册回调

    pmic_register_interrupt_callback(12, accdet_int_handler );
    pmic_register_interrupt_callback(13, accdet_eint_int_handler );
    
  7. 初始化accdet 硬件,并第一次检测

    long_press_time = press_key_time->headset_long_press_time;
    
    ACCDET_DEBUG("[Accdet]accdet_probe : ACCDET_INIT\n");  
    if (g_accdet_first == 1) 
    {   
        long_press_time_ns = (s64)long_press_time * NSEC_PER_MSEC;
    
        eint_accdet_sync_flag = 1;
        //Accdet Hardware Init
        accdet_init();   
        queue_work(accdet_workqueue, &accdet_work); //schedule a work for the first detection               
        #ifdef ACCDET_EINT
          accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable");  // 此线程作用,3端耳机插入的话,会停掉ACCDET检测。
      INIT_WORK(&accdet_disable_work, disable_micbias_callback);
        accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint");  // 此线程作用,
      INIT_WORK(&accdet_eint_work, accdet_eint_work_callback);
      accdet_setup_eint();  初始化中断
        #endif
        g_accdet_first = 0;
    }
    

耳机插入流程分析:

  1. 耳机插入—-EINT——accdet_eint_func GPIO中断处理函数

    static irqreturn_t  accdet_eint_func(int irq,void *data)
    {
    
        int ret=0;
        if(cur_eint_state ==  EINT_PIN_PLUG_IN )  // 如果当前是插入状态,中断来了则是拔出
        {
        /*
        To trigger EINT when the headset was plugged in
        We set the polarity back as we initialed.
        */
        #ifndef ACCDET_EINT_IRQ  变换中断边沿
            if (CUST_EINT_ACCDET_TYPE == CUST_EINTF_TRIGGER_HIGH){
            irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_HIGH);
            }else{
            irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_LOW);
            }
        #endif
        #ifdef ACCDET_EINT_IRQ
            pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL)&(~(7<<4)));
            pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL)|EINT_IRQ_DE_IN);//debounce=256ms
        #else
            mt_gpio_set_debounce(gpiopin,headsetdebounce);
        #endif
    
            /* update the eint status */
            cur_eint_state = EINT_PIN_PLUG_OUT;  // 设置状态
    //#ifdef ACCDET_LOW_POWER
    //      del_timer_sync(&micbias_timer);
    //#endif
        } 
        else  // 当前是拔出状态,则是检测到插入
        {
    
        #ifndef ACCDET_EINT_IRQ  
            if (CUST_EINT_ACCDET_TYPE == CUST_EINTF_TRIGGER_HIGH){
            irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_LOW);
            }else{
            irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_HIGH);
            }
        #endif
    
        #ifdef ACCDET_EINT_IRQ
            pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL)&(~(7<<4)));
            pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL)|EINT_IRQ_DE_OUT);//debounce=16ms
        #else
            mt_gpio_set_debounce(gpiopin,ACCDET_SHORT_PLUGOUT_DEBOUNCE_CN*1000);
        #endif  
            /* update the eint status */
            cur_eint_state = EINT_PIN_PLUG_IN;
    
            //INIT the timer to disable micbias.  如果3端耳机6秒后关闭。
    
            init_timer(&micbias_timer);
            micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER;
            micbias_timer.function = &disable_micbias;
            micbias_timer.data = ((unsigned long) 0 );
            add_timer(&micbias_timer);
        }
    
        重点是下面的代码,只要是状态发生变化,就会调用WORK处理 accdet_eint_work_callback
        ret = queue_work(accdet_eint_workqueue, &accdet_eint_work); 
        return IRQ_HANDLED;
    }
    

——-》 accdet_eint_work_callback

static void accdet_eint_work_callback(struct work_struct *work)
{
   //KE  under fastly plug in and plug out
    #ifdef CONFIG_OF
    disable_irq(accdet_irq);
     #else
    mt_eint_mask(CUST_EINT_ACCDET_NUM);
     #endif


if (cur_eint_state == EINT_PIN_PLUG_IN) {  新状态是插入
        ACCDET_DEBUG("[Accdet]EINT func :plug-in\n");
        mutex_lock(&accdet_eint_irq_sync_mutex); // 同步
        eint_accdet_sync_flag = 1;
        mutex_unlock(&accdet_eint_irq_sync_mutex);
        wake_lock_timeout(&accdet_timer_lock, 7*HZ);
        accdet_init();// do set pwm_idle on in accdet_init  
        //set PWM IDLE  on
        pmic_pwrap_write(ACCDET_STATE_SWCTRL, (pmic_pwrap_read(ACCDET_STATE_SWCTRL)|ACCDET_SWCTRL_IDLE_EN));
    //enable ACCDET unit
        enable_accdet(ACCDET_SWCTRL_EN);  打开ACCDET,然后等ACCDET的中断回调处理
} else {新状态是拔出
 //EINT_PIN_PLUG_OUT
 //Disable ACCDET
        ACCDET_DEBUG("[Accdet]EINT func :plug-out\n");
        mutex_lock(&accdet_eint_irq_sync_mutex);  同步
        eint_accdet_sync_flag = 0;
        mutex_unlock(&accdet_eint_irq_sync_mutex);
        del_timer_sync(&micbias_timer);  移除6S的timer
            //accdet_auxadc_switch(0);
            disable_accdet();     关闭ACCDET 
            headset_plug_out();  上报SWITCH
}
 #ifdef CONFIG_OF
    enable_irq(accdet_irq);
 #else
    mt_eint_unmask(CUST_EINT_ACCDET_NUM);
 #endif
}

—–》 打开后PMIC会收到硬件中断,然后会回调我们在accdet中注册的回调。注册了2个回调,实际上两个回调都会在内部调用同一个函数来处理。

void accdet_int_handler(void)
{
int ret=0;

ACCDET_DEBUG("[accdet_int_handler]....\n");
ret = accdet_irq_handler();
if(0 == ret){
ACCDET_DEBUG("[accdet_int_handler] don't finished\n");
}
}

void accdet_eint_int_handler(void)
{
int ret=0;

ACCDET_DEBUG("[accdet_eint_int_handler]....\n");
    ret = accdet_irq_handler();
    if(0 == ret){
ACCDET_DEBUG("[accdet_int_handler] don't finished\n");
}
}

———- accdet_irq_handler() 分析

int accdet_irq_handler(void)
{
    U64 cur_time = 0;
    cur_time = accdet_get_current_time();

    if((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT)) {  //  clear INT
        clear_accdet_interrupt();
    }
if (accdet_status == MIC_BIAS){  // 如果是带MIC的,配置相应的硬件
        //accdet_auxadc_switch(1);
    pmic_pwrap_write(ACCDET_PWM_WIDTH, REGISTER_VALUE(cust_headset_settings->pwm_width));
        pmic_pwrap_write(ACCDET_PWM_THRESH, REGISTER_VALUE(cust_headset_settings->pwm_width));
}
accdet_workqueue_func();   状态处理函数
    while(((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) && 
           (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT)))) {  等待超时,硬件稳定相关的。
    }
}

—————-》 accdet_workqueue_func();

static void accdet_workqueue_func(void)
{
    int ret;
    ret = queue_work(accdet_workqueue,  &accdet_work);  
 }

——–》 accdet_work_callback

static void accdet_work_callback(struct work_struct *work)
{
wake_lock(&accdet_irq_lock);   
check_cable_type();   状态机处理

    mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
        switch_set_state((struct switch_dev *)&accdet_data, cable_type);  // 上报SWITCH
}else {
        ACCDET_DEBUG("[Accdet] Headset has plugged out don't set accdet state\n");
    }
    mutex_unlock(&accdet_eint_irq_sync_mutex);
    ACCDET_DEBUG( " [accdet] set state in cable_type  status\n");

wake_unlock(&accdet_irq_lock);
}

————–》 static inline void check_cable_type(void)

static inline void  check_cable_type(void)
{
int current_status = 0;
    int irq_temp = 0; //for clear IRQ_bit
    int wait_clear_irq_times = 0;
 #ifdef ACCDET_PIN_RECOGNIZATION
int pin_adc_value = 0;
 #define PIN_ADC_CHANNEL 5
 #endif

    // 读取2个比较器的状态,AB, 
current_status = ((pmic_pwrap_read(ACCDET_STATE_RG) & 0xc0)>>6); //A=bit1; B=bit0
ACCDET_DEBUG("[Accdet]accdet interrupt happen:[%s]current AB = %d\n", 
        accdet_status_string[accdet_status], current_status);

button_status = 0;
pre_status = accdet_status;  保存现在状态

ACCDET_DEBUG("[Accdet]check_cable_type: ACCDET_IRQ_STS = 0x%x\n", pmic_pwrap_read(ACCDET_IRQ_STS));
IRQ_CLR_FLAG = FALSE;
switch(accdet_status)
{
    case PLUG_OUT:

        if(current_status == 0)
        {
            mutex_lock(&accdet_eint_irq_sync_mutex);
              if(1 == eint_accdet_sync_flag) {  EINT检测到中断已经插入
                cable_type = HEADSET_NO_MIC;  
                accdet_status = HOOK_SWITCH;   ?? 这个地方仍有点疑问
              }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
              }
              mutex_unlock(&accdet_eint_irq_sync_mutex);

        }
        else if(current_status == 1) 
        {
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {  // gpio 插入, 
                accdet_status = MIC_BIAS;       
                cable_type = HEADSET_MIC;
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
            //ALPS00038030:reduce the time of remote button pressed during incoming call
            //solution: reduce hook switch debounce time to 0x400
            pmic_pwrap_write(ACCDET_DEBOUNCE0, button_press_debounce);
        }
        else if(current_status == 3)  
        {
            ACCDET_DEBUG("[Accdet]PLUG_OUT state not change!\n");
            #ifdef ACCDET_EINT  仍然是 PLUG  OUT 
                ACCDET_DEBUG("[Accdet] do not send plug out event in plug out\n");
            #else
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {
                accdet_status = PLUG_OUT;       
                cable_type = NO_DEVICE;
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
            #endif
        }
        else
        {
            ACCDET_DEBUG("[Accdet]PLUG_OUT can't change to this state!\n"); 
        }
        break;

    case MIC_BIAS:
    //ALPS00038030:reduce the time of remote button pressed during incoming call
        //solution: resume hook switch debounce time
        pmic_pwrap_write(ACCDET_DEBOUNCE0, cust_headset_settings->debounce0);

        if(current_status == 0)  // 00 
        {
        mutex_lock(&accdet_eint_irq_sync_mutex);
        if(1 == eint_accdet_sync_flag) {  //  下面延时稳定, 15MS
            while((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) && (wait_clear_irq_times<3))
            {
              ACCDET_DEBUG("[Accdet]check_cable_type: MIC BIAS clear IRQ on-going1....\n"); 
              wait_clear_irq_times++;
              msleep(5);
            }
            irq_temp = pmic_pwrap_read(ACCDET_IRQ_STS);
            irq_temp = irq_temp & (~IRQ_CLR_BIT);
            pmic_pwrap_write(ACCDET_IRQ_STS, irq_temp);
            IRQ_CLR_FLAG = TRUE;
            accdet_status = HOOK_SWITCH;  // 按键按下状态
        }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
        }
        mutex_unlock(&accdet_eint_irq_sync_mutex);
        button_status = 1;
        if(button_status)
        {   
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {   
                multi_key_detection(current_status);  AD 采样并上报按键键值。 注意current_status 为0, 上报的是按键按下。
            }else {
                ACCDET_DEBUG("[Accdet] multi_key_detection: Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
            //accdet_auxadc_switch(0);
        //recover  pwm frequency and duty
            pmic_pwrap_write(ACCDET_PWM_WIDTH, REGISTER_VALUE(cust_headset_settings->pwm_width));
            pmic_pwrap_write(ACCDET_PWM_THRESH, REGISTER_VALUE(cust_headset_settings->pwm_thresh));
        }
      }
      else if(current_status == 1)
      {
         mutex_lock(&accdet_eint_irq_sync_mutex);
         if(1 == eint_accdet_sync_flag) {
            accdet_status = MIC_BIAS;       
            cable_type = HEADSET_MIC;
            ACCDET_DEBUG("[Accdet]MIC_BIAS state not change!\n");
         }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
         }
         mutex_unlock(&accdet_eint_irq_sync_mutex);
      }
      else if(current_status == 3)
      {
       #if defined ACCDET_EINT || defined ACCDET_EINT_IRQ
            ACCDET_DEBUG("[Accdet]do not send plug ou in micbiast\n");
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {
                accdet_status = PLUG_OUT;
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
       #else
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {
                accdet_status = PLUG_OUT;       
                cable_type = NO_DEVICE;
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
       #endif
      }
      else
       {
           ACCDET_DEBUG("[Accdet]MIC_BIAS can't change to this state!\n"); 
       }
      break;

case HOOK_SWITCH:
        if(current_status == 0)
        {
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {  按键仍旧按下
                //for avoid 01->00 framework of Headset will report press key info for Audio
                //cable_type = HEADSET_NO_MIC;
                //accdet_status = HOOK_SWITCH; 
                ACCDET_DEBUG("[Accdet]HOOK_SWITCH state not change!\n");
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
        }
        else if(current_status == 1)
        {
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {            
                multi_key_detection(current_status); current_status 为1, 按键弹起。
                accdet_status = MIC_BIAS;       
                cable_type = HEADSET_MIC;
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);
            //accdet_auxadc_switch(0);
            pmic_pwrap_write(ACCDET_DEBOUNCE0, button_press_debounce);
       }
        else if(current_status == 3)
        {

            ACCDET_DEBUG("[Accdet] do not send plug out event in hook switch\n"); 
            mutex_lock(&accdet_eint_irq_sync_mutex);
            if(1 == eint_accdet_sync_flag) {
                accdet_status = PLUG_OUT;
            }else {
                ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
            }
            mutex_unlock(&accdet_eint_irq_sync_mutex);

        }
        else
        {
            ACCDET_DEBUG("[Accdet]HOOK_SWITCH can't change to this state!\n"); 
        }
        break;          
case  STAND_BY:
        if(current_status == 3)   只处理3 的状态。
        {
             #if defined ACCDET_EINT || defined ACCDET_EINT_IRQ
                    ACCDET_DEBUG("[Accdet]accdet do not send plug out event in stand by!\n");
             #else
                    mutex_lock(&accdet_eint_irq_sync_mutex);
                     if(1 == eint_accdet_sync_flag) {
                        accdet_status = PLUG_OUT;       
                        cable_type = NO_DEVICE;
                     }else {
                        ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
                     }
                    mutex_unlock(&accdet_eint_irq_sync_mutex);
            #endif
         }
         else
        {
                ACCDET_DEBUG("[Accdet]STAND_BY can't change to this state!\n"); 
        }
        break;

        default:
            ACCDET_DEBUG("[Accdet]check_cable_type: accdet current status error!\n");
        break;

    }

    if(!IRQ_CLR_FLAG)
    {
        mutex_lock(&accdet_eint_irq_sync_mutex);
        if(1 == eint_accdet_sync_flag) {
            while((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) && (wait_clear_irq_times<3))
            {
              ACCDET_DEBUG("[Accdet]check_cable_type: Clear interrupt on-going2....\n");
              wait_clear_irq_times++;
              msleep(5);
            }
        }
        irq_temp = pmic_pwrap_read(ACCDET_IRQ_STS);
        irq_temp = irq_temp & (~IRQ_CLR_BIT);
        pmic_pwrap_write(ACCDET_IRQ_STS, irq_temp);
        mutex_unlock(&accdet_eint_irq_sync_mutex);
        IRQ_CLR_FLAG = TRUE;
        ACCDET_DEBUG("[Accdet]check_cable_type:Clear interrupt:Done[0x%x]!\n", pmic_pwrap_read(ACCDET_IRQ_STS));    

    }
    else
    {
        IRQ_CLR_FLAG = FALSE;
    }

    ACCDET_DEBUG("[Accdet]cable type:[%s], status switch:[%s]->[%s]\n",
    accdet_report_string[cable_type], accdet_status_string[pre_status], 
    accdet_status_string[accdet_status]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值