標籤:linux alsa dapm
首先宏觀看核心暴露給上層的介面:
[email protected]:/ # cat /dev/snd/
controlC0 pcmC0D10p pcmC0D13c pcmC0D15c pcmC0D2c pcmC0D3c pcmC0D5p pcmC0D8c
pcmC0D0c pcmC0D11p pcmC0D13p pcmC0D15p pcmC0D2p pcmC0D3p pcmC0D6c pcmC0D9c
pcmC0D0p pcmC0D12c pcmC0D14c pcmC0D1c pcmC0D31c pcmC0D4p pcmC0D6p pcmC0D9p
pcmC0D10c pcmC0D12p pcmC0D14p pcmC0D1p pcmC0D32p pcmC0D5c pcmC0D7p timer
主要由control與許多pcm裝置群組成,其中控制類control介面是通過get與put來實現上層與核心的互動的;
而pcm介面主要是實現音頻資料流的,其組成C0表示0號音效卡,最後面的c表示capture p表示palyback,D後面表示pcm裝置號。
為什麼pcm有這麼多的裝置號?原因是dsp底層通道不一樣。必須在我們平台上audio speaker 開啟 pcmC0D14p,audio handset 開啟
pcmC0D12p。為什麼裝置號0-32有的沒有?原因是有的前端dsi links定義了.no_pcm = 1這樣就不會註冊pcm。
static struct 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資料流介面:
static struct 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重點是實現更改寄存器的值:
static const struct snd_kcontrol_new tabla_snd_controls[] = {
-------------
SOC_ENUM_EXT("EAR PA Gain", tabla_ear_pa_gain_enum[0],
tabla_pa_gain_get, tabla_pa_gain_put),
SOC_SINGLE_TLV("LINEOUT1 Volume", TABLA_A_RX_LINE_1_GAIN, 0, 12, 1,
line_gain),
--------------
};
static struct snd_soc_codec_driver soc_codec_dev_tabla = {
--------
.controls = tabla_snd_controls,
.num_controls = ARRAY_SIZE(tabla_snd_controls),
---------
};
通過 snd_soc_add_controls完成註冊:
static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
const struct snd_kcontrol_new *controls, int num_controls,
const char *prefix, void *data)
{
int err, i;
for (i = 0; i < num_controls; i++) {
const struct 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);
return err;
}
}
return 0;
}
<二>在widget中註冊kcontrol,這類control重點是提供widget與path的橋樑來完成音頻通路的切換:
static const 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("SLIM RX1 MUX", SND_SOC_NOPM, TABLA_RX1, 0,
&slim_rx_mux[TABLA_RX1]),
}
#define SND_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}
static const struct snd_kcontrol_new dac1_switch[] = {
SOC_DAPM_SINGLE("Switch", TABLA_A_RX_EAR_EN, 5, 1, 0)
};
widget裡面的get與put介面通常是現成的,不需要自己填充。
/* dapm kcontrol types */
#define SOC_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定義的情況下才會產生new widget。
int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)
{
struct snd_soc_dapm_widget *w;
unsigned int 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(struct snd_kcontrol *),
GFP_KERNEL);
if (!w->kcontrols) {
mutex_unlock(&dapm->card->dapm_mutex);
return -ENOMEM;
}
}
switch(w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
dapm_new_mixer(w);
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_virt_mux:
case snd_soc_dapm_value_mux:
dapm_new_mux(w);
break;
case snd_soc_dapm_pga:
case snd_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);
return 0;
}
以 mixer類的control為例,唯獨mixer類的control的name是下面的long_name 由當前widget name + kcontrol name組成。而mux類control
的name還是wigdet的名字。
/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_context *dapm = w->dapm;
int i, ret = 0;
size_t name_len, prefix_len;
struct snd_soc_dapm_path *path;
struct snd_card *card = dapm->card->snd_card;
const char *prefix;
struct snd_soc_dapm_widget_list *wlist;
size_t wlistsize;
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;
case snd_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;
return ret;
}
w->kcontrols[i] = path->kcontrol;
}
}r
eturn ret;
}
<三>kcontrol統一最後註冊介面snd_ctl_add:
每註冊一個kcontrol, numid就會加一,而上層調用kcontrol也是通過 numid來識別的。
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
struct snd_ctl_elem_id id;
unsigned int idx;
int err = -EINVAL;
if (! kcontrol)
return err;
if (snd_BUG_ON(!card || !kcontrol->info))
goto error;
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;
goto error;
} if (
snd_ctl_find_hole(card, kcontrol->count) <
0) {
up_write(&card->controls_rwsem);
err = -ENOMEM;
goto error;
} 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);
return 0;
error:
snd_ctl_free_one(kcontrol);
return err;
}
下面是列印的log:
<3>[ 2.187774] snd_ctl_add kcontrol numid=1 name=Voice Rx Device Mute is already present
<3>[ 2.187835] snd_ctl_add kcontrol numid=2 name=Voice Tx Mute is already present
<3>[ 2.187927] snd_ctl_add kcontrol numid=3 name=Voice Rx Volume is already present
<3>[ 2.187988] snd_ctl_add kcontrol numid=4 name=TTY Mode is already present
<3>[ 2.188049] snd_ctl_add kcontrol numid=5 name=Widevoice Enable is already present
-----
<3>[ 2.267761] snd_ctl_add kcontrol numid=128 name=VoLTE Stub Tx Mixer SLIM_1_TX is already present
<3>[ 2.267883] snd_ctl_add kcontrol numid=129 name=VoLTE Stub Tx Mixer STUB_1_TX_HL is already present
<3>[ 2.268035] snd_ctl_add kcontrol numid=130 name=VoLTE Stub Tx Mixer MI2S_TX is already present
<3>[ 2.268157] snd_ctl_add kcontrol numid=131 name=VoLTE Stub Tx Mixer SLIM_3_TX is already present
--------
<3>[ 2.375000] snd_ctl_add kcontrol numid=462 name=HDMI RX Format is already present
<四>上層如何操作kcontrol以及核心接收調用
alsa機制linux提供了alsa庫,高通平台在/hardware/qcom/audio/libalsa-intf裡面,同時通過ucm來管理需要發送的kcontrol
sequence。
Name "Speaker"
Comment "Speaker Rx device"
EnableSequence
‘SLIM_0_RX Channels‘:0:Two
‘RX3 MIX1 INP1‘:0:RX1
‘RX3 MIX1 INP2‘:0:RX6
‘RX5 MIX1 INP1‘:0:RX2
‘RX5 MIX1 INP2‘:0:RX7
‘RX4 DSM MUX‘:0:DSM_INV
‘RX6 DSM MUX‘:0:DSM_INV
‘LINEOUT1 Volume‘:1:72
‘LINEOUT2 Volume‘:1:72
‘LINEOUT3 Volume‘:1:72
‘LINEOUT4 Volume‘:1:72
------
EndSequence
播放mp3 時 中介層logcat log,可以看見通過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 value HiFi 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函數:
int mixer_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 < input_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講完,後面重點分析widget route path以及音頻通道如何切換。
Linux音頻ALSA機制學習筆記<一>