本文基於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檢測就是基於這個原理。
爲了實現錄音,需要在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設置當前狀態。