高通平台耳机类型识别

耳机类型

耳机类型一般有两种:3段式耳机和4段式耳机。其中4段式耳机又分为欧标和美标。
3段耳机:线序分别为,L,R,G,没有MIC端,所以三段耳机无法使用麦克风,只能接受声音,另外,三段耳机L,R线序长度正常,G端比较长。

四段 - 美标(CTIA)耳机:线序分别为L,R,G,M,第三阶为GND。

四段 - 欧标(OMTP)耳机:线序分别为L,R,M,G,第四段为GND。

Android的驱动层的耳机检测代码

1、耳机类型检测
\ kernel \ msm-3.18 \ sound \ soc \ codecs \ wcd-mbhc-v2.c当耳机插入会触发中断执行检测代码,在wcd -mbhc-v2.c中有一个重要的检测函数:== static void wcd_correct_swch_plug(struct work_struct * work)==此函数会将检测结果存进变量plug_type中,在通过函数:static void wcd_mbhc_find_plug_and_report(struct wcd_mbhc * mbhc,enum wcd_mbhc_plug_type plug_type)把检测结果上报到系统上层。变量plug_type的数据类型为枚举型

 enum wcd_mbhc_plug_type { 
                           MBHC_PLUG_TYPE_INVALID = -1,//无效设备
                           MBHC_PLUG_TYPE_NONE,//未接入设备
                           MBHC_PLUG_TYPE_HEADSET,//四段耳机
                           MBHC_PLUG_TYPE_HEADPHONE,//三段耳机
                           MBHC_PLUG_TYPE_HIGH_HPH,//高阻抗耳机
                           MBHC_PLUG_TYPE_GND_MIC_SWAP,//欧美标标志位
 }     

在函数static void wcd_correct_swch_plug(struct work_struct * work)中调用了static int wcd_check_cross_conn(struct wcd_mbhc * mbhc)函数,该函数的具体作用还没有看懂,推测是检测欧美标志换的检测函数。函数wcd_correct_swch_plug()中的部分代码

WCD_MBHC_REG_READ(WCD_MBHC_BTN_RESULT,btn_result); 
WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT,hs_comp_res);
 if(!rc)
 {
   pr_debug(“%s No btn press interrupt \ n”,__ func__); 
   if(!btn_result &&!hs_comp_res)
     plug_type = MBHC_PLUG_TYPE_HEADSET;
  else if(!btn_result && hs_comp_res)
    plug_type = MBHC_PLUG_TYPE_HIGH_HPH; 		其他
	plug_type = MBHC_PLUG_TYPE_INVALID; 
} else { 
		if(!btn_result &&!hs_comp_res)
			plug_type = MBHC_PLUG_TYPE_HEADPHONE; 
		else 
			plug_type = MBHC_PLUG_TYPE_INVALID; 
}

可以发现该函数是通过读取两个寄存器中的值来判断耳机类型,那么WCD_MBHC_BTN_RESULT,WCD_MBHC_HS_COMP_RESULT应该就是寄存器的偏移地址。查阅高通平台的数据表可以在对应地址查找到相关的寄存器。那么这些寄存器中的值是哪里来的呢?
2、耳机阻抗计算
\ kernel \ msm-3.18 \ sound \ soc \ codecs \ msm8x16-wcd.c无意中发现msm8x16-wcd.c中的函数:void msm8x16_wcd_mbhc_calc_impedance(struct wcd_mbhc * mbhc,uint32_t * zl,uint32_t * zr)改函数中调用了一个计算耳机左右声道阻抗的函数:


