音频调试工具: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"表格,
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格式
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”
tinymix “MultiMedia1 Mixer TERT_MI2S_TX” 1
tinymix “ADC1 Volume” 6
tinymix “DEC1 MUX” “ADC1”
tinycap /data/mic1.wav
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
/*
* 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名字字符串
tinymix controler的函数流程:
[ 48.684637] (2)[2357:AudioHfpThread]------------[ cut here ]------------
[ 48.684644] (1)[233:logd.auditd]type=1400 audit(1262304054.036:226): avc: denied { getattr } for pid=243 comm="AudioHfpThread" path="/dev/__properties__/u:object_r:bluetooth_prop:s0" dev="tmpfs" ino=160 scontext=u:r:mtk_hal_audio:s0 tcontext=u:object_r:bluetooth_prop:s0 tclass=file permissive=1
[ 48.684671] (2)[2357:AudioHfpThread]WARNING: CPU: 2 PID: 2357 at /code/ch007Go/kernel-3.18/sound/soc/mediatek/mt6580/mt_soc_codec_63xx.c:2490 Headset_PGAR_Set+0x1c/0x84()
[ 48.684684] (2)[2357:AudioHfpThread]Modules linked in: wlan_drv bf0fe000 (null) 937604 0 (O) wmt_chrdev_wifi bf0f9000 (null) 5642 0 (O) gps_drv bf0ef000 (null) 21666 0 (O) bt_drv bf0e8000 (null) 11775 0 (O) wmt_drv bf000000 (null) 818500 0 (O)
[ 48.684749] -(2)[2357:AudioHfpThread]CPU: 2 PID: 2357 Comm: AudioHfpThread Tainted: G W O 3.18.79 #3
[ 48.684760] -(2)[2357:AudioHfpThread]Backtrace:
[ 48.684786] -(2)[2357:AudioHfpThread][<c010b7b4>] (dump_backtrace) from [<c010b9cc>] (show_stack+0x18/0x1c)
[ 48.684797] -(2)[2357:AudioHfpThread] r7:00000000 r6:00000000 r5:600e0013 r4:c0d41a54
[ 48.684837] -(2)[2357:AudioHfpThread][<c010b9b4>] (show_stack) from [<c08bbd70>] (dump_stack+0x88/0xa8)
[ 48.684857] -(2)[2357:AudioHfpThread][<c08bbce8>] (dump_stack) from [<c0122ed8>] (warn_slowpath_common+0x70/0x94)
[ 48.684867] -(2)[2357:AudioHfpThread] r7:000009ba r6:c0af3f19 r5:00000009 r4:00000000
[ 48.684906] -(2)[2357:AudioHfpThread][<c0122e68>] (warn_slowpath_common) from [<c0122fe8>] (warn_slowpath_null+0x24/0x2c)
[ 48.684917] -(2)[2357:AudioHfpThread] r8:de4f6400 r7:c2164000 r6:dd822238 r5:dd822200
[ 48.684955] -(2)[2357:AudioHfpThread][<c0122fc4>] (warn_slowpath_null) from [<c0722e28>] (Headset_PGAR_Set+0x1c/0x84)
case SNDRV_CTL_IOCTL_ELEM_WRITE:
return snd_ctl_elem_write_user(ctl, argp);->snd_ctl_elem_write-》result = kctl->put(kctl, control);
[ 48.684973] -(2)[2357:AudioHfpThread][<c0722e0c>] (Headset_PGAR_Set) from [<c06bac60>] (snd_ctl_ioctl+0x2e4/0xc74)
[ 48.684984] -(2)[2357:AudioHfpThread] r5:dd822200 r4:b028e4b0
[ 48.685011] -(2)[2357:AudioHfpThread][<c06ba97c>] (snd_ctl_ioctl) from [<c023940c>] (do_vfs_ioctl+0x9c/0x69c)
[ 48.685022] -(2)[2357:AudioHfpThread] r10:c0d1fe48 r9:00000008 r8:b028e4b0 r7:c2c85513
[ 48.685058] -(2)[2357:AudioHfpThread][<c0239370>] (do_vfs_ioctl) from [<c0239a60>] (SyS_ioctl+0x54/0x78)
[ 48.685069] -(2)[2357:AudioHfpThread] r10:00000000 r9:00000008 r8:b028e4b0 r7:c2c85513
[ 48.685107] -(2)[2357:AudioHfpThread][<c0239a0c>] (SyS_ioctl) from [<c01064c0>] (ret_fast_syscall+0x0/0x44)
[ 48.685121] (2)[2357:AudioHfpThread]---[ end trace 077b2ee3a8ba33d4 ]---
snd_soc_add_codec_controls-》snd_soc_add_controls-》snd_ctl_add
snd_ctl_add(card, snd_soc_cnew(control, data,
control->name, prefix))
snd_soc_cnew-》snd_ctl_new1是snd_kcontrol_new转换成snd_kcontrol的地方
如名字:snd_kcontrol kctl->id.name-》snd_kcontrol_new的name
snd_ctl_elem_write
加调试打印即可追踪:
--- a/kernel-3.18/sound/core/control.c
+++ b/kernel-3.18/sound/core/control.c
@@ -907,6 +907,7 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
result = -EPERM;
} else {
snd_ctl_build_ioff(&control->id, kctl, index_offset);
+ printk("%s %d name:%s value:%ld func:%pF zhongyukang \n",__FUNCTION__,__LINE__,kctl->id.name,control->value.integer.value[0],kctl->put);
result = kctl->put(kctl, control);
}
if (result > 0) {