mtk 耳機accdet檢測驅動分析

accdet相關驅動程序:

accdet_drv.c accdet_drv.h
accdet.c accdet.h
accdet_custom.c accdet_custom.h
accdet_custom_def.c accdet_custom_def.h

驅動分析

  • accdet_drv.c:繪製驅動大體框圖,描述框架結構
module_init(accdet_mod_init);

static int accdet_mod_init(void)
{
    platform_device_register(&accdet_device);
    platform_driver_register(&accdet_driver);
    return 0;
}

struct platform_device accdet_device = {
    .name     ="Accdet_Driver",
    .id       = -1,
};


static struct platform_driver accdet_driver = {
    .probe = accdet_probe,
    .remove = accdet_remove,
    .driver = {
           .name = "Accdet_Driver",
#ifdef CONFIG_PM
    .pm         = &accdet_pm_ops,
#endif
           },
};

static int accdet_probe(struct platform_device *dev)
{
    mt_accdet_probe();
    return 0;
}
  • accdet.c:填充具體實現細節
    mt_accdet_probe
int mt_accdet_probe(void)   
{
    /*獲取按鍵長按時間*/
    struct headset_key_custom* press_key_time = get_headset_key_custom_setting();
/*------------------------------------
struct headset_key_custom{
int headset_long_press_time;
};
struct headset_key_custom headset_key_custom_setting = {
    2000
};

struct headset_key_custom* get_headset_key_custom_setting(void)
{
    return &headset_key_custom_setting;
}
-------------------------------------*/
    /*註冊開關類設備*/
    accdet_data.name = "h2w";
    accdet_data.index = 0;
    accdet_data.state = NO_DEVICE;
    switch_dev_register(&accdet_data);
/*------------------------------------
accdet_data:全局變量 static struct switch_dev accdet_data;
accdet_data.state的狀態有:
enum accdet_report_state
{
    NO_DEVICE =0,
    HEADSET_MIC = 1,
    HEADSET_NO_MIC = 2,
    //HEADSET_ILEGAL = 3,
    //DOUBLE_CHECK_TV = 4
};
作用:爲上層提供操作接口,註冊成功後會在sys/class/switch/目錄下生成名爲h2w目錄,該目錄下會有name,state等屬性文件,當硬件中斷髮生,驅動會改變這些屬性的值,如state,上層通過讀取這些屬性文件來獲取耳機的狀態等信息。
-------------------------------------*/
    /*獲取耳機的配置信息*/
    cust_headset_settings = get_cust_headset_settings();
/*------------------------------------
struct headset_mode_settings{
    int pwm_width;  //pwm frequence
    int pwm_thresh; //pwm duty  增加該值可提高耳機PWM的佔空比,延長headset芯片內部比較器的work時
間,使雙擊識別率提高,建議值 0x600 或 0x400
    int fall_delay; //falling stable time
    int rise_delay; //rising stable time
    int debounce0;  //hook switch or double check debounce 減小該值也可以提高雙擊識別率 建議值 600
    int debounce1;  //mic bias debounce
    int debounce3;  //plug out debounce
};

static struct headset_mode_settings cust_headset_settings = {
    0x500, 0x200, 1, 0x1f0, 0x800, 0x8000, 0x0
};
struct headset_mode_settings* get_cust_headset_settings(void)
{
    return &cust_headset_settings;
}
-------------------------------------*/
    /*註冊字符設備對象並自動創建設備節點inode*/
    alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME);//
    accdet_cdev = cdev_alloc();//static struct cdev *accdet_cdev
    accdet_cdev->owner = THIS_MODULE;
    accdet_cdev->ops = accdet_get_fops();
    cdev_add(accdet_cdev, accdet_devno, 1);
    accdet_class = class_create(THIS_MODULE, ACCDET_DEVNAME);
    accdet_nor_device = device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME);  
    /*創建並註冊輸入設備*/
    kpd_accdet_dev = input_allocate_device();//static struct input_dev *kpd_accdet_dev;     
    __set_bit(EV_KEY, kpd_accdet_dev->evbit);//按鍵類事件
    __set_bit(KEY_CALL, kpd_accdet_dev->keybit);//call功能按鍵
    __set_bit(KEY_ENDCALL, kpd_accdet_dev->keybit);//結束call功能鍵
    __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);//音量+功能鍵

    kpd_accdet_dev->id.bustype = BUS_HOST;
    kpd_accdet_dev->name = "ACCDET";
    if(input_register_device(kpd_accdet_dev))
    {
        ACCDET_DEBUG("[Accdet]kpd_accdet_dev register : fail!\n");
    }else
    {
        ACCDET_DEBUG("[Accdet]kpd_accdet_dev register : success!!\n");
    } 
    /*INIT the timer to disable micbias. */
    init_timer(&micbias_timer);
    micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER;
    micbias_timer.function = &disable_micbias;
    micbias_timer.data = ((unsigned long) 0 );
    /*創建工作隊列*/
    accdet_workqueue = create_singlethread_workqueue("accdet");
    INIT_WORK(&accdet_work, accdet_work_callback);
    /*初始化喚醒鎖,阻止進入深度睡眠*/
    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");
    /*創建sys節點*/
    #if DEBUG_THREAD
     if((ret = accdet_create_attr(&accdet_driver_hal.driver))!=0) 
     {
        ACCDET_DEBUG("create attribute err = %d\n", ret);
     }
    #endif 
    /*註冊pmic中斷*/ //有可能會在pmic.c中的PMIC_EINT_SETTING函數處註冊
     pmic_register_interrupt_callback(12,accdet_int_handler);
     pmic_register_interrupt_callback(13,accdet_eint_int_handler);  
    /*長按時間*/
     long_press_time = press_key_time->headset_long_press_time;

    ACCDET_DEBUG("[Accdet]accdet_probe : ACCDET_INIT\n"); 

    /*static int g_accdet_first = 1;*/
    if (g_accdet_first == 1) 
    {   
        long_press_time_ns = (s64)long_press_time * NSEC_PER_MSEC;
        #ifdef ACCDET_EINT_IRQ
          accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint");
          INIT_WORK(&accdet_eint_work, accdet_eint_work_callback);
          accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable");
          INIT_WORK(&accdet_disable_work, disable_micbias_callback);
        #endif
        //Accdet Hardware Init
        accdet_init();   
        accdet_pmic_Read_Efuse_HPOffset();
        queue_work(accdet_workqueue, &accdet_work); //schedule a work for the first detection               
        #ifdef ACCDET_EINT
          accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable");
          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;
}