static void msm8x16_wcd_compute_impedance(struct snd_soc_codec *codec, s16 l,
   			s16 r, uint32_t *zl, uint32_t *zr, bool high)
{
   struct msm8x16_wcd_priv *msm8x16_wcd = snd_soc_codec_get_drvdata(codec);
   uint32_t rl = 0, rr = 0;
   struct wcd_imped_i_ref R = msm8x16_wcd->imped_i_ref;
   int codec_ver = get_codec_version(msm8x16_wcd);

   switch (codec_ver) {
   case TOMBAK_1_0:
   case TOMBAK_2_0:
   case CONGA:
   	if (high) {
   		pr_debug("%s: This plug has high range impedance\n",
   			 __func__);
   		rl = (uint32_t)(((100 * (l * 400 - 200))/96) - 230);
   		rr = (uint32_t)(((100 * (r * 400 - 200))/96) - 230);
   	} else {
   		pr_debug("%s: This plug has low range impedance\n",
   			 __func__);
   		rl = (uint32_t)(((1000 * (l * 2 - 1))/1165) - (13/10));
   		rr = (uint32_t)(((1000 * (r * 2 - 1))/1165) - (13/10));
   	}
   	break;
   case CAJON:
   case CAJON_2_0:
   case DIANGU:
   	if (msm8x16_wcd->imped_det_pin == WCD_MBHC_DET_HPHL) {
   		rr = (uint32_t)(((DEFAULT_MULTIPLIER * (10 * r - 5)) -
   		   (DEFAULT_OFFSET * DEFAULT_GAIN))/DEFAULT_GAIN);
   		rl = (uint32_t)(((10000 * (R.multiplier * (10 * l - 5)))
   		      - R.offset * R.gain_adj)/(R.gain_adj * 100));
   	} else if (msm8x16_wcd->imped_det_pin == WCD_MBHC_DET_HPHR) {
   		rr = (uint32_t)(((10000 * (R.multiplier * (10 * r - 5)))
   		      - R.offset * R.gain_adj)/(R.gain_adj * 100));
   		rl = (uint32_t)(((DEFAULT_MULTIPLIER * (10 * l - 5))-
   		   (DEFAULT_OFFSET * DEFAULT_GAIN))/DEFAULT_GAIN);
   	} else if (msm8x16_wcd->imped_det_pin == WCD_MBHC_DET_NONE) {
   		rr = (uint32_t)(((DEFAULT_MULTIPLIER * (10 * r - 5)) -
   		   (DEFAULT_OFFSET * DEFAULT_GAIN))/DEFAULT_GAIN);
   		rl = (uint32_t)(((DEFAULT_MULTIPLIER * (10 * l - 5))-
   		   (DEFAULT_OFFSET * DEFAULT_GAIN))/DEFAULT_GAIN);
   	} else {
   		rr = (uint32_t)(((10000 * (R.multiplier * (10 * r - 5)))
   		      - R.offset * R.gain_adj)/(R.gain_adj * 100));
   		rl = (uint32_t)(((10000 * (R.multiplier * (10 * l - 5)))
   		      - R.offset * R.gain_adj)/(R.gain_adj * 100));
   	}
   	break;
   default:
   	pr_debug("%s: No codec mentioned\n", __func__);
   	break;
   }
   *zl = rl;
   *zr = rr;
}

阻抗值通过指针存放到zl和zr里返回。

3、音频控件函数实现
\kernel\msm-3.18\sound\soc\msm\msm8952.c

static const struct snd_kcontrol_new msm_snd_controls[] = {
	SOC_ENUM_EXT("MI2S_RX Format", msm_snd_enum[0],
			mi2s_rx_bit_format_get, mi2s_rx_bit_format_put),
	SOC_ENUM_EXT("MI2S_TX Channels", msm_snd_enum[1],
			msm_ter_mi2s_tx_ch_get, msm_ter_mi2s_tx_ch_put),
	SOC_ENUM_EXT("MI2S_RX Channels", msm_snd_enum[1],
			msm_pri_mi2s_rx_ch_get, msm_pri_mi2s_rx_ch_put),
	SOC_ENUM_EXT("Loopback MCLK", msm_snd_enum[2],
			loopback_mclk_get, loopback_mclk_put),
	SOC_ENUM_EXT("Internal BTSCO SampleRate", msm_snd_enum[3],
		     msm_btsco_rate_get, msm_btsco_rate_put),
	SOC_ENUM_EXT("PROXY_RX Channels", msm_snd_enum[4],
			msm_proxy_rx_ch_get, msm_proxy_rx_ch_put),
	SOC_ENUM_EXT("VI_FEED_TX Channels", msm_snd_enum[5],
			msm_vi_feed_tx_ch_get, msm_vi_feed_tx_ch_put),
	SOC_ENUM_EXT("MI2S_RX SampleRate", msm_snd_enum[6],
			mi2s_rx_sample_rate_get, mi2s_rx_sample_rate_put),
	SOC_ENUM_EXT("Speaker Ext", msm_snd_enum[7],			
	            ext_spk_amp_get, ext_spk_amp_put),
};

