硬件和框图
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设置,按键检测相关配置参数),通过全局变量获取。
ret = switch_dev_register(&accdet_data);
注册SWITCH设备。注册字符设备,关注其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; }
注册 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);
初始化 处理线程:
accdet_workqueue = create_singlethread_workqueue("accdet"); INIT_WORK(&accdet_work, accdet_work_callback);
初始化 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");
注册回调
pmic_register_interrupt_callback(12, accdet_int_handler ); pmic_register_interrupt_callback(13, accdet_eint_int_handler );
初始化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; }
耳机插入流程分析:
耳机插入—-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]);
}