probe爲耳機的插入做好了充分的準備,下面分解兩種耳機的插入與拔出:
三段式耳機插入與拔出:觸發普通中斷
插入->accdet中斷產生->中斷處理函數前半部accdet_eint_func->後半部accdet_eint_work_callback

/*
上半部主要內容:
    1.改變觸發方式
    2.設置延時
    3.改變耳機插入狀態標識cur_eint_state 
    4.關中斷,調度下半部
*/
static irqreturn_t accdet_eint_func(int irq,void *data)
{
    int ret=0;
    /*拔出走這裏*/
    if(cur_eint_state ==  EINT_PIN_PLUG_IN ) 
    {
    /*重置觸發方式,爲下次插入做準備*/
    #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
    /*設置延時*/
        mt_gpio_set_debounce(gpiopin,headsetdebounce);

    /* update the eint status */
        cur_eint_state = EINT_PIN_PLUG_OUT;
    } 
    /*插入走這裏*/
    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
        mt_gpio_set_debounce(gpiopin,ACCDET_SHORT_PLUGOUT_DEBOUNCE_CN*1000);
        /* update the eint status */
        cur_eint_state = EINT_PIN_PLUG_IN;          
        /*開定時器,6秒*/
        mod_timer(&micbias_timer, jiffies + MICBIAS_DISABLE_TIMER);
    }
    #ifndef ACCDET_EINT_IRQ
    disable_irq_nosync(accdet_irq);
    #endif
    ACCDET_DEBUG("[Accdet]accdet_eint_func after cur_eint_state=%d\n", cur_eint_state);
    ret = queue_work(accdet_eint_workqueue, &accdet_eint_work); //調度後半部accdet_eint_work_callback
    return IRQ_HANDLED;
}

/*
後半部主要內容:
    1.改變插入標識eint_accdet_sync_flag 
    2.pmic初始化
    3.使能或禁止accdet,使檢測四段耳機插入
    4.重開中斷
*/
static void accdet_eint_work_callback(struct work_struct *work)
{
    ACCDET_DEBUG("[Accdet]accdet_eint_work_callback cur_eint_state=%d\n", cur_eint_state);
    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檢測,以便開始檢測是否爲四段耳機
    }
    else {//EINT_PIN_PLUG_OUT
        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);

        disable_accdet();//關閉accdet檢測              
        headset_plug_out();
    }
#ifdef CONFIG_OF
    enable_irq(accdet_irq);//前半部關中斷,後半部處理完後要重開中斷
#else
    mt_eint_unmask(CUST_EINT_ACCDET_NUM);
#endif
    ACCDET_DEBUG("[Accdet]enable_irq  !!!!!!\n");
#endif   
}

四段式耳機插入與拔出:觸發普通中斷 觸發pmic中斷
插入->accdet中斷產生->中斷處理函數前半部accdet_eint_func->後半部accdet_eint_work_callback->檢測到四段觸發pmic中斷->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_accdet_interrupt();
    }//清中斷標識

    if (accdet_status == MIC_BIAS){
        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();//調度工作accdet_work_callback
    while(((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) && 
           (accdet_timeout_ns(cur_time, ACCDET_TIME_OUT)))) {
    }//等待超時,硬件穩定相關的。
    return 1;
}

accdet_workqueue_func();//調度工作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);
}

check_cable_type(); // 狀態機處理

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;

    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) {
                    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);
                 #endif
            }
            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;
                    pmic_pwrap_write(ACCDET_DEBOUNCE3, cust_headset_settings->debounce3*30);//AB=11 debounce=30ms
                }else {
                    ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
                }
                mutex_unlock(&accdet_eint_irq_sync_mutex);               
                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
                    ACCDET_DEBUG("[Accdet] do not send plug out event in plug out\n");
            }
            else
            {
                ACCDET_DEBUG("[Accdet]PLUG_OUT can't change to this state!\n"); 
            }
            break;

        case MIC_BIAS://一般是按鍵觸發進來的,所以這裏主要處理按鍵按下的情況
            pmic_pwrap_write(ACCDET_DEBOUNCE0, cust_headset_settings->debounce0);

            if(current_status == 0)//有按鍵按下
            {
            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: 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);//按鍵檢測
                }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)//拔出耳機
          {
                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
           {
               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);

                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;          
        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;        
        }
        else
        {
            IRQ_CLR_FLAG = FALSE;
        }
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章