记得2010年一边接触linux内核源码一边读宋宝华的驱动书籍就接触到了LINUX ALSA,但是看书看的稀里糊涂的具体
的机制根本不清,现在有幸干这行,在高通平台上花点点时间学习了下,后面都是自己学习所得。
首先宏观看内核暴露给上层的接口:
root@android:/# cat /dev/snd/
controlC0pcmC0D10p pcmC0D13c pcmC0D15c pcmC0D2c pcmC0D3c pcmC0D5p pcmC0D8c
pcmC0D0c pcmC0D11p pcmC0D13p pcmC0D15p pcmC0D2p pcmC0D3p pcmC0D6c pcmC0D9c
pcmC0D0p pcmC0D12c pcmC0D14c pcmC0D1c pcmC0D31c pcmC0D4p pcmC0D6p pcmC0D9p
pcmC0D10cpcmC0D12p pcmC0D14p pcmC0D1p pcmC0D32p pcmC0D5c pcmC0D7p timer
主要由control与许多pcm设备组成,其中控制类control接口是通过get与put来实现上层与内核的交互的;而pcm接口主要是实现音频数据流的,其成C0表示0号声卡,最后面的c表示capturep表示palyback,D后面表示pcm设备号。为什么pcm有这么多的设备号?原因是dsp底层通道不一样。必须在我们平台上audio speaker打开pcmC0D14p,audio handset打开pcmC0D12p。为什么设备号0-32有的没有?原因是有的前端dsil inks定义了.no_pcm= 1这样就不会注册pcm。
staticstruct snd_soc_dai_link msm_dai[] = {
/*FrontEnd DAI Links */
{
.name= "MSM8960 Media1",
.stream_name= "MultiMedia1",
.cpu_dai_name ="MultiMedia1",
.platform_name = "msm-pcm-dsp",
.dynamic= 1,
.trigger= {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.codec_dai_name= "snd-soc-dummy-dai",
.codec_name= "snd-soc-dummy",
.ignore_suspend= 1,
.ignore_pmdown_time= 1, /* this dainlink has playback support */
.be_id= MSM_FRONTEND_DAI_MULTIMEDIA1
},
-----------
{
.name= LPASS_BE_SLIMBUS_0_TX,
.stream_name= "Slimbus Capture",
.cpu_dai_name= "msm-dai-q6.16385",
.platform_name= "msm-pcm-routing",
.codec_name = "tabla_codec",
.codec_dai_name ="tabla_tx1",
.no_pcm= 1,
.be_id= MSM_BACKEND_DAI_SLIMBUS_0_TX,
.be_hw_params_fixup= msm_slim_0_tx_be_hw_params_fixup,
.ops= &msm_be_ops,
},
-----------
};
注意在snd_soc_dai_link中的platform_name,最终pcm数据流是通过platform_name对应的平台驱动将数据流发送到
最底层的。在平台驱动需要实现pcm数据流接口:
staticstruct snd_pcm_ops msm_pcm_ops = {
.open = msm_pcm_open,
.copy =msm_pcm_copy,
.hw_params =msm_pcm_hw_params,
.close = msm_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.prepare = msm_pcm_prepare,
.trigger = msm_pcm_trigger,
.pointer = msm_pcm_pointer,
.mmap =msm_pcm_mmap,
};
下面重点说kcontrol以及如何与上层交互:
<一>直接注册kcontrol,这类kcontrol重点是实现更改寄存器的值:
staticconst struct snd_kcontrol_new tabla_snd_controls[] = {
-------------
SOC_ENUM_EXT("EARPA Gain", tabla_ear_pa_gain_enum[0],
tabla_pa_gain_get,tabla_pa_gain_put),
SOC_SINGLE_TLV("LINEOUT1Volume", TABLA_A_RX_LINE_1_GAIN, 0, 12, 1,
line_gain),
--------------
};
staticstruct snd_soc_codec_driver soc_codec_dev_tabla = {
--------
.controls =tabla_snd_controls,
.num_controls =ARRAY_SIZE(tabla_snd_controls),
---------
};
通过snd_soc_add_controls完成注册:
staticint snd_soc_add_controls(struct snd_card *card, struct device *dev,
conststruct snd_kcontrol_new *controls, int num_controls,
constchar *prefix, void *data)
{
interr, i;
for(i = 0; i < num_controls; i++) {
conststruct snd_kcontrol_new *control = &controls[i];
err= snd_ctl_add(card, snd_soc_cnew(control, data,
control->name, prefix));
if(err < 0) {
dev_err(dev,"Failed to add %s: %d\n", control->name, err);
returnerr;
}
}
return0;
}
<二>在widget中注册kcontrol,这类control重点是提供widget与path的桥梁来完成音频通路的切换:
staticconst struct snd_soc_dapm_widget tabla_dapm_widgets[] = {
--------
SND_SOC_DAPM_MIXER("DAC1",SND_SOC_NOPM, 0, 0, dac1_switch,
ARRAY_SIZE(dac1_switch)),
--------
SND_SOC_DAPM_MUX("SLIMRX1 MUX", SND_SOC_NOPM, TABLA_RX1, 0,
&slim_rx_mux[TABLA_RX1]),
}
#defineSND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \
wcontrols,wncontrols)\
{ .id= snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrol_news = wcontrols,.num_kcontrols = wncontrols}
staticconst struct snd_kcontrol_new dac1_switch[] = {
SOC_DAPM_SINGLE("Switch",TABLA_A_RX_EAR_EN, 5, 1, 0)
};
widget里面的get与put接口通常是现成的,不需要自己填充。
/*dapm kcontrol types */
#defineSOC_DAPM_SINGLE(xname, reg, shift, max, invert) \
{ .iface= SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info= snd_soc_info_volsw, \
.get= snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
.private_value= SOC_SINGLE_VALUE(reg, shift, max, invert) }
通过snd_soc_dapm_new_widgets来完成注册的,会遍历card里面的widget链表(这个链表是在之前widget注册时将widget全部放进来的),
只有当widget里面的num_kcontrols定义的情况下才会生成newwidget。
intsnd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
{
structsnd_soc_dapm_widget *w;
unsignedint val;
mutex_lock_nested(&dapm->card->dapm_mutex,SND_SOC_DAPM_CLASS_INIT);
list_for_each_entry(w,&dapm->card->widgets, list)
{
if(w->new)
continue;
if(w->num_kcontrols) {
w->kcontrols= kzalloc(w->num_kcontrols *
sizeof(structsnd_kcontrol *),
GFP_KERNEL);
if(!w->kcontrols) {
mutex_unlock(&dapm->card->dapm_mutex);
return-ENOMEM;
}
}
switch(w->id){
casesnd_soc_dapm_switch:
casesnd_soc_dapm_mixer:
casesnd_soc_dapm_mixer_named_ctl:
dapm_new_mixer(w);
break;
casesnd_soc_dapm_mux:
casesnd_soc_dapm_virt_mux:
casesnd_soc_dapm_value_mux:
dapm_new_mux(w);
break;
casesnd_soc_dapm_pga:
casesnd_soc_dapm_out_drv:
dapm_new_pga(w);
break;
default:
break;
}
/*Read the initial power state from the device */
if(w->reg >= 0) {
val= soc_widget_read(w, w->reg);
val&= 1 << w->shift;
if(w->invert)
val= !val;
if(val)
w->power= 1;
}
w->new= 1;
dapm_mark_dirty(w,"new widget");
dapm_debugfs_add_widget(w);
}
dapm_power_widgets(dapm,SND_SOC_DAPM_STREAM_NOP);
mutex_unlock(&dapm->card->dapm_mutex);
return0;
}
以mixer类的control为例,唯独mixer类的control的name是下面的long_name由当前widgetname + kcontrol name组
成。而mux类control的name还是wigdet的名字。
/*create new dapm mixer control */
staticint dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
structsnd_soc_dapm_context *dapm = w->dapm;
inti, ret = 0;
size_tname_len, prefix_len;
structsnd_soc_dapm_path *path;
structsnd_card *card = dapm->card->snd_card;
constchar *prefix;
structsnd_soc_dapm_widget_list *wlist;
size_twlistsize;
if(dapm->codec)
prefix= dapm->codec->name_prefix;
else
prefix= NULL;
if(prefix)
prefix_len= strlen(prefix) + 1;
else
prefix_len= 0;
/*add kcontrol */
for(i = 0; i < w->num_kcontrols; i++) {
/*match name */
list_for_each_entry(path,&w->sources, list_sink) {
/*mixer/mux paths name must match control name */
if(path->name != (char *)w->kcontrol_news[i].name)
continue;
if(w->kcontrols[i]) {
path->kcontrol= w->kcontrols[i];
continue;
}
wlistsize= sizeof(struct snd_soc_dapm_widget_list) +
sizeof(struct snd_soc_dapm_widget *),
wlist= kzalloc(wlistsize, GFP_KERNEL);
if(wlist == NULL) {
dev_err(dapm->dev,
"asoc:can't allocate widget list for %s\n",
w->name);
return-ENOMEM;
}
wlist->num_widgets= 1;
wlist->widgets[0]= w;
/*add dapm control with long name.
* for dapm_mixer this is the concatenation of the
* mixer and kcontrol name.
* for dapm_mixer_named_ctl this is simply the
* kcontrol name.
*/
name_len= strlen(w->kcontrol_news[i].name) + 1;
if(w->id != snd_soc_dapm_mixer_named_ctl)
name_len+= 1 + strlen(w->name);
path->long_name= kmalloc(name_len, GFP_KERNEL);
if(path->long_name == NULL) {
kfree(wlist);
return-ENOMEM;
}
switch(w->id) {
default:
/*The control will get a prefix from
* the control creation process but
* we're also using the same prefix
* for widgets so cut the prefix off
* the front of the widget name.
*/
snprintf(path->long_name,name_len, "%s %s",
w->name + prefix_len,
w->kcontrol_news[i].name);
break;
casesnd_soc_dapm_mixer_named_ctl:
snprintf(path->long_name,name_len, "%s",
w->kcontrol_news[i].name);
break;
}
path->long_name[name_len- 1] = '\0';
path->kcontrol= snd_soc_cnew(&w->kcontrol_news[i],
wlist, path->long_name,
prefix);
ret= snd_ctl_add(card, path->kcontrol);
if(ret < 0) {
dev_err(dapm->dev,
"asoc:failed to add dapm kcontrol %s: %d\n",
path->long_name,ret);
kfree(wlist);
kfree(path->long_name);
path->long_name= NULL;
returnret;
}
w->kcontrols[i]= path->kcontrol;
}
}
returnret;
}
<三>kcontrol统一最后注册接口snd_ctl_add:
每注册一个kcontrol,numid就会加一,而上层调用kcontrol也是通过numid来识别的。
intsnd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
structsnd_ctl_elem_id id;
unsignedint idx;
interr = -EINVAL;
if(! kcontrol)
returnerr;
if(snd_BUG_ON(!card || !kcontrol->info))
gotoerror;
id= kcontrol->id;
down_write(&card->controls_rwsem);
if(snd_ctl_find_id(card, &id)) {
up_write(&card->controls_rwsem);
snd_printd(KERN_ERR"control %i:%i:%i:%s:%i is already present\n",
id.iface,
id.device,
id.subdevice,
id.name,
id.index);
err= -EBUSY;
gotoerror;
}
if(snd_ctl_find_hole(card, kcontrol->count) < 0) {
up_write(&card->controls_rwsem);
err= -ENOMEM;
gotoerror;
}
list_add_tail(&kcontrol->list,&card->controls);
card->controls_count+= kcontrol->count;
kcontrol->id.numid= card->last_numid + 1;
printk(KERN_ERR"%s kcontrol numid=%d name=%s is already present\n",
__func__,
kcontrol->id.numid,
kcontrol->id.name);
card->last_numid+= kcontrol->count;
up_write(&card->controls_rwsem);
for(idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
snd_ctl_notify(card,SNDRV_CTL_EVENT_MASK_ADD, &id);
return0;
error:
snd_ctl_free_one(kcontrol);
returnerr;
}
下面是打印的log:
<3>[ 2.187774] snd_ctl_add kcontrol numid=1 name=Voice Rx Device Muteis already present
<3>[ 2.187835] snd_ctl_add kcontrol numid=2 name=Voice Tx Mute isalready present
<3>[ 2.187927] snd_ctl_add kcontrol numid=3 name=Voice Rx Volume isalready present
<3>[ 2.187988] snd_ctl_add kcontrol numid=4 name=TTY Mode is alreadypresent
<3>[ 2.188049] snd_ctl_add kcontrol numid=5 name=Widevoice Enable isalready present
-----
<3>[ 2.267761] snd_ctl_add kcontrol numid=128 name=VoLTE Stub Tx MixerSLIM_1_TX is already present
<3>[ 2.267883] snd_ctl_add kcontrol numid=129 name=VoLTE Stub Tx MixerSTUB_1_TX_HL is already present
<3>[ 2.268035] snd_ctl_add kcontrol numid=130 name=VoLTE Stub Tx MixerMI2S_TX is already present
<3>[ 2.268157] snd_ctl_add kcontrol numid=131 name=VoLTE Stub Tx MixerSLIM_3_TX is already present
--------
<3>[ 2.375000] snd_ctl_add kcontrol numid=462 name=HDMI RX Format isalready present
<四>上层如何操作kcontrol以及内核接收调用
alsa机制linux提供了alsa库,高通平台在/hardware/qcom/audio/libalsa-intf里面,同时通过ucm来管理需要发送的kcontrolsequence。
Name"Speaker"
Comment"Speaker Rx device"
EnableSequence
'SLIM_0_RXChannels':0:Two
'RX3MIX1 INP1':0:RX1
'RX3MIX1 INP2':0:RX6
'RX5MIX1 INP1':0:RX2
'RX5MIX1 INP2':0:RX7
'RX4DSM MUX':0:DSM_INV
'RX6DSM MUX':0:DSM_INV
'LINEOUT1Volume':1:72
'LINEOUT2Volume':1:72
'LINEOUT3Volume':1:72
'LINEOUT4Volume':1:72
------
EndSequence
播放mp3时中间层logcatlog,可以看见通过numid传到内核来识别kcontrol:
D/alsa_ucm( 263): set_controls_of_device_for_all_usecases: Speaker
D/alsa_ucm( 263): print_list: head 0x0
D/alsa_ucm( 263): Empty list
D/alsa_ucm( 263): Empty list
D/alsa_ucm( 263): snd_use_case_set(): uc_mgr 0x41b09d38 identifier _verb valueHiFi Lowlatency
D/alsa_ucm( 263): No switch device/modifier option found: _verb
D/alsa_ucm( 263): Index:17 Verb:HiFi Lowlatency
D/alsa_ucm( 263): set_use_case_ident_for_all_devices(): HiFi Lowlatency
D/alsa_ucm( 263): getUseCaseType: use case is HiFi Lowlatency
D/alsa_ucm( 263): Applying mixer controls for device: Speaker
D/alsa_ucm( 263): Set mixer controls for Speaker enable 1
D/alsa_ucm( 263): Empty list
D/alsa_ucm( 263): No voice use case found
D/alsa_ucm( 263): acdb_id 15 cap 1 enable 1
D/alsa_ucm( 263): Setting mixer control: SLIM_0_RX Channels, value: Two
D/alsa_mixer( 263): mixer_ctl_select numid =464 item =1
D/alsa_ucm( 263): Setting mixer control: RX3 MIX1 INP1, value: RX1
D/alsa_mixer( 263): mixer_ctl_select numid =401 item =5
D/alsa_ucm( 263): Setting mixer control: RX3 MIX1 INP2, value: RX6
D/alsa_mixer( 263): mixer_ctl_select numid =400 item =10
D/alsa_ucm( 263): Setting mixer control: RX5 MIX1 INP1, value: RX2
D/alsa_mixer( 263): mixer_ctl_select numid =397 item =6
D/alsa_ucm( 263): Setting mixer control: RX5 MIX1 INP2, value: RX7
D/alsa_mixer( 263): mixer_ctl_select numid =396 item =11
D/alsa_ucm( 263): Setting mixer control: RX4 DSM MUX, value: DSM_INV
D/alsa_mixer( 263): mixer_ctl_select numid =408 item =1
D/alsa_ucm( 263): Setting mixer control: RX6 DSM MUX, value: DSM_INV
D/alsa_mixer( 263): mixer_ctl_select numid =407 item =1
D/alsa_ucm( 263): Setting mixer control: LINEOUT1 Volume, value: 72
alsa库中mixer_ctl_select函数:
intmixer_ctl_select(struct mixer_ctl *ctl, const char *value)
{
unsigned n, max;
struct snd_ctl_elem_value ev;
unsigned int input_str_len,str_len;
if (ctl->info->type !=SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
errno = EINVAL;
return -1;
}
input_str_len = strnlen(value,64);
max =ctl->info->value.enumerated.items;
for (n = 0; n < max; n++) {
str_len =strnlen(ctl->ename[n], 64);
if (str_len
str_len = input_str_len;
if (!strncmp(value,ctl->ename[n], str_len)) {
memset(&ev, 0,sizeof(ev));
ev.value.enumerated.item[0]= n;
ev.id.numid =ctl->info->id.numid;
ALOGD("mixer_ctl_select numid =%d item =%d\n",ctl->info->id.numid,n);
if(ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev) <0)
return -1;
return 0;
}
}
errno = EINVAL;
return errno;
}
下面再看看如何调用内核的,搜索SNDRV_CTL_IOCTL_ELEM_WRITE,以下是调用流程:
<-------snd_ctl_ioctl
<-------snd_ctl_elem_write_user
<-------snd_ctl_elem_write//在这里通过snd_ctl_find_id调用snd_ctl_find_numid来找到kctl
<-------kctl->put//在这里调用kcontrol对应的put
到这里kcontrol讲完,后面重点分析widgetroute path以及DAPM动态电源管理。