Android 4.x下基于wm8994的mic检测

        本文基于Android 4.4和4.2,检测所用codec为wm8994。

        Android和Kernel下的mic检测是建立在headset检测的基础上的,具体过程如下:

        1)       kernel通过Jack检测脚中断检测到有耳机插入

        2)       读取codec寄存器判断headset是否带mic

        3)       通过InputEvent/UEvent机制通知Android上层

        详情可以参看我的前一篇基于耳机插拔检测的文章。本文基于UEvent机制来实现,即 switch driver的方式。

1.   mic检测原理

        先看看带mic的耳机和不带mic的耳机的差别,如下图,不带mic的耳机为3段,带mic的耳机为4段,比对一下实物可以看出两者左右声道段没有差别,差别之处是不带mic的耳机将GND和MIC两段合并在一起。因而对于不带mic的耳机来说,GND和MIC两段是几乎短路的(有一定电阻),而mic检测就是基于这个原理。

Android 4.x下基于wm8994的mic检测_第1张图片

Android 4.x下基于wm8994的mic检测_第2张图片

        为了实现录音,需要在MIC段施加一定的偏置电压,即micbias,对于没有mic的耳机来说,由于MIC和GND合成为一段,就相当于将micbias接地,因此会产生比较大的电流。一些codec支持电流检测功能,当电流超过某个阈值时,会将相应的寄存器设置为1,从而可以查询得到结果。

2.   codec设置

        wm8994 codec支持电流检测功能,要使用该功能,需要进行相应设置。

        具体的设置可以参考wm8994_mic_detect函数,但我在Linux 3.4.39中调用这个函数后会导致整个播放无声,因此只能手动设置寄存器,以MICBIAS1为例,需要手动设置寄存器代码如下:


/*enable BIAS, MICBIAS1 and VMID in R01h*/
snd_soc_update_bits(codec,WM8994_POWER_MANAGEMENT_1,
                          WM8994_MICB1_ENA_MASK | WM8994_BIAS_ENA |WM8994_VMID_SEL_MASK,
                          WM8994_MICB1_ENA |WM8994_BIAS_ENA | 1 << WM8994_VMID_SEL_SHIFT);

 

/*enable MICBIAS Current Detect*/
snd_soc_update_bits(codec,WM8994_MICBIAS, WM8994_MICD_ENA, reg);

 

/* enable MICDETdeboune */
snd_soc_update_bits(codec,WM8994_IRQ_DEBOUNCE,
                          WM8994_MIC1_DET_DB_MASK,
                          WM8994_MIC1_DET_DB);
 

/* set MICDETthreshold */
snd_soc_update_bits(codec,WM8994_MICBIAS,
                          WM8994_MICD_THR_MASK,
                          0b100 <<WM8994_MICD_THR_SHIFT);
 

        由于有些寄存器和widget绑定导致dapm会在widget下电时将寄存器disable,因此还需要对codec原有代码做相应修改:

        1) vmid_dereference中,对于MICBIAS和VMID的寄存器不应该设置回0,即取消如下行:

           snd_soc_update_bits(codec,WM8994_POWER_MANAGEMENT_1, WM8994_BIAS_ENA |WM8994_VMID_SEL_MASK, 0);

        2) 对于MICBIAS1对应的widget,将原有的其绑定的寄存器取消,MICBIAS对应widget在wm_hubs.c中,修改如下:

           SND_SOC_DAPM_SUPPLY("MICBIAS1",WM8993_POWER_MANAGEMENT_1, 4, 0, NULL, 0),

           -> SND_SOC_DAPM_SUPPLY("MICBIAS1", SND_SOC_NOPM, 4, 0, NULL, 0),

3.   switch driver中的实现

        switch driver的实现在前一篇文章中提到过,这里基于已经实现的switch driver添加关于mic检测的部分即可。

        前面提到,对于插拔检测的中断处理函数一般处理成delayed work,防止插拔过程中多次中断,那么在delayed work的回调函数中,流程如下:

        1) 读取GPIO电平值,如果为高(低)电平,则耳机插入。具体是高还是低与耳机检测机制有关,大多数为高电平插入。

        2) 如果检测到耳机有插入,那么读取wm8994 codec上的寄存器进行进一步判断,代码如下: 


    mic = wm8994_reg_read(wm8994, WM8994_INTERRUPT_RAW_STATUS_2);
    mic = (mic & WM8994_MIC2_DET_STS_MASK) >> WM8994_MIC2_DET_STS_SHIFT;
    if(mic)
          switch_set_state(&data->sdev, 2);  //no mic
    else
          switch_set_state(&data->sdev, 1);  //with mic


            这里的最大问题是,在switch driver中如何访问wm8994的寄存器,即上面wm8994_reg_read的第一个参数wm8994从哪里获得?这里介绍两种方法:

 

  •   将switch device注册为wm8994-core.c的子设备

        这里有必要先介绍一下wm8994驱动的结构。由于wm8994的控制部分(寄存器设置)走i2c,所以在linux kernel中,wm8994首先作为一个mfd设备挂载在i2c总线下面,即为wm8994 core(对应文件drivers/mfd/wm8994-core.c)。在wm8994 core下面,有若干个子设备(codec,gpio,regulator)实现不同的功能。

        其中codec(对应文件sound/soc/codecs/wm8994.c)就是wm8994 core的一个子设备,这个子设备又注册到了ASOC中。而前面提到的其它子设备(gpio,regulator)也是wm8994 codec硬件的一些其它应用,如可以将codec当作gpio或regulator设备来使用,kernel为这些特殊应用单独实现了driver。

        了解了wm8994的大概结构,我这里借鉴了wm8994-regulator.c和gpio-wm8994.c访问wm8994寄存器的方法,将switch device挂载为wm8994-core.c的一个子设备,实现代码如下:


static struct mfd_cell wm8994_devs[] = {

          {

                   .name= "wm8994-codec",

                   .num_resources= ARRAY_SIZE(wm8994_codec_resources),

                   .resources= wm8994_codec_resources,

          },

 

          {

                   .name= "wm8994-gpio",

                   .num_resources= ARRAY_SIZE(wm8994_gpio_resources),

                   .resources= wm8994_gpio_resources,

                   .pm_runtime_no_callbacks= true,

          },

  
          /* hp detect switch driver*/
          {

                   .name= "xxx-hp-switch",

          },

};
 

          这样,通过wm8994_device_init -> mfd_add_devices中就将switch device添加成了wm8994 core的一个子设备。在switch driver的probe函数中,通过如下语句:

          structwm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);

即可得到wm8994结构,从而调用wm8994_reg_read读取寄存器。

 

  • Ÿ 在wm8994 codecdriver中注册switch device

        这个方法不需要实现单独的switch driver,整个检测过程中集成在codec driver中,相当于将switch driver嵌入在了codec driver中,大致流程如下:

        1)在codec driver(sound/soc/codec/wm8994.c)的probe函数中:

             a)  调用switch_dev_register注册一个switch device

             b)  申请GPIO中断

         2)在中断处理函数中读取GPIO状态和codec寄存器,判断耳机插入和mic是否存在,通过switch_set_state设置当前状态。

你可能感兴趣的:(Android 4.x下基于wm8994的mic检测)