First macro view of the kernel exposed to the upper interface:
[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
Mainly by control and many PCM devices, wherein the control interface is controlled by get and put to achieve upper-level interaction with the kernel;
And the PCM interface is mainly to achieve audio data flow, its composition C0 represents No. 0 sound card, the last face of C means that capture p represents the PCM device number after palyback,d.
Why does PCM have so many device numbers? The reason is that the DSP underlying channel is different. Must be on our platform audio speaker Open Pcmc0d14p,audio handset Open
pcmc0d12p. Why is there no device number 0-32? The reason is that some front-end DSI links are defined. NO_PCM = 1 This will not register the 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 have 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,
},
-----------
};
Note In the Snd_soc_dai_link Platform_name, the final PCM traffic is sent to the lowest level by platform_name corresponding platform driver. In the platform drive
The PCM data stream interface is required for:
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,
};
The following focuses on Kcontrol and how to interact with the upper layers:
< a > Direct registration Kcontrol, this type of Kcontrol focus is to implement the value of the change register:
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),
---------
};
Complete registration via 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;
}
< two > register Kcontrol in the widget, which focuses on providing a bridge between the widget and path to complete the switching of the audio 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)
};
The get and put interfaces inside the widget are usually ready-made and do not need to be populated by themselves.
/* 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)}
Through the snd_soc_dapm_new_widgets to complete the registration, will traverse the card inside the widget list (this list is in the previous widget registration with the widget all in),
The new widget is generated only if the Num_kcontrols definition is within the 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;
}
Take the control of the mixer class as an example, except that the name of the control of the mixer class is long_name by the current widget name + kcontrol name. and the Mux class control
Name or 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 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 would 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] = ' + ';
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;
}
< three >kcontrol unified last registered Interface Snd_ctl_add:
Each registration of a kcontrol, Numid will add one, and the upper call Kcontrol is also through the numid to identify.
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;
}
Here is the printed 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
< four > How to operate Kcontrol and kernel receive calls
ALSA mechanism Linux provides a ALSA library, Qualcomm platform in/hardware/qcom/audio/libalsa-intf, and through UCM to manage the kcontrol that need to be sent
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
When playing MP3, the middle-tier logcat log can be seen through numid to the kernel to identify 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 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
Mixer_ctl_select function in Alsa library:
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;
}
Now look at how to call the kernel, search Sndrv_ctl_ioctl_elem_write, the following is the calling process:
<-------Snd_ctl_ioctl
<-------Snd_ctl_elem_write_user
<-------snd_ctl_elem_write//here through snd_ctl_find_id call Snd_ctl_find_numid to find Kctl
<-------kctl->put//here calls the Kcontrol corresponding put
Here Kcontrol is finished, and the following focuses on analyzing the widget route path and how the audio channel switches.
Linux audio alsa Mechanism learning notes < a >