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;
        }
} 
  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值