Speaker Ext为外放喇叭,ext_spk_amp_get, ext_spk_amp_put都为函数指针,执行对应Speaker Ext的开关操作。其它则与音频的输出有关。初始化msm_snd_controls[]结构体数组后,使用函数:
snd_soc_add_codec_controls(codec, msm_snd_controls, ARRAY_SIZE(msm_snd_controls));
将音频控件添加到系统

4、音频控件的调用
\kernel\msm-3.18\sound\core\control.c
函数static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg),系统上层应用通过设备节点/dev/snd/controlC0向该函数传递命令,由底层驱动执行对应操作。
跟踪一下代码发现最跳转到static int snd_ctl_elem_write_user(struct snd_ctl_file *file, struct snd_ctl_elem_value __user *_control)函数,在函数中又调用了static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, struct snd_ctl_elem_value *control)函数

static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
			      struct snd_ctl_elem_value *control)
{
	struct snd_kcontrol *kctl;
	struct snd_kcontrol_volatile *vd;
	unsigned int index_offset;
	int result;

	down_read(&card->controls_rwsem);
	kctl = snd_ctl_find_id(card, &control->id);
	if (kctl == NULL) {
		result = -ENOENT;
	} else {
		index_offset = snd_ctl_get_ioff(kctl, &control->id);
		vd = &kctl->vd[index_offset];
		if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) ||
		    kctl->put == NULL ||
		    (file && vd->owner && vd->owner != file)) {
			result = -EPERM;
		} else {
			snd_ctl_build_ioff(&control-> id,kctl,index_offset); 
			result = kctl-> put(kctl,control);			
		} 
		if(result> 0){ 
			struct snd_ctl_elem_id id = control-> id; 
			up_read(&card- > controls_rwsem); 
			snd_ctl_notify(card,SNDRV_CTL_EVENT_MASK_VALUE,&id); 
			return 0; 
		} 
	} 
	up_read(&card- > controls_rwsem); 
	return result; 
} 

通过result = kctl-> put(kctl,control);调用了上面的ext_spk_amp_put(),至此完成上层应用对底层驱动的调用,上层应用可以根据底层上报的耳机类型通过音频控件实现外放喇叭的开关。

5、音频通道开关控制
audio_hw.c文件位于/hardware/qcom/audio/hal,属于hardware层
select_devices函数主要作用根据音频配置文件mixer_paths_mtp.xml,选择要打开的通道。音频配置文件位置:\hardware\qcom\audio\configs\msm8937

int select_devices(struct audio_device *adev, audio_usecase_t uc_id)
{
	...
	usecase = get_usecase_from_list(adev, uc_id);//根据uc_id获取当前的usecase
	...
    out_snd_device = platform_get_output_snd_device(adev->platform,usecase->stream.out->devices);
    in_snd_device = platform_get_input_snd_device(adev->platform,  usecase->stream.out->devices);
    //如果当前是通话的状态下的话,获取输入和输出设备
	...
	if (usecase->type == PCM_PLAYBACK) {
            usecase->devices = usecase->stream.out->devices;
            in_snd_device = SND_DEVICE_NONE;
            out_snd_device = platform_get_output_snd_device(adev->platform,
                                            usecase->stream.out->devices);
     //如果当前是音乐播放的话只选择输出设备
} else if (usecase->type == PCM_CAPTURE) {
            usecase->devices = usecase->stream.in->device;
            out_snd_device = SND_DEVICE_NONE;
            in_snd_device = platform_get_input_snd_device(adev->platform, AUDIO_DEVICE_NONE);
   //如果当时是录音的话只选择输入设备
}
...
if (out_snd_device != SND_DEVICE_NONE) {
...
        enable_snd_device(adev, out_snd_device, false);//打开输出设备通路
}
if (in_snd_device != SND_DEVICE_NONE) {
    ....
    enable_snd_device(adev, in_snd_device, false);//打开输入设备通路
} 
...
usecase->in_snd_device = in_snd_device;
usecase->out_snd_device = out_snd_device;//更新当前的输入和输出设备
 
 
enable_audio_route(adev, usecase, true);//使能audio route
...

enable_snd_device函数中会调用Audio_route.c中的函数

int audio_route_apply_and_update_path(struct audio_route *ar, const char *name)
//跟当前的device_name在 mixer_paths.xml上找到对应的设备通路并打开
{
if (audio_route_apply_path(ar, name) < 0) {
//更新声卡中所有控件的值
        return -1;
    }
return audio_route_update_path(ar, name, false /*reverse*/);
//将更新后声卡控件的值写入到“/dev/snd/controlC0”中
}
int audio_route_apply_path(struct audio_route *ar, const char *name)
{
    struct mixer_path *path;
...
    path = path_get_by_name(ar, name);//根据当前设备名字找到该设备通路
...
    path_apply(ar, path);//根据当前使能通路控件值更新声卡中控件的状态,即mixer_state
 
 
    return 0;
}
 
 
static int path_apply(struct audio_route *ar, struct mixer_path *path)
{
...
    for (i = 0; i < path->length; i++) {
        ctl_index = path->setting[i].ctl_index;//通路控件的id
        ctl = index_to_ctl(ar, ctl_index);
        type = mixer_ctl_get_type(ctl);
        if (!is_supported_ctl_type(type))
            continue;
...
        memcpy(ar->mixer_state[ctl_index].new_value, path->setting[i].value,
               path->setting[i].num_values * sizeof(int));//更新声卡中该id对应的空间的值
    }
 
 
    return 0;
}
 
 
static int audio_route_update_path(struct audio_route *ar, const char *name, bool reverse)
{
...
path = path_get_by_name(ar, name);//找出当前设备通路
...
i = reverse ? (path->length - 1) : 0;
end = reverse ? -1 : (int32_t)path->length;
 
 
while (i != end) {
...
ctl_index = path->setting[i].ctl_index;//找出设备通路中控件id
struct mixer_state * ms = &ar->mixer_state[ctl_index];//根据控件id找到当前控件的值
 ...
 /* if any value has changed, update the mixer */
 for (j = 0; j < ms->num_values; j++) {
    if (ms->old_value[j] != ms->new_value[j]) {
        if (type == MIXER_CTL_TYPE_ENUM)
            mixer_ctl_set_value(ms->ctl, 0, ms->new_value[0]);
//将值写入到底层控件中
        else
            mixer_ctl_set_array(ms->ctl, ms->new_value, ms->num_values);
            memcpy(ms->old_value, ms->new_value, ms->num_values * sizeof(int));
            break;
    }
  }
  i = reverse ? (i - 1) : (i + 1);
}
return 0;
}    
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
{
struct snd_ctl_elem_value ev;
...
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info->id.numid;
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);//读当前所有音频控件的值
...
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);//将新的控件的值写入到/dev/snd/controlC0中去
}

在此处的ioctl函数就对应上面刚开始时control.c中的函数
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
然后在control.c中执行回调函数put和get。

kernel\msm-3.18\sound\soc\msm\qdsp6v2\msm-pcm-routing-v2.h

#define INT_RX_VOL_GAIN 0x2000  //耳机录音音量增益

你可能感兴趣的:(高通平台耳机类型识别)