转载:https://blog.csdn.net/u010481276/article/details/81122226
音频调试工具:audacity ,cool edit, gold wav Sonic Visualiser
http://www.360doc.com/content/18/0314/12/32862269_736896674.shtml ./hardware/qcom/audio/configs/msm8909/mixer_paths_msm8909_pm8916.xml中查看 ,这个配置文件是根据平台声卡名得来的
hardware/qcom/audio/hal/msm8916/platform.c
msm8909:/dev/snd # cat /proc/asound/cards 0 [msm8909pm8916sn]: msm8909-pm8916- - msm8909-pm8916-snd-card msm8909-pm8916-snd-card
} else if (!strncmp(snd_card_name, "msm8909-pm8916-snd-card", sizeof("msm8909-pm8916-snd-card"))) { strlcpy(mixer_xml_path, MIXER_XML_PATH_MSM8909_PM8916, sizeof(MIXER_XML_PATH_MSM8909_PM8916));
msm_device_to_be_id = msm_device_to_be_id_internal_codec; msm_be_id_array_len = sizeof(msm_device_to_be_id_internal_codec) / sizeof(msm_device_to_be_id_internal_codec[0]);
}
msm8909:/dev/snd # cat /proc/asound/cards 0 [msm8909pm8916sn]: msm8909-pm8916- - msm8909-pm8916-snd-card msm8909-pm8916-snd-card
xml包含codec切换path的配置,如下面配有切换至speaker所需要的参数设置(通过tinymix进行配置)
驱动代码放在如下位置, 里面包含audio path切换的"audio_map"表格,
本平台为msm8909 注意,mixer_paths_msm8909_pm8916.xml中如下,只需要一个空格,否则无效
//headset <path name="handset"> <ctl name="RX1 MIX1 INP1" value="RX1" /> <ctl name="RDAC2 MUX" value="RX1" /> <ctl name="RX1 Digital Volume" value="84" /> <ctl name="EAR PA Gain" value="POS_6_DB" /> <ctl name="EAR_S" value="Switch" /> </path>
tinymix “PRI_MI2S_RX Audio Mixer MultiMedia1” “1” tinymix “RX1 MIX1 INP1” “RX1” tinymix “RDAC2 MUX” “RX1” tinymix “RX1 Digital Volume” “84” tinymix “EAR PA Gain” “POS_6_DB” tinymix “EAR_S” “Switch” tinyplay xxx.wav
tinymix ‘MI2S_RX Format’ ‘1’ 设置成24bit,i2s传输,设备也需要切到对应的i2s格式
//headphones <path name="headphones"> <ctl name="MI2S_RX Channels" value="Two" /> <ctl name="RX1 MIX1 INP1" value="RX1" /> <ctl name="RX2 MIX1 INP1" value="RX2" /> <ctl name="RDAC2 MUX" value="RX2" /> <ctl name="HPHL" value="Switch" /> <ctl name="HPHR" value="Switch" /> </path>
tinymix “PRI_MI2S_RX Audio Mixer MultiMedia1” “1”
SND_SOC_DAPM_AIF_OUT("PRI_MI2S_RX", "Primary MI2S Playback", 0, 0, 0, 0),snd_soc_dapm_aif_out 实际为snd_soc_dapm_aif_out对应一个数字音频输出接口,比如I2S接口的输出端。如一组i2s的输出端
msm8909:/ # tinymix |grep "PRI_MI2S_RX Audio Mixer MultiMedia1" 1460 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia1 Off 1468 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia10 Off 1469 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia11 Off 1470 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia12 Off 1471 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia13 Off 1472 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia14 Off 1473 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia15 Off 1474 BOOL 1 PRI_MI2S_RX Audio Mixer MultiMedia16 Off
tinymix “MI2S_RX Channels” “Two” tinymix “RX1 MIX1 INP1” “RX1” tinymix “RX2 MIX1 INP1” “RX2” tinymix “RDAC2 MUX” “RX2” tinymix “HPHL” “Switch” tinymix “HPHR” “Switch”
tinymix “RX2 Digital Volume” “84”
//mic adc1 如上面mic为adc1那路,那只找 adc1即可 <path name="adc1"> <ctl name="ADC1 Volume" value="6" /> <ctl name="DEC1 MUX" value="ADC1" /> </path>
tinymix “MultiMedia1 Mixer TERT_MI2S_TX” 1 tinymix “ADC1 Volume” 6 tinymix “DEC1 MUX” “ADC1” tinycap /data/mic1.wav
//speaker <path name="speaker"> <ctl name="RX3 MIX1 INP1" value="RX1" /> <ctl name="SPK" value="Switch" /> </path>
tinymix ‘PRI_MI2S_RX Audio Mixer MultiMedia1’ 1 tinymix ‘RX3 MIX1 INP1’ ‘RX1’ tinymix “RX1 Digital Volume” “84” tinymix “RX1 Digital Volume” “60” tinymix “SPK” “Switch” tinyplay /data/01_1KHz_0dB.wav
以上的控制功能都是由kcontrol提供的如pm8916上的kcontrol
static const struct snd_kcontrol_new msm8x16_wcd_snd_controls[] = {
SOC_ENUM_EXT("RX HPH Mode", msm8x16_wcd_hph_mode_ctl_enum[0], msm8x16_wcd_hph_mode_get, msm8x16_wcd_hph_mode_set),
SOC_ENUM_EXT("Boost Option", msm8x16_wcd_boost_option_ctl_enum[0], msm8x16_wcd_boost_option_get, msm8x16_wcd_boost_option_set),
SOC_ENUM_EXT("EAR PA Boost", msm8x16_wcd_ear_pa_boost_ctl_enum[0], msm8x16_wcd_ear_pa_boost_get, msm8x16_wcd_ear_pa_boost_set),
SOC_ENUM_EXT("EAR PA Gain", msm8x16_wcd_ear_pa_gain_enum[0], msm8x16_wcd_pa_gain_get, msm8x16_wcd_pa_gain_put),
SOC_ENUM_EXT("Speaker Boost", msm8x16_wcd_spk_boost_ctl_enum[0], msm8x16_wcd_spk_boost_get, msm8x16_wcd_spk_boost_set),
SOC_ENUM_EXT("Ext Spk Boost", msm8x16_wcd_ext_spk_boost_ctl_enum[0], msm8x16_wcd_ext_spk_boost_get, msm8x16_wcd_ext_spk_boost_set),
SOC_ENUM_EXT("LOOPBACK Mode", msm8x16_wcd_loopback_mode_ctl_enum[0], msm8x16_wcd_loopback_mode_get, msm8x16_wcd_loopback_mode_put),
SOC_SINGLE_TLV("ADC1 Volume", MSM8X16_WCD_A_ANALOG_TX_1_EN, 3, 8, 0, analog_gain), SOC_SINGLE_TLV("ADC2 Volume", MSM8X16_WCD_A_ANALOG_TX_2_EN, 3, 8, 0, analog_gain), SOC_SINGLE_TLV("ADC3 Volume", MSM8X16_WCD_A_ANALOG_TX_3_EN, 3, 8, 0, analog_gain),
SOC_ENUM_EXT("EAR PA Gain", msm8x16_wcd_ear_pa_gain_enum[0], msm8x16_wcd_pa_gain_get, msm8x16_wcd_pa_gain_put),
如ear的增益 "EAR PA Gain"是暴露给tinymix的属性名字 msm8x16_wcd_ear_pa_gain_enum[0]是对应可供选择的值 “POS_1P5_DB”, “POS_6_DB” msm8x16_wcd_pa_gain_get, msm8x16_wcd_pa_gain_put是对应的操作函数
static const char * const msm8x16_wcd_ear_pa_gain_text[] = { "POS_1P5_DB", "POS_6_DB"}; static const struct soc_enum msm8x16_wcd_ear_pa_gain_enum[] = { SOC_ENUM_SINGLE_EXT(2, msm8x16_wcd_ear_pa_gain_text), };
对应的操作接口set
static int msm8x16_wcd_pa_gain_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { u8 ear_pa_gain; struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
dev_dbg(codec->dev, "%s: ucontrol->value.integer.value[0] = %ld\n", __func__, ucontrol->value.integer.value[0]);
switch (ucontrol->value.integer.value[0]) { case 0: //对应的枚举值 ear_pa_gain = 0x00; break; case 1: ear_pa_gain = 0x20; break; default: return -EINVAL; }
snd_soc_update_bits(codec, MSM8X16_WCD_A_ANALOG_RX_EAR_CTL, 0x20, ear_pa_gain); return 0; }
ls -al /dev/snd/下面的文件 音频设备的命名规则为 [device type]C[card index]D[device index][capture/playback],即名字中含有4部分的信息: device type 设备类型,通常只有compr/hw/pcm这3种。从上图可以看到声卡会管理很多设备,PCM设备只是其中的一种设备。 card index 声卡的id,代表第几块声卡。通常都是0,代表第一块声卡。手机上通常都只有一块声卡。 device index 设备的id,代表这个设备是声卡上的第几个设备。设备的ID只和驱动中配置的DAI link的次序有关。如果驱动没有改变,那么这些ID就是固定的。
msm8909:/dev/snd # cat /sys/kernel/debug/asoc/dais cajon_vifeedback msm8x16_wcd_i2s_tx1 msm8x16_wcd_i2s_rx1 tas5751-i2s MultiMedia29 MultiMedia28 MultiMedia21 MultiMedia20 MultiMedia19 MultiMedia18 MultiMedia17 VoiceMMode2 VoiceMMode1 MultiMedia16 MultiMedia15 MultiMedia14 MultiMedia13 MultiMedia12 MultiMedia11 MultiMedia10 VoWLAN LSM8 LSM7 LSM6 LSM5 LSM4 LSM3 LSM2 LSM1
前面已经创建了control设备,现在soc_probe_link_dais调用soc_new_pcm创建pcm设备。 1)设置pcm native中要使用的pcm操作函数,这些函数用于操作音频物理设备,包括machine、codec_dai、cpu_dai、platform; 2)调用snd_pcm_new()创建pcm设备,回放子流实例和录制子流实例都在这里创建; 3)回调platform驱动的pcm_new(),完成音频dma设备初始化和dma buffer内存分配;
soc-core.c soc_probe platform_get_drvdata(pdev)//获取声卡 snd_soc_register_card snd_soc_instantiate_card soc_probe_link_dais soc-pcm.c soc_new_pcm
capture/playback 只有PCM设备才有这部分,只有c和p两种。c代表capture,说明这是一个提供录音的设备,p代表palyback,说明这是一个提供播放的设备。 系统会在/proc/asound/pcm文件中列出所有的音频设备的信息,如果是肉眼查看,/proc/asound/pcm中的信息会更直观一些
调用流程:
tinyplay调用流程 从pcm的操作函数开始
const struct file_operations snd_pcm_f_ops[2] = { { .owner = THIS_MODULE, .write = snd_pcm_write, .aio_write = snd_pcm_aio_write, .open = snd_pcm_playback_open, .release = snd_pcm_release, .llseek = no_llseek, .poll = snd_pcm_playback_poll, .unlocked_ioctl = snd_pcm_playback_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = snd_pcm_get_unmapped_area, }, { .owner = THIS_MODULE, .read = snd_pcm_read, .aio_read = snd_pcm_aio_read, .open = snd_pcm_capture_open, .release = snd_pcm_release, .llseek = no_llseek, .poll = snd_pcm_capture_poll, .unlocked_ioctl = snd_pcm_capture_ioctl, .compat_ioctl = snd_pcm_ioctl_compat, .mmap = snd_pcm_mmap, .fasync = snd_pcm_fasync, .get_unmapped_area = snd_pcm_get_unmapped_area, } };
snd_pcm_playback_open snd_pcm_open snd_pcm_open_file snd_pcm_open_substream-》substream->ops->open在
soc_new_pcm中注册回调的 /* ASoC PCM operations */ if (rtd->dai_link->dynamic) { rtd->ops.open = dpcm_fe_dai_open; rtd->ops.hw_params = dpcm_fe_dai_hw_params; rtd->ops.prepare = dpcm_fe_dai_prepare; rtd->ops.trigger = dpcm_fe_dai_trigger; rtd->ops.hw_free = dpcm_fe_dai_hw_free; rtd->ops.close = dpcm_fe_dai_close; rtd->ops.pointer = soc_pcm_pointer; rtd->ops.delay_blk = soc_pcm_delay_blk; rtd->ops.ioctl = soc_pcm_ioctl; rtd->ops.compat_ioctl = soc_pcm_compat_ioctl; } else { rtd->ops.open = soc_pcm_open; rtd->ops.hw_params = soc_pcm_hw_params; rtd->ops.prepare = soc_pcm_prepare; rtd->ops.trigger = soc_pcm_trigger; rtd->ops.hw_free = soc_pcm_hw_free; rtd->ops.close = soc_pcm_close; rtd->ops.pointer = soc_pcm_pointer; rtd->ops.delay_blk = soc_pcm_delay_blk; rtd->ops.ioctl = soc_pcm_ioctl; rtd->ops.compat_ioctl = soc_pcm_compat_ioctl; }
if (platform->driver->ops) { rtd->ops.ack = platform->driver->ops->ack; rtd->ops.copy = platform->driver->ops->copy; rtd->ops.silence = platform->driver->ops->silence; rtd->ops.page = platform->driver->ops->page; rtd->ops.mmap = platform->driver->ops->mmap; rtd->ops.restart = platform->driver->ops->restart; }
soc_pcm_open 1 /* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls * startup for the cpu DAI, platform, machine and codec DAI. */ static int soc_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dai *codec_dai; const char *codec_dai_name = "multicodec"; int i, ret = 0;
pinctrl_pm_select_default_state(cpu_dai->dev); for (i = 0; i < rtd->num_codecs; i++) pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); pm_runtime_get_sync(cpu_dai->dev); for (i = 0; i < rtd->num_codecs; i++) pm_runtime_get_sync(rtd->codec_dais[i]->dev); pm_runtime_get_sync(platform->dev);
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) snd_soc_set_runtime_hwparams(substream, &no_host_hardware);
/* startup the audio subsystem */操作cpu dai的startup接口 if (cpu_dai->driver->ops && cpu_dai->driver->ops->startup) { ret = cpu_dai->driver->ops->startup(substream, cpu_dai); if (ret < 0) { dev_err(cpu_dai->dev, "ASoC: can't open interface" " %s: %d\n", cpu_dai->name, ret); goto out; } } 操作platform的open接口 if (platform->driver->ops && platform->driver->ops->open) { ret = platform->driver->ops->open(substream); if (ret < 0) { dev_err(platform->dev, "ASoC: can't open platform" " %s: %d\n", platform->component.name, ret); goto platform_err; } } 操作codec dai的startup接口 for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->ops && codec_dai->driver->ops->startup) { ret = codec_dai->driver->ops->startup(substream, codec_dai); if (ret < 0) { dev_err(codec_dai->dev, "ASoC: can't open codec %s: %d\n", codec_dai->name, ret); goto codec_dai_err; } }
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) codec_dai->tx_mask = 0; else codec_dai->rx_mask = 0; } 操作 dai link的startup接口 if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { ret = rtd->dai_link->ops->startup(substream); if (ret < 0) { pr_err("ASoC: %s startup failed: %d\n", rtd->dai_link->name, ret); goto machine_err; } }
/* Dynamic PCM DAI links compat checks use dynamic capabilities */ if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) goto dynamic;
/* Check that the codec and cpu DAIs are compatible */ soc_pcm_init_runtime_hw(substream);
if (rtd->num_codecs == 1) codec_dai_name = rtd->codec_dai->name;
if (soc_pcm_has_symmetry(substream)) runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
ret = -EINVAL; if (!runtime->hw.rates) { printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n", codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.formats) { printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n", codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max || runtime->hw.channels_min > runtime->hw.channels_max) { printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n", codec_dai_name, cpu_dai->name); goto config_err; }
soc_pcm_apply_msb(substream);
/* Symmetry only applies if we've already got an active stream. */ if (cpu_dai->active) { ret = soc_pcm_apply_symmetry(substream, cpu_dai); if (ret != 0) goto config_err; }
for (i = 0; i < rtd->num_codecs; i++) { if (rtd->codec_dais[i]->active) { ret = soc_pcm_apply_symmetry(substream, rtd->codec_dais[i]); if (ret != 0) goto config_err; } }
pr_debug("ASoC: %s <-> %s info:\n", codec_dai_name, cpu_dai->name); pr_debug("ASoC: rate mask 0x%x\n", runtime->hw.rates); pr_debug("ASoC: min ch %d max ch %d\n", runtime->hw.channels_min, runtime->hw.channels_max); pr_debug("ASoC: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max);
dynamic:
snd_soc_runtime_activate(rtd, substream->stream);
mutex_unlock(&rtd->pcm_mutex); return 0;
config_err://出错处理,shutdown dai link if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) rtd->dai_link->ops->shutdown(substream);
machine_err: i = rtd->num_codecs;
codec_dai_err: while (--i >= 0) {//出错处理,shutdown codec dai codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->ops->shutdown) codec_dai->driver->ops->shutdown(substream, codec_dai); } //出错处理,close platform if (platform->driver->ops && platform->driver->ops->close) platform->driver->ops->close(substream);
platform_err://出错处理,shutdown cpu dai if (cpu_dai->driver->ops && cpu_dai->driver->ops->shutdown) cpu_dai->driver->ops->shutdown(substream, cpu_dai); out: mutex_unlock(&rtd->pcm_mutex);
pm_runtime_put(platform->dev); for (i = 0; i < rtd->num_codecs; i++) pm_runtime_put(rtd->codec_dais[i]->dev); pm_runtime_put(cpu_dai->dev); for (i = 0; i < rtd->num_codecs; i++) { if (!rtd->codec_dais[i]->active) pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev);
return ret; }
.name = LPASS_BE_QUAT_MI2S_TX, .stream_name = "Quaternary MI2S Capture", .cpu_dai_name = "msm-dai-q6-mi2s.3", .platform_name = "msm-pcm-routing", .codec_dai_name = "snd-soc-dummy-dai", .codec_name = "snd-soc-dummy", .no_pcm = 1, .dpcm_capture = 1, .be_id = MSM_BACKEND_DAI_QUATERNARY_MI2S_TX, .be_hw_params_fixup = msm_be_hw_params_fixup, .ops = &msm8952_quat_mi2s_be_ops, .ignore_suspend = 1,
cpu_dai_name = "msm-dai-q6-mi2s.3",
{ .playback = { .stream_name = "Quaternary MI2S Playback", .aif_name = "QUAT_MI2S_RX", .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rate_min = 8000, .rate_max = 192000, }, .capture = { .stream_name = "Quaternary MI2S Capture", .aif_name = "QUAT_MI2S_TX", .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rate_min = 8000, .rate_max = 48000, }, .ops = &msm_dai_q6_mi2s_ops, .id = MSM_QUAT_MI2S, .probe = msm_dai_q6_dai_mi2s_probe, .remove = msm_dai_q6_dai_mi2s_remove, } static struct snd_soc_dai_ops msm_dai_q6_mi2s_ops = { .startup = msm_dai_q6_mi2s_startup, .prepare = msm_dai_q6_mi2s_prepare, .hw_params = msm_dai_q6_mi2s_hw_params, .hw_free = msm_dai_q6_mi2s_hw_free, .set_fmt = msm_dai_q6_mi2s_set_fmt, .shutdown = msm_dai_q6_mi2s_shutdown, };
msm-pcm-routing static struct snd_soc_platform_driver msm_soc_routing_platform = { .ops = &msm_routing_pcm_ops, .probe = msm_routing_probe, .pcm_new = msm_routing_pcm_new, .pcm_free = msm_routing_pcm_free, }; static struct snd_pcm_ops msm_routing_pcm_ops = { .hw_params = msm_pcm_routing_hw_params, .close = msm_pcm_routing_close, .prepare = msm_pcm_routing_prepare, };
snd-soc-dummy-dai static struct snd_soc_dai_driver dummy_dai = { .name = "snd-soc-dummy-dai", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 384, .rates = STUB_RATES, .formats = STUB_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 384, .rates = STUB_RATES, .formats = STUB_FORMATS, }, }; static struct snd_pcm_ops dummy_dma_ops = { .open = dummy_dma_open, .ioctl = snd_pcm_lib_ioctl, };
static struct snd_soc_platform_driver dummy_platform = { .ops = &dummy_dma_ops, }; int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, const struct snd_pcm_hardware *hw) { struct snd_pcm_runtime *runtime = substream->runtime; if (!runtime) return 0; runtime->hw.info = hw->info; runtime->hw.formats = hw->formats; runtime->hw.period_bytes_min = hw->period_bytes_min; runtime->hw.period_bytes_max = hw->period_bytes_max; runtime->hw.periods_min = hw->periods_min; runtime->hw.periods_max = hw->periods_max; runtime->hw.buffer_bytes_max = hw->buffer_bytes_max; runtime->hw.fifo_size = hw->fifo_size; return 0; }
dai_link:
{ .name = LPASS_BE_QUAT_MI2S_TX, .stream_name = "Quaternary MI2S Capture", .cpu_dai_name = "msm-dai-q6-mi2s.3", .platform_name = "msm-pcm-routing", .codec_dai_name = "snd-soc-dummy-dai", .codec_name = "snd-soc-dummy", .no_pcm = 1, .dpcm_capture = 1, .be_id = MSM_BACKEND_DAI_QUATERNARY_MI2S_TX, .be_hw_params_fixup = msm_be_hw_params_fixup, .ops = &msm8952_quat_mi2s_be_ops, .ignore_suspend = 1,
static struct snd_soc_ops msm8952_quat_mi2s_be_ops = { .startup = msm_quat_mi2s_snd_startup, .hw_params = msm_mi2s_snd_hw_params, .shutdown = msm_quat_mi2s_snd_shutdown, };
static int msm_quat_mi2s_snd_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_card *card = rtd->card; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct msm8916_asoc_mach_data *pdata = snd_soc_card_get_drvdata(card); int ret = 0, val = 0;
pr_debug("%s(): substream = %s stream = %d\n", __func__, substream->name, substream->stream);
if (!q6core_is_adsp_ready()) { pr_err("%s(): adsp not ready\n", __func__); return -EINVAL; }
if (pdata->vaddr_gpio_mux_mic_ctl) { val = ioread32(pdata->vaddr_gpio_mux_mic_ctl); val = val | 0x02020002; iowrite32(val, pdata->vaddr_gpio_mux_mic_ctl); } ret = msm_mi2s_sclk_ctl(substream, true); if (ret < 0) { pr_err("failed to enable sclk\n"); return ret; } ret = msm_gpioset_activate(CLIENT_WCD_INT, "quat_i2s"); if (ret < 0) { pr_err("failed to enable codec gpios\n"); goto err; } if (atomic_inc_return(&quat_mi2s_clk_ref) == 1) { ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) pr_err("%s: set fmt cpu dai failed\n", __func__); } return ret; err: ret = msm_mi2s_sclk_ctl(substream, false); if (ret < 0) pr_err("failed to disable sclk\n"); return ret; }
tinymix ‘QUAT_MI2S_RX Audio Mixer MultiMedia1’ 1 tinyplay /data/1.wav QUAT_MI2S_RX Audio Mixer是一个widget ,这里其实和MultiMedia1就决定了后端的端口号,即将要打开的port_id号 MultiMedia1是一个snd_kcontrol_new,根据情况分析,这里为对应的前端,一般MultiMedia1对应alsa前端即上层打开的设备号,对应的声卡设备如下,MultiMedia1 为c0d0 且tinyplay tinymix里面程序默认打开是c0d0p因此能播放,若是MultiMedia5刚不是c0d0p,刚tinymix tinyplay要指定c号d号是多少,详细参见tinyplay命令
程序中用pcm_open打开需要带device id号,如muutimedia6 card是0,device是18 00-18: MultiMedia6 (*) : : playback 1 : capture 1 struct pcm *pcm_open(unsigned int card, unsigned int device, unsigned int flags, struct pcm_config *config); 则device要为18
这里的c号d号为codec在snd_soc_dai_link的数组下标,MultiMedia6为结构中数组中的cpu_dai_name名字
msm8909:/proc/asound # cat pcm cat pcm 00-00: MultiMedia1 (*) : : playback 1 : capture 1 00-01: MultiMedia2 (*) : : playback 1 : capture 1 00-02: CS-Voice (*) : : playback 1 : capture 1 00-03: VoIP (*) : : playback 1 : capture 1 00-04: ULL (*) : : playback 1 00-05: Primary MI2S_RX Hostless (*) : : playback 1 00-06: INT_FM Hostless (*) : : capture 1 00-07: AFE-PROXY RX msm-stub-rx-7 : : playback 1 00-08: AFE-PROXY TX msm-stub-tx-8 : : capture 1 00-09: (Compress1) : : playback 1 : capture 1 00-10: AUXPCM Hostless (*) : : playback 1 : capture 1 00-11: Tertiary MI2S_TX Hostless (*) : : capture 1 00-12: MultiMedia5 (*) : : playback 1 : capture 1 00-13: Voice2 (*) : : playback 1 : capture 1 00-14: MultiMedia9 (*) : : playback 1 : capture 1 00-15: VoLTE (*) : : playback 1 : capture 1 00-16: VoWLAN (*) : : playback 1 : capture 1 00-17: INT_HFP_BT Hostless (*) : : playback 1 : capture 1 00-18: MultiMedia6 (*) : : playback 1 : capture 1 00-19: Listen 1 Audio Service (*) : : capture 1 00-20: Listen 2 Audio Service (*) : : capture 1 00-21: Listen 3 Audio Service (*) : : capture 1 00-22: Listen 4 Audio Service (*) : : capture 1 00-23: Listen 5 Audio Service (*) : : capture 1 00-24: (Compress2) : : playback 1 00-25: QUAT_MI2S Hostless (*) : : playback 1 00-26: Senary_mi2s Capture cajon_vifeedback-26 : : capture 1 00-27: (Compress3) : : playback 1 00-28: (Compress4) : : playback 1 00-29: (Compress5) : : playback 1 00-30: (Compress6) : : playback 1 00-31: (Compress7) : : playback 1 00-32: (Compress8) : : playback 1 00-33: (Compress9) : : playback 1 00-34: VoiceMMode1 (*) : : playback 1 : capture 1 00-35: VoiceMMode2 (*) : : playback 1 : capture 1 00-36: MultiMedia8 (*) : : playback 1 : capture 1 00-37: QCHAT (*) : : playback 1 : capture 1 00-38: (Compress10) : : capture 1 00-39: (Compress11) : : capture 1 00-40: (Compress12) : : capture 1 00-41: (Compress13) : : capture 1 00-42: (Compress14) : : capture 1 00-43: (Primary MI2S Playback) : : playback 1 00-44: (Secondary MI2S Playback) : : playback 1 00-45: (Tertiary MI2S Capture) : : capture 1 00-46: (Quaternary MI2S Playback) : : playback 1 00-47: (Quaternary MI2S Capture) : : capture 1 00-48: (AUX PCM Playback) : : playback 1 00-49: (AUX PCM Capture) : : capture 1 00-50: (Internal BT-SCO Playback) : : playback 1 00-51: (Internal BT-SCO Capture) : : capture 1 00-52: (Internal FM Playback) : : playback 1 00-53: (Internal FM Capture) : : capture 1 00-54: (AFE Playback) : : playback 1 00-55: (AFE Capture) : : capture 1 00-56: (Voice Uplink Capture) : : capture 1 00-57: (Voice Downlink Capture) : : capture 1 00-58: (Voice Farend Playback) : : playback 1 00-59: (Voice2 Farend Playback) : : playback 1 00-60: (Quinary MI2S Capture) : : capture 1 00-61: (Quinary MI2S Playback) : : playback 1 00-62: (Internal BT-A2DP Playback) : : playback 1 msm8909:/proc/asound # cat pcm|grep BT cat pcm|grep BT 00-17: INT_HFP_BT Hostless (*) : : playback 1 : capture 1 00-50: (Internal BT-SCO Playback) : : playback 1 00-51: (Internal BT-SCO Capture) : : capture 1 00-62: (Internal BT-A2DP Playback) : : playback 1 ---------------------
#define SND_SOC_DAPM_SIGGEN(wname) \ { .id = snd_soc_dapm_siggen, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM } #define SND_SOC_DAPM_INPUT(wname) \ { .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM } #define SND_SOC_DAPM_OUTPUT(wname) \ { .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM } #define SND_SOC_DAPM_MIC(wname, wevent) \ { .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD} #define SND_SOC_DAPM_HP(wname, wevent) \ { .id = snd_soc_dapm_hp, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD} #define SND_SOC_DAPM_SPK(wname, wevent) \ { .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD} #define SND_SOC_DAPM_LINE(wname, wevent) \ { .id = snd_soc_dapm_line, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
以上这些widget分别对应信号发生器,输入引脚,输出引脚,麦克风,耳机,扬声器,线路输入接口。其中的reg字段被设置为SND_SOC_NOPM(-1),表明这些widget是没有寄存器控制位来控制widget的电源状态的。麦克风,耳机,扬声器,线路输入接口这几种widget,还可以定义一个dapm事件回调函数wevent,从event_flags字段的设置可以看出,他们只会响应SND_SOC_DAPM_POST_PMU(上电后)和SND_SOC_DAPM_PMD(下电前)事件,这几个widget通常会在machine驱动中定义,而SND_SOC_DAPM_INPUT和SND_SOC_DAPM_OUTPUT则用来定义codec芯片的输出输入脚,通常在codec驱动中定义,最后,在machine驱动中增加相应的route,把麦克风和耳机等widget与相应的codec输入输出引脚的widget连接起来
根据打印,先执行get,获取到值,1,再执行put,将值用于设置连接 将snd_soc_dapm_widget与snd_kcontrol_new连接
inymix 'QUAT_MI2S_RX Audio Mixer MultiMedia1' 1 < [ 340.617577] msm_routing_get_audio_mixer: reg 1e shift 0 val 0 [ 340.622612] msm_pcm_routing_process_audio: reg 1e val 0 set 1
msm_pcm_routing_process_audio
snd_soc_dapm_mixer_update_power soc_dpcm_runtime_update dpcm_process_paths[[[[[[— Create any new FE <–> BE connections–dpcm_be_connect-> list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients);]]]]]]
dpcm_run_new_update dpcm_run_update_startup dpcm_be_dai_startup soc_pcm_open
struct snd_soc_dapm_route { const char *sink; const char *control; const char *source; int (*connected)(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink); };
注意该结构体的注释, sink 是目的部件, source 是源部件, control 是目的部件定义的 kcontrol ;通过 control 可以选择 source 作为 sink 的输入源 ink指向到达端widget的名字字符串,source指向起始端widget的名字字符串,control指向负责控制该连接所对应的kcontrol名字字符串,connected回调则定义了上一节所提到的自定义连接检查回调函数。该结构的意义很明显就是:source通过一个kcontrol,和sink连接在一起,现在是否处于连接状态,请调用connected回调函数检查。
struct snd_soc_dapm_path { const char *name;
/* source (input) and sink (output) widgets */ struct snd_soc_dapm_widget *source; struct snd_soc_dapm_widget *sink;
如上数据结构可以看出path是sink与source是两个widget 下面snd_soc_dapm_route中 {“QUAT_MI2S_RX Port Mixer”, “INTERNAL_BT_SCO_TX”,“INT_BT_SCO_TX”}, "QUAT_MI2S_RX Port Mixer"为widget的名字 "INT_BT_SCO_TX"为widget的名字,代表一个输入设备,数据流入此设备为in
SND_SOC_DAPM_AIF_IN("INT_BT_SCO_TX", "Internal BT-SCO Capture", 0, 0, 0, 0),
"INTERNAL_BT_SCO_TX"为负责控制该连接所对应的kcontrol名字字符串 ———————————————— |