ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。
Kernel-3.10/sound/core该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分。
Kernel-3.10/sound/soc 针对system-on-chip体系的中间层代码 ,soc/codecs 针对soc体系的各种codec的代码,与平台无关 。
声卡的主要功能
三个主要功能:
(1)播放声音(playback)
(2)录音(capture)
(3)声音控制(control)
运行adb shell ls -l /dev/snd,我们可以看到当前平台注册的声卡驱动设备。
主要分为以下几类:
pcmC0D0p —— Playback
pcmC0D0c —— Capture
controlC0 —— Control,比如各种音频控件开关、音量增益等
ASoC把音频系统同样分为3大部分:Machine,Platform和Codec。
Platform 一般是指某一个SoC平台,比如MT6582, MT6595, MT6752等等,与音频相关的通常包含该SoC中的Clock、AFE、I2S、DMA等等。
Codec 字面上的意思就是编解码器,Codec里面包含了I2S接口、DAC、ADC、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件。
Machine 绑定platform driver和codec driver 。
下面我们来对这三个模块进行分析。
从上图可以看出ASOC中包含了多个platform,它们每种platform对应一个.c代码,下面针对这些platform进行分析。
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_capture.c
snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:
为snd_soc_platform实例申请内存;
从platform_device中获得它的名字,用于Machine驱动的匹配工作;
初始化snd_soc_platform的字段;
把snd_soc_platform实例连接到全局链表platform_list中;
调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;
由上图可知capture被注册到mtk_soc_platform结构体中,接下来分析mtk_afe_capture_ops
static struct snd_pcm_ops mtk_afe_capture_ops = {
.open = mtk_capture_pcm_open,
.close = mtk_capture_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = mtk_capture_pcm_hw_params,
.hw_free = mtk_capture_pcm_hw_free,
.prepare = mtk_capture_pcm_prepare,
.trigger = mtk_capture_pcm_trigger,
.pointer = mtk_capture_pcm_pointer,
.copy = mtk_capture_pcm_copy,
.silence = mtk_capture_pcm_silence,
.page = mtk_capture_pcm_page,
};
static int mtk_capture_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret = 0;
AudDrv_Clk_On();
AudDrv_ADC_Clk_On();//使能模拟时钟
VUL_Control_context = Get_Mem_ControlT(Soc_Aud_Digital_Block_MEM_VUL);
/* can allocate sram_dbg */
AfeControlSramLock();
#ifndef CAPTURE_FORCE_USE_DRAM
if (GetSramState() == SRAM_STATE_FREE) {
pr_warn("mtk_capture_pcm_open use sram\n");
mtk_capture_hardware.buffer_bytes_max = GetCaptureSramSize();
SetSramState(SRAM_STATE_CAPTURE);
mCaptureUseSram = true;
} else {
pr_warn("mtk_capture_pcm_open use dram\n");
mtk_capture_hardware.buffer_bytes_max = UL1_MAX_BUFFER_SIZE;
mCaptureUseSram = false;
}
#else
pr_warn("mtk_capture_pcm_open use dram\n");
mtk_capture_hardware.buffer_bytes_max = UL1_MAX_BUFFER_SIZE;
#endif
AfeControlSramUnLock();
runtime->hw = mtk_capture_hardware;
memcpy((void *)(&(runtime->hw)), (void *)&mtk_capture_hardware , sizeof(struct snd_pcm_hardware));
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_sample_rates);
ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
pr_warn("snd_pcm_hw_constraint_integer failed\n");
if (ret < 0) {
pr_err("mtk_capture_pcm_close\n");
mtk_capture_pcm_close(substream);
return ret;
}
if (mCaptureUseSram == false)
AudDrv_Emi_Clk_On();
pr_warn("mtk_capture_pcm_open return\n");
return 0;
}
static int mtk_capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
pr_warn("mtk_capture_pcm_trigger cmd = %d\n", cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
//唤醒
return mtk_capture_alsa_start(substream);
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
//睡眠
return mtk_capture_alsa_stop(substream);
}
return -EINVAL;
}
static int mtk_capture_alsa_start(struct snd_pcm_substream *substream)
{
pr_warn("mtk_capture_alsa_start\n");
SetMemifSubStream(Soc_Aud_Digital_Block_MEM_VUL, substream);
StartAudioCaptureHardware(substream);
#ifdef DENALI_FPGA_EARLYPORTING /* ccc early porting, copy from TurnOnDacPower() and ADC_LOOP_DAC_Func() */
/* Afe_Set_Reg(AFE_SGEN_CON0, 0x24862862, 0xffffffff); */
/* Ana_Set_Reg(PMIC_AFE_TOP_CON0, 0x0002, 0x0002); //UL from sinetable */
/* Ana_Set_Reg(PMIC_AFE_TOP_CON0, 0x0001, 0x0001); //DL from sinetable */
/* Ana_Set_Reg(AFE_SGEN_CFG0 , 0x0080 , 0xffff); */
/* Ana_Set_Reg(AFE_SGEN_CFG1 , 0x0101 , 0xffff); */
Ana_Get_Reg(AFE_AUDIO_TOP_CON0); /* power on clock */
Ana_Get_Reg(AFUNC_AUD_CON2);
Ana_Get_Reg(AFUNC_AUD_CON0); /* sdm audio fifo clock power on */
Ana_Get_Reg(AFUNC_AUD_CON2); /* sdm power on */
Ana_Get_Reg(AFUNC_AUD_CON2); /* sdm fifo enable */
Ana_Get_Reg(AFE_DL_SDM_CON1); /* set attenuation gain */
Ana_Get_Reg(AFE_UL_DL_CON0); /* [0] afe enable */
Ana_Get_Reg(AFE_PMIC_NEWIF_CFG0); /* 8k sample rate */
Ana_Get_Reg(AFE_DL_SRC2_CON0_H);/* 8k sample rate */
Ana_Get_Reg(AFE_DL_SRC2_CON0_L); /* turn off mute function and turn on dl */
Ana_Get_Reg(PMIC_AFE_TOP_CON0); /* set DL in normal path, not from sine gen table */
Ana_Get_Reg(AFE_SGEN_CFG0); /* set DL in normal path, not from sine gen table */
Ana_Get_Reg(AFE_SGEN_CFG1); /* set DL in normal path, not from sine gen table */
Ana_Get_Reg(TOP_CLKSQ); /* Enable CLKSQ 26MHz */
Ana_Get_Reg(TOP_CLKSQ_SET); /* Turn on 26MHz source clock */
Ana_Get_Reg(AFE_AUDIO_TOP_CON0); /* power on clock */
Ana_Get_Reg(FPGA_CFG1); /* must set in FPGA platform for PMIC digital loopback */
#endif
return 0;
}
通用的PCM ioctl回调函数
/**
* snd_pcm_lib_ioctl - a generic PCM ioctl callback
* @substream: the pcm substream instance
* @cmd: ioctl command
* @arg: ioctl argument
*
* Processes the generic ioctl commands for PCM.
* Can be passed as the ioctl callback for PCM ops.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream,
unsigned int cmd, void *arg)
{
switch (cmd) {
case SNDRV_PCM_IOCTL1_INFO:
return 0;
case SNDRV_PCM_IOCTL1_RESET:
return snd_pcm_lib_ioctl_reset(substream, arg);
case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
return snd_pcm_lib_ioctl_channel_info(substream, arg);
case SNDRV_PCM_IOCTL1_FIFO_SIZE:
return snd_pcm_lib_ioctl_fifo_size(substream, arg);
}
return -ENXIO;
}
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_fm_i2s.c
static struct snd_pcm_ops mtk_fm_i2s_ops = {
.open = mtk_pcm_fm_i2s_open,
.close = mtk_pcm_fm_i2s_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = mtk_pcm_fm_i2s_hw_params,
.hw_free = mtk_pcm_fm_i2s_hw_free,
.prepare = mtk_pcm_fm_i2s_prepare,
.trigger = mtk_pcm_fm_i2s_trigger,
.pointer = mtk_pcm_fm_i2s_pointer,
.copy = mtk_pcm_fm_i2s_copy,
.silence = mtk_pcm_fm_i2s_silence,
.page = mtk_fm_i2s_pcm_page,
};
接下来分析mtk_fm_i2s_ops
static int mtk_pcm_fm_i2s_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret = 0;
AudDrv_Clk_On();
AudDrv_I2S_Clk_On();//打开I2S时钟
/*
static struct snd_pcm_hardware mtk_fm_i2s_hardware = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SND_SOC_STD_MT_FMTS,
.rates = SOC_HIGH_USE_RATE,
.rate_min = SOC_NORMAL_USE_RATE_MIN,
.rate_max = SOC_NORMAL_USE_RATE_MAX,
.channels_min = SOC_NORMAL_USE_CHANNELS_MIN,
.channels_max = SOC_NORMAL_USE_CHANNELS_MAX,
.buffer_bytes_max = FM_I2S_MAX_BUFFER_SIZE,
.period_bytes_max = FM_I2S_MAX_PERIOD_SIZE,
.periods_min = FM_I2S_MIN_PERIOD_SIZE,
.periods_max = FM_I2S_MAX_PERIOD_SIZE,
.fifo_size = 0,
};
#define SOC_HIGH_USE_RATE (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000)
#define SOC_HIGH_USE_RATE_MIN 8000
#define SOC_HIGH_USE_RATE_MAX 192000
#define SOC_HIGH_USE_CHANNELS_MIN 1
#define SOC_HIGH_USE_CHANNELS_MAX 8
*/
/*pr_warn("mtk_pcm_fm_i2s_open\n");*/
runtime->hw = mtk_fm_i2s_hardware;
memcpy((void *)(&(runtime->hw)), (void *)&mtk_fm_i2s_hardware ,
sizeof(struct snd_pcm_hardware));
/*
static struct snd_pcm_hw_constraint_list fm_i2s_constraints_sample_rates = {
.count = ARRAY_SIZE(soc_fm_supported_sample_rates),
.list = soc_fm_supported_sample_rates,
.mask = 0,
};
const unsigned int soc_fm_supported_sample_rates[3] = {
32000, 44100, 48000
};
支持的采样率
*/
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&fm_i2s_constraints_sample_rates);
ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
pr_warn("snd_pcm_hw_constraint_integer failed\n");
if (ret < 0) {
pr_err("mtk_pcm_fm_i2s_close\n");
mtk_pcm_fm_i2s_close(substream);
return ret;
}
static int mtk_pcm_fm_i2s_prepare(struct snd_pcm_substream *substream)
{
AudioDigtalI2S m2ndI2SInAttribute;
struct snd_pcm_runtime *runtime = substream->runtime;
pr_warn("%s rate = %d\n", __func__, runtime->rate);
if (mPrepareDone == false) {
/* mtk_wcn_cmb_stub_audio_ctrl((CMB_STUB_AIF_X)CMB_STUB_AIF_3);//temp mark for early porting */
/*
外挂smart pa,FM切到外放播放无声
1.公版默认的行为,headset和speaker 都是走PMIC这边输出,也就是interconnection对应的O03,O04.
2.当外挂Smart PA后,Smart PA连接的I2S为I2S0,I2S3,output 的interconnection 为O00,O01;
3.FM 从headset 切换到speaker 后,headset走的还是PMIC(O03,O04),而speaker 走的是Smart PA,所以FM driver文件里面的interconnection 需要增加O00,O01的设置
4.如果还有其他应用场景,同样会出现切换smart PA后,没有声音,请首先确认interconnection是否有正确的设置,具体的修改是根据应用场景走的是哪一只driver 文件,在对应的driver 文件里面prepare函数添加O00,O01的interconnection。
在mt_soc_pcm_dl1_i2s0Dl1.c的start函数中添加O00,O01的interconnection后,fm切换speaker可以使用了
我们公版默认的行为,,FM 从headset 切到speaker 后,把headset 关闭
只让speaker 输出,如果贵司想让一起输出,找到关闭headset地方打开即可
*/
/* interconnection setting */
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I00,
Soc_Aud_InterConnectionOutput_O13);
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I01,
Soc_Aud_InterConnectionOutput_O14);
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I10,
Soc_Aud_InterConnectionOutput_O03);
SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I11,
Soc_Aud_InterConnectionOutput_O04);
/* Set HW_GAIN */
SetHwDigitalGainMode(Soc_Aud_Hw_Digital_Gain_HW_DIGITAL_GAIN1, runtime->rate,
0x40);
SetHwDigitalGainEnable(Soc_Aud_Hw_Digital_Gain_HW_DIGITAL_GAIN1, true);
SetHwDigitalGain(mfm_i2s_Volume, Soc_Aud_Hw_Digital_Gain_HW_DIGITAL_GAIN1);
/* start I2S DAC out */
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_OUT_DAC) == false) {
SetI2SDacOut(runtime->rate, false, Soc_Aud_I2S_WLEN_WLEN_16BITS);
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_OUT_DAC, true);
SetI2SDacEnable(true);
} else
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_OUT_DAC, true);
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2) == false) {
/* set merge interface */
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, true);
/* Config 2nd I2S IN */
memset_io((void *)&m2ndI2SInAttribute, 0, sizeof(m2ndI2SInAttribute));
m2ndI2SInAttribute.mLR_SWAP = Soc_Aud_LR_SWAP_NO_SWAP;
m2ndI2SInAttribute.mI2S_IN_PAD_SEL = false; /* I2S_IN_FROM_CONNSYS */
m2ndI2SInAttribute.mI2S_SLAVE = Soc_Aud_I2S_SRC_SLAVE_MODE;
m2ndI2SInAttribute.mI2S_SAMPLERATE = 32000;
m2ndI2SInAttribute.mINV_LRCK = Soc_Aud_INV_LRCK_NO_INVERSE;
m2ndI2SInAttribute.mI2S_FMT = Soc_Aud_I2S_FORMAT_I2S;
m2ndI2SInAttribute.mI2S_WLEN = Soc_Aud_I2S_WLEN_WLEN_16BITS;
Set2ndI2SIn(&m2ndI2SInAttribute);
if (runtime->rate == 48000)
SetI2SASRCConfig(true, 48000); /* Covert from 32000 Hz to 48000 Hz */
else
SetI2SASRCConfig(true, 44100); /* Covert from 32000 Hz to 44100 Hz */
SetI2SASRCEnable(true);
Set2ndI2SInEnable(true);
} else
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, true);
EnableAfe(true);
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_OUT_DAC) == true)
SetI2SADDAEnable(true);
mPrepareDone = true;
}
return 0;
}
static int mtk_pcm_fm_i2s_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
pr_warn("%s rate = %d\n", __func__, runtime->rate);
/* mtk_wcn_cmb_stub_audio_ctrl((CMB_STUB_AIF_X)CMB_STUB_AIF_0);//temp mark for early porting */
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, false);
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2) == false) {
SetI2SASRCEnable(false);
SetI2SASRCConfig(false, 0); /* Setting to bypass ASRC */
Set2ndI2SInEnable(false);
}
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_OUT_DAC, false);
if (GetI2SDacEnable() == false) {
SetI2SADDAEnable(false);
SetI2SDacEnable(false);
}
/* interconnection setting */
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I00,
Soc_Aud_InterConnectionOutput_O13);
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I01,
Soc_Aud_InterConnectionOutput_O14);
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I10,
Soc_Aud_InterConnectionOutput_O03);
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I11,
Soc_Aud_InterConnectionOutput_O04);
EnableAfe(false);
AudDrv_I2S_Clk_Off();
AudDrv_Clk_Off();
mPrepareDone = false;
SetFMEnableFlag(false);
return 0;
}
static int mtk_pcm_fm_i2s_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
pr_warn("%s rate = %d\n", __func__, runtime->rate);
/* mtk_wcn_cmb_stub_audio_ctrl((CMB_STUB_AIF_X)CMB_STUB_AIF_0);//temp mark for early porting */
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, false);
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2) == false) {
SetI2SASRCEnable(false);
SetI2SASRCConfig(false, 0); /* Setting to bypass ASRC */
Set2ndI2SInEnable(false);
}
SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_OUT_DAC, false);
if (GetI2SDacEnable() == false) {
SetI2SADDAEnable(false);
SetI2SDacEnable(false);
}
/* interconnection setting */
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I00,
Soc_Aud_InterConnectionOutput_O13);
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I01,
Soc_Aud_InterConnectionOutput_O14);
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I10,
Soc_Aud_InterConnectionOutput_O03);
SetConnection(Soc_Aud_InterCon_DisConnect, Soc_Aud_InterConnectionInput_I11,
Soc_Aud_InterConnectionOutput_O04);
EnableAfe(false);
AudDrv_I2S_Clk_Off();
AudDrv_Clk_Off();
mPrepareDone = false;
SetFMEnableFlag(false);
return 0;
}
Asoc 还有很多platform,和上面注册方式都一致,只是在ops方面不同,在此就不一一分析了。
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_dai_stub.c
snd_soc_register_component(&pdev->dev, &mt_dai_component,
mtk_dai_stub_dai, ARRAY_SIZE(mtk_dai_stub_dai));
int snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
{
...
cmpnt->ignore_pmdown_time = true;
cmpnt->registered_as_component = true;
ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);
...
snd_soc_component_add(cmpnt);
return 0;
}
static int snd_soc_register_dais(struct snd_soc_component *component,
struct snd_soc_dai_driver *dai_drv, size_t count,
bool legacy_dai_naming)
{
...
for (i = 0; i < count; i++) {
if (count == 1 && legacy_dai_naming) {
dai->name = fmt_single_name(dev, &dai->id);
} else {
dai->name = fmt_multiple_name(dev, &dai_drv[i]);
if (dai_drv[i].id)
dai->id = dai_drv[i].id;
else
dai->id = i;
}
}
dai->component = component;
dai->dev = dev;
dai->driver = &dai_drv[i]; //dai->driver = &dai_drv[i] = mtk_dai_stub_dai
list_add(&dai->list, &component->dai_list);//list_add(&cmpnt->list, &component_list)
}
...
return 0;
}
Platform Dai 通过snd_soc_register_component进行注册,将数组mtk_dai_stub_dai[]传入,再通过snd_soc_register_dais将所有的PCM(platform)(如播放、录音、通话等)循环加入dai list里面。
下面看下mtk_dai_stub_dai数组。
static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
{
.playback = {
.stream_name = MT_SOC_DL1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_DL1DAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_UL1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_UL1DAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_TDM_CAPTURE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_TDMRX_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_HDMI_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 192000,
},
.capture = {
.stream_name = MT_SOC_HDMI_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_HDMI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_VOICE_MD1_BT_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 32000,
},
.name = MT_SOC_VOICE_MD1_BT_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_VOICE_MD2_BT_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 32000,
},
.name = MT_SOC_VOICE_MD2_BT_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_VOIP_BT_OUT_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_VOIP_CALL_BT_OUT_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_VOIP_BT_IN_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_VOIP_CALL_BT_IN_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_FM_I2S2_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000 ,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = "FM_I2S2_OUT",
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_FM_I2S2_RECORD_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = "FM_I2S2_IN",
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_VOICE_MD1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_STD_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 32000,
},
.capture = {
.stream_name = MT_SOC_VOICE_MD1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_STD_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 32000,
},
.name = MT_SOC_VOICE_MD1_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_VOICE_MD2_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_STD_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 32000,
},
.capture = {
.stream_name = MT_SOC_VOICE_MD2_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_STD_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 32000,
},
.name = MT_SOC_VOICE_MD2_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_ULDLLOOPBACK_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.capture = {
.stream_name = MT_SOC_ULDLLOOPBACK_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_ULDLLOOPBACK_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_I2S0_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.capture = {
.stream_name = MT_SOC_I2S0_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_I2S0_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_I2S0DL1_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_DL1_AWB_RECORD_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_DL1AWB_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_MRGRX_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.capture = {
.stream_name = MT_SOC_MRGRX_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_MRGRX_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_MRGRX_CAPTURE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.capture = {
.stream_name = MT_SOC_MRGRX_CAPTURE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_MRGRXCAPTURE_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_FM_MRGTX_STREAM_NAME,
.rates = SNDRV_PCM_RATE_44100,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 44100,
.rate_max = 44100,
},
.name = MT_SOC_FM_MRGTX_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_UL1DATA2_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_UL2DAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_I2S0AWB_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_I2S0AWBDAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_MODADCI2S_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_MODADCI2SDAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_ADC2AWB_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_ADC2AWBDAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.capture = {
.stream_name = MT_SOC_IO2DAI_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_IO2DAIDAI_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_HP_IMPEDANCE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_HP_IMPEDANCE_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_FM_I2S_PLAYBACK_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.capture = {
.stream_name = MT_SOC_FM_I2S_PLAYBACK_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_FM_I2S_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_FM_I2S_CAPTURE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.capture = {
.stream_name = MT_SOC_FM_I2S_CAPTURE_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.name = MT_SOC_FM_I2S_CAPTURE_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_OFFLOAD_GDMA_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 48000,
},
.compress_dai = 1,
.name = MT_SOC_OFFLOAD_GDMA_NAME,
.ops = &mtk_dai_stub_ops,
},
{
.playback = {
.stream_name = MT_SOC_DL2_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_DL2DAI_NAME,
.ops = &mtk_dai_stub_ops,
},
};
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c
Codec和Codec dai都是在一个文件注册完成的,下面进行分析mt_soc_codec_63xx.c文件。
从上图可知snd_soc_register_codec分别注册了codec和codec dai,下面从这两个方面进行分析。
static struct snd_soc_codec_driver soc_mtk_codec = {
.probe = mt6331_codec_probe,
.remove = mt6331_codec_remove,
.read = mt6331_read,
.write = mt6331_write,
/* use add control to replace */
/* .controls = mt6331_snd_controls, */
/* .num_controls = ARRAY_SIZE(mt6331_snd_controls), */
.dapm_widgets = mt6331_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt6331_dapm_widgets),
.dapm_routes = mtk_audio_map,
.num_dapm_routes = ARRAY_SIZE(mtk_audio_map),
};
下面进行分析soc_mtk_codec结构体
static int mt6331_codec_probe(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = &codec->dapm;
pr_warn("%s()\n", __func__);
if (mInitCodec == true)
return 0;
pin_extspkamp = pin_extspkamp_2 = pin_vowclk = pin_audmiso = pin_rcvspkswitch = 0;
pin_mode_extspkamp = pin_mode_extspkamp_2 = pin_mode_vowclk = pin_mode_audmiso =
pin_mode_rcvspkswitch = 0;
snd_soc_dapm_new_controls(dapm, mt6331_dapm_widgets, ARRAY_SIZE(mt6331_dapm_widgets));
snd_soc_dapm_add_routes(dapm, mtk_audio_map, ARRAY_SIZE(mtk_audio_map));
/* add codec controls */
snd_soc_add_codec_controls(codec, mt6331_snd_controls, ARRAY_SIZE(mt6331_snd_controls));
snd_soc_add_codec_controls(codec, mt6331_UL_Codec_controls,
ARRAY_SIZE(mt6331_UL_Codec_controls));
snd_soc_add_codec_controls(codec, mt6331_Voice_Switch, ARRAY_SIZE(mt6331_Voice_Switch));
snd_soc_add_codec_controls(codec, mt6331_pmic_Test_controls,
ARRAY_SIZE(mt6331_pmic_Test_controls));
#ifdef CONFIG_MTK_SPEAKER
snd_soc_add_codec_controls(codec, mt6331_snd_Speaker_controls,
ARRAY_SIZE(mt6331_snd_Speaker_controls));
#endif
snd_soc_add_codec_controls(codec, Audio_snd_auxadc_controls,
ARRAY_SIZE(Audio_snd_auxadc_controls));
/* here to set private data */
mCodec_data = kzalloc(sizeof(mt6331_Codec_Data_Priv), GFP_KERNEL);
if (!mCodec_data) {
pr_warn("Failed to allocate private data\n");
return -ENOMEM;
}
snd_soc_codec_set_drvdata(codec, mCodec_data);
memset((void *)mCodec_data, 0, sizeof(mt6331_Codec_Data_Priv));
mt6331_codec_init_reg(codec);
InitCodecDefault();
mInitCodec = true;
return 0;
}
上面代码为probe函数,里面有很多重要知识,下面进行一一分析。
DAMP(Dynamic Audio Power Management)是动态音频电源管理,是为了让移动设备在任何时候都工作在最小功耗下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
snd_soc_dapm_new_controls: snd_soc_dapm_new_controls会调用snd_soc_dapm_new_controls函数,snd_soc_dapm_new_control只是创建widget的第一步,它为每个widget分配内存,初始化必要的字段,然后把这些widget挂在代表声卡的snd_soc_card的widgets链表字段中。
snd_soc_dapm_route: 系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义。通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,我们一样有两种方式来注册音频路径信息:
(1)通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中的dapm_routes和num_dapm_routes字段;
(2)在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;
两种方法最终都是通过调用snd_soc_dapm_add_routes函数来完成音频路径的注册工作的。
snd_soc_dapm_new_controls(dapm, mt6331_dapm_widgets, ARRAY_SIZE(mt6331_dapm_widgets))
//mt6331_dapm_widgets数组定义如下:
static const struct snd_soc_dapm_widget mt6331_dapm_widgets[] = {
/* Outputs */
SND_SOC_DAPM_OUTPUT("EARPIECE"),//听筒
SND_SOC_DAPM_OUTPUT("HEADSET"),//耳机
SND_SOC_DAPM_OUTPUT("SPEAKER"),//喇叭
/*
SND_SOC_DAPM_MUX_E("VOICE_Mux_E", SND_SOC_NOPM, 0, 0 , &mt6331_Voice_Switch, codec_enable_rx_bias,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD |
SND_SOC_DAPM_PRE_REG | SND_SOC_DAPM_POST_REG),
*/
};
snd_soc_dapm_add_routes(dapm, mtk_audio_map, ARRAY_SIZE(mtk_audio_map))
//mtk_audio_map数组定义如下
static const struct snd_soc_dapm_route mtk_audio_map[] = {
{"VOICE_Mux_E", "Voice Mux", "SPEAKER PGA"},
};
snd_soc_add_codec_controls:该函数首先通过 snd_soc_cnew(创建新的control) 函数将这些来自snd_ctl_new1的成员组织到新分配的snd_kcontrol结构体成员中,然后调用 snd_ctl_add 函数,将这些音频控件添加到声卡对象(struct snd_card)的控件列表中(card->controls),同时为这个kcontrol分配一个唯一的id号。
对于每个控件,我们需要定义一个和它对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。
snd_soc_add_codec_controls(codec, mt6331_snd_controls, ARRAY_SIZE(mt6331_snd_controls));
//mt6331_snd_controls数组定义如下
/////////////////////////////////////////////////////////
/*
static int Audio_AmpR_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("Audio_AmpR_Get = %d\n",mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_HEADSETR]);
ucontrol->value.integer.value[0] = mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_HEADSETR];//选择设备电源类型
return 0;
}
static int Audio_AmpR_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
mutex_lock(&Ana_Ctrl_Mutex);
pr_warn("%s()\n", __func__);
if ((ucontrol->value.integer.value[0] == true)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_HEADSETR] == false))//判断选择了正确的耳机设备电源类型
{
Audio_Amp_Change(AUDIO_ANALOG_CHANNELS_RIGHT1, true);//此函数主要功能:1、上电 2、设置pmic模拟控制寄存器 3、耳机音量设置
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_HEADSETR] =
ucontrol->value.integer.value[0];
} else if ((ucontrol->value.integer.value[0] == false)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_HEADSETR] ==
true)) {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_HEADSETR] =
ucontrol->value.integer.value[0];
Audio_Amp_Change(AUDIO_ANALOG_CHANNELS_RIGHT1, false);
}
mutex_unlock(&Ana_Ctrl_Mutex);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_Amp_R_Switch", Audio_DL_Enum[0], Audio_AmpR_Get, Audio_AmpR_Set),//耳机右声道控件开关
//左声道设置与上面右声道设置大致一样,耳机电源类型选择不同
SOC_ENUM_EXT("Audio_Amp_L_Switch", Audio_DL_Enum[1], Audio_AmpL_Get, Audio_AmpL_Set),//耳机左声道控件开关
/////////////////////////////////////////////////////////
/*
//听筒
static int Voice_Amp_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("Voice_Amp_Get = %d\n",
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EARPIECEL]);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EARPIECEL];//选择听筒电源类型
return 0;
}
static int Voice_Amp_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
mutex_lock(&Ana_Ctrl_Mutex);
pr_warn("%s()\n", __func__);
if ((ucontrol->value.integer.value[0] == true)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EARPIECEL] == false)) {
Voice_Amp_Change(true);//1、上电 2、设置pmic寄存器 3、
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EARPIECEL] =
ucontrol->value.integer.value[0];
} else if ((ucontrol->value.integer.value[0] == false)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EARPIECEL] ==
true)) {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EARPIECEL] =
ucontrol->value.integer.value[0];
Voice_Amp_Change(false);
}
mutex_unlock(&Ana_Ctrl_Mutex);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Voice_Amp_Switch", Audio_DL_Enum[2], Voice_Amp_Get, Voice_Amp_Set),//听筒控件开关
/////////////////////////////////////////////////////////
/*
static int Speaker_Amp_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKERL];//选择喇叭电源类型
return 0;
}
static int Speaker_Amp_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s() value = %ld\n ", __func__, ucontrol->value.integer.value[0]);
if ((ucontrol->value.integer.value[0] == true)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKERL] == false)) {
Speaker_Amp_Change(true);//1、上电 2、设置pmic 3、AB类和D类模式选择 4、设置喇叭gain值 (注:3和4只在用内置PA时设置)
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKERL] =
ucontrol->value.integer.value[0];
} else if ((ucontrol->value.integer.value[0] == false)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKERL] ==
true)) {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKERL] =
ucontrol->value.integer.value[0];
Speaker_Amp_Change(false);
}
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Speaker_Amp_Switch", Audio_DL_Enum[3], Speaker_Amp_Get, Speaker_Amp_Set),//喇叭控件开关
/////////////////////////////////////////////////////////
/*
static int Headset_Speaker_Amp_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKER_HEADSET_R];//选择耳机和喇叭同时工作电源类型
return 0;
}
static int Headset_Speaker_Amp_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); */
pr_warn("%s() gain = %lu\n ", __func__, ucontrol->value.integer.value[0]);
if ((ucontrol->value.integer.value[0] == true)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKER_HEADSET_R] ==
false)) {
Headset_Speaker_Amp_Change(true);//1、打开电源 2、设置pmic寄存器 3、选择AB类 、D类和receiver模式 4、耳机音量设置 5、喇叭gain值设置 (注:3和5只在内置PA设置)
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKER_HEADSET_R] =
ucontrol->value.integer.value[0];
} else if ((ucontrol->value.integer.value[0] == false)
&& (mCodec_data->
mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKER_HEADSET_R] == true)) {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_SPEAKER_HEADSET_R] =
ucontrol->value.integer.value[0];
Headset_Speaker_Amp_Change(false);
}
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Headset_Speaker_Amp_Switch", Audio_DL_Enum[4], Headset_Speaker_Amp_Get,
Headset_Speaker_Amp_Set),//耳机喇叭控件开关
SOC_ENUM_EXT("Headset_PGAL_GAIN", Audio_DL_Enum[5], Headset_PGAL_Get, Headset_PGAL_Set),
SOC_ENUM_EXT("Headset_PGAR_GAIN", Audio_DL_Enum[6], Headset_PGAR_Get, Headset_PGAR_Set),
SOC_ENUM_EXT("Handset_PGA_GAIN", Audio_DL_Enum[7], Handset_PGA_Get, Handset_PGA_Set),
SOC_ENUM_EXT("Lineout_PGAR_GAIN", Audio_DL_Enum[8], Lineout_PGAR_Get, Lineout_PGAR_Set),
SOC_ENUM_EXT("AUD_CLK_BUF_Switch", Audio_DL_Enum[10], Aud_Clk_Buf_Get, Aud_Clk_Buf_Set),
/////////////////////////////////////////////////////////
/*
static int Ext_Speaker_Amp_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_debug("%s()\n", __func__);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EXTSPKAMP];//选择外置PA电源类型
return 0;
}
static int Ext_Speaker_Amp_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_debug("%s() gain = %ld\n ", __func__, ucontrol->value.integer.value[0]);
if (ucontrol->value.integer.value[0]) {
Ext_Speaker_Amp_Change(true);//外置PA使能脚gpio控制
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EXTSPKAMP] =
ucontrol->value.integer.value[0];
} else {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_OUT_EXTSPKAMP] =
ucontrol->value.integer.value[0];
Ext_Speaker_Amp_Change(false);
}
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Ext_Speaker_Amp_Switch", Audio_DL_Enum[11], Ext_Speaker_Amp_Get,
Ext_Speaker_Amp_Set),//外置PA控件开关
/////////////////////////////////////////////////////////
/*
static int Receiver_Speaker_Switch_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("%s() : %d\n", __func__,
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_RECEIVER_SPEAKER_SWITCH]);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_RECEIVER_SPEAKER_SWITCH];//选择听筒喇叭切换电源类型
return 0;
}
static int Receiver_Speaker_Switch_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("%s()\n", __func__);
if ((ucontrol->value.integer.value[0] == true)
&& (mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_RECEIVER_SPEAKER_SWITCH] ==
false)) {
Receiver_Speaker_Switch_Change(true);//根据传进去的参数判断,ture,使能听筒,关喇叭 false,使能喇叭,关听筒。 例如通话时开关扬声器操作
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_RECEIVER_SPEAKER_SWITCH] =
ucontrol->value.integer.value[0];
} else if ((ucontrol->value.integer.value[0] == false)
&& (mCodec_data->
mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_RECEIVER_SPEAKER_SWITCH] ==
true)) {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_RECEIVER_SPEAKER_SWITCH] =
ucontrol->value.integer.value[0];
Receiver_Speaker_Switch_Change(false);
}
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Receiver_Speaker_Switch", Audio_DL_Enum[11], Receiver_Speaker_Switch_Get,
Receiver_Speaker_Switch_Set),//听筒喇叭切换控件开关
SOC_SINGLE_EXT("Audio HP Impedance", SND_SOC_NOPM, 0, 512, 0, Audio_Hp_Impedance_Get,
Audio_Hp_Impedance_Set),
};
snd_soc_add_codec_controls(codec, mt6331_UL_Codec_controls,
ARRAY_SIZE(mt6331_UL_Codec_controls));
//mt6331_UL_Codec_controls数组定义如下
static const struct snd_kcontrol_new mt6331_UL_Codec_controls[] = {
/////////////////////////////////////////////////////////
/*
static int Audio_ADC1_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("Audio_ADC1_Get = %d\n",
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_IN_ADC1]);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_IN_ADC1];//选择ADC1电源类型
return 0;
}
static int Audio_ADC1_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
mutex_lock(&Ana_Power_Mutex);
if (ucontrol->value.integer.value[0]) {
//从上图可知MIC代码定义六中模式,本代码有四种模式ACC、DCC、DMIC、DCCECMDIFF可选。定义手机mic和耳机mic模式,具体根据硬件设计有关。定义mic模式代码在vendor/mediatek/proprietary/custom/project_name/hal/audioflinger/audio/audio_custom_exp.h里
//1 : ACC mode (Traditional)
//2 : DCC mode without internal bias circuit (MEMS Mic) (default)
//3 : Digital MIC
if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_ACC)
TurnOnADcPowerACC(AUDIO_ANALOG_DEVICE_IN_ADC1, true);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DCC)
TurnOnADcPowerDCC(AUDIO_ANALOG_DEVICE_IN_ADC1, true, 0);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DMIC)
TurnOnADcPowerDmic(AUDIO_ANALOG_DEVICE_IN_ADC1, true);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DCCECMDIFF)
TurnOnADcPowerDCC(AUDIO_ANALOG_DEVICE_IN_ADC1, true, 1);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DCCECMSINGLE)
TurnOnADcPowerDCC(AUDIO_ANALOG_DEVICE_IN_ADC1, true, 2);
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_IN_ADC1] =
ucontrol->value.integer.value[0];
} else {
mCodec_data->mAudio_Ana_DevicePower[AUDIO_ANALOG_DEVICE_IN_ADC1] =
ucontrol->value.integer.value[0];
if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_ACC)
TurnOnADcPowerACC(AUDIO_ANALOG_DEVICE_IN_ADC1, false);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DCC)
TurnOnADcPowerDCC(AUDIO_ANALOG_DEVICE_IN_ADC1, false, 0);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DMIC)
TurnOnADcPowerDmic(AUDIO_ANALOG_DEVICE_IN_ADC1, false);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DCCECMDIFF)
TurnOnADcPowerDCC(AUDIO_ANALOG_DEVICE_IN_ADC1, false, 1);
else if (mAudio_Analog_Mic1_mode == AUDIO_ANALOGUL_MODE_DCCECMSINGLE)
TurnOnADcPowerDCC(AUDIO_ANALOG_DEVICE_IN_ADC1, false, 2);
}
mutex_unlock(&Ana_Power_Mutex);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_ADC_1_Switch", Audio_UL_Enum[0], Audio_ADC1_Get, Audio_ADC1_Set),//主麦克风控件开关
SOC_ENUM_EXT("Audio_ADC_2_Switch", Audio_UL_Enum[1], Audio_ADC2_Get, Audio_ADC2_Set),//耳机麦克风控件开关,和ADC1基本相同,只是adc通道不同
SOC_ENUM_EXT("Audio_ADC_3_Switch", Audio_UL_Enum[2], Audio_ADC3_Get, Audio_ADC3_Set),//不支持
SOC_ENUM_EXT("Audio_ADC_4_Switch", Audio_UL_Enum[3], Audio_ADC4_Get, Audio_ADC4_Set),//不支持
/////////////////////////////////////////////////////////
//Mixer可以混合多个输入到输出
//Mux只能从多个输入里选择一个作为输出
//Mixer当然也可以只从多个输入里选择一个作为输出
/*
static int Audio_PreAmp1_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s() mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_PREAMP_1]; = %d\n", __func__,
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_PREAMP_1]);
ucontrol->value.integer.value[0] =
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_PREAMP_1];//选择前置放大器Mux
return 0;
}
static int Audio_PreAmp1_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(PreAmp_Mux_function)) {
pr_err("return -EINVAL\n");
return -EINVAL;
}
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_PREAMP_1] =
ucontrol->value.integer.value[0];
/////
static bool AudioPreAmp1_Sel(int Mul_Sel)
{
pr_warn("%s Mul_Sel = %d ", __func__, Mul_Sel);
if (Mul_Sel == 0)
Ana_Set_Reg(AUDENC_ANA_CON0, 0x0000, 0x00C0); /* pinumx open */
else if (Mul_Sel == 1)
Ana_Set_Reg(AUDENC_ANA_CON0, 0x0040, 0x00C0); /* AIN0 */
else if (Mul_Sel == 2)
Ana_Set_Reg(AUDENC_ANA_CON0, 0x0080, 0x00C0); /* AIN1 */
else if (Mul_Sel == 3)
Ana_Set_Reg(AUDENC_ANA_CON0, 0x00C0, 0x00C0); /* AIN2 */
else
pr_warn("AudioPreAmp1_Sel warning");
return true;
}
由上可知此函数设置差分输入AIN0、AIN1、AIN2,由上上图知是麦克风前置放大器设置AIN。所谓的差分输入形式。
差分放大电路利用电路参数的对称性和负反馈作用,有效地稳定静态工作点,以放大差模信号抑制共模信号为显著特征,广泛应用于直接耦合电路和测量电路的输入级。
下面来简单的介绍一下单端输入与差分输入。
在单端方式工作时,ADC转换的是单输入引脚对地的电压值。当增益为1时,测量的值就是输入的电压值;范围是0V到VREF;当增益增加时,输入的范围要相应的减小;
在差分方式工作时;ADC转换的是AIN+与AIN-两个引脚的差值;在增益为1时,测量的值等于(AIN+)-(AIN-),范围是-VREF到+VREF;当增益增加时,输入的范围要相应的减小。
注意:在差分方式时所提的负压是指AIN-引脚的电压大于AIN+引脚的电压,实际输入到两个引脚的电压对地都必需是正的;例如:如果AIN+引脚输入的电压为0V,AIN-引脚的输入电压为1/2VREF时,差分的输入电压为(0V-1/2VREF) = -1/2VREF
/////////
AudioPreAmp1_Sel(mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_PREAMP_1]);
pr_warn("%s() done\n", __func__);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_Preamp1_Switch", Audio_UL_Enum[4], Audio_PreAmp1_Get,
Audio_PreAmp1_Set),//前置放大器控件开关,前置放大器是指置于信源与放大器级之间的电路或电子设备,是专为接受来自信源的微弱电压信号而设计的
/////////////////////////////////////////////////////////
/*
static int Audio_ADC1_Sel_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s() = %d\n", __func__, mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC1]);
ucontrol->value.integer.value[0] = mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC1];//选择mic1 mux
return 0;
}
static int Audio_ADC1_Sel_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(Adc_Input_Sel)) {
pr_err("return -EINVAL\n");
return -EINVAL;
}
if (ucontrol->value.integer.value[0] == 0)
Ana_Set_Reg(AUDENC_ANA_CON0, (0x0000 << 9), 0x0600); /* pinumx sel */
else if (ucontrol->value.integer.value[0] == 1)
Ana_Set_Reg(AUDENC_ANA_CON0, (0x0001 << 9), 0x0600); /* AIN0 */
else if (ucontrol->value.integer.value[0] == 2)
Ana_Set_Reg(AUDENC_ANA_CON0, (0x0002 << 9), 0x0600); /* Left preamp */
else
pr_warn("%s() warning\n ", __func__);
pr_warn("%s() done\n", __func__);
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC1] = ucontrol->value.integer.value[0];
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_ADC_1_Sel", Audio_UL_Enum[5], Audio_ADC1_Sel_Get, Audio_ADC1_Sel_Set),//mic1差分输入控件开关
SOC_ENUM_EXT("Audio_ADC_2_Sel", Audio_UL_Enum[6], Audio_ADC2_Sel_Get, Audio_ADC2_Sel_Set),//mic2差分输入控件开关
SOC_ENUM_EXT("Audio_ADC_3_Sel", Audio_UL_Enum[7], Audio_ADC3_Sel_Get, Audio_ADC3_Sel_Set),//不支持
SOC_ENUM_EXT("Audio_ADC_4_Sel", Audio_UL_Enum[8], Audio_ADC4_Sel_Get, Audio_ADC4_Sel_Set),//不支持
SOC_ENUM_EXT("Audio_PGA1_Setting", Audio_UL_Enum[9], Audio_PGA1_Get, Audio_PGA1_Set),//mic左PGA控件开关,从可编程增益放大器(PGA),是一种通用性很强的放大器,其放大倍数可以根据需要用程序进行控制,采用这种放大器,可通过程序调节放大倍数,使A/D转换器满量程信号达到均一化,因而大大提高测量精度。
SOC_ENUM_EXT("Audio_PGA2_Setting", Audio_UL_Enum[10], Audio_PGA2_Get, Audio_PGA2_Set),//mic右PGA控件开关
SOC_ENUM_EXT("Audio_PGA3_Setting", Audio_UL_Enum[11], Audio_PGA3_Get, Audio_PGA3_Set),//不支持
SOC_ENUM_EXT("Audio_PGA4_Setting", Audio_UL_Enum[12], Audio_PGA4_Get, Audio_PGA4_Set),//不支持
/////////////////////////////////////////////////////////
/*
static int Audio_MicSource1_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_warn("Audio_MicSource1_Get = %d\n",
mCodec_data->mAudio_Ana_Mux[AUDIO_MICSOURCE_MUX_IN_1]);
ucontrol->value.integer.value[0] = mCodec_data->mAudio_Ana_Mux[AUDIO_MICSOURCE_MUX_IN_1];
return 0;
}
static int Audio_MicSource1_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
/* 6752 used for ADC1 Mic source selection, "ADC1" is main_mic, "ADC2" is headset_mic */
int index = 0;
pr_warn("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(Pmic_Digital_Mux)) {// Pmic_Digital_Mux[] = { "ADC1", "ADC2", "ADC3", "ADC4" };
pr_err("return -EINVAL\n");
return -EINVAL;
}
index = ucontrol->value.integer.value[0];
pr_warn("%s() index = %d done\n", __func__, index);
mCodec_data->mAudio_Ana_Mux[AUDIO_MICSOURCE_MUX_IN_1] = ucontrol->value.integer.value[0];
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_MicSource1_Setting", Audio_UL_Enum[13], Audio_MicSource1_Get,
Audio_MicSource1_Set),//采集音源mic控件设置,无论从phone mic还是从earphone mic传来的音源,mt6737都只从ADC1进行采集
SOC_ENUM_EXT("Audio_MicSource2_Setting", Audio_UL_Enum[14], Audio_MicSource2_Get,
Audio_MicSource2_Set),//不支持
SOC_ENUM_EXT("Audio_MicSource3_Setting", Audio_UL_Enum[15], Audio_MicSource3_Get,
Audio_MicSource3_Set),//不支持
SOC_ENUM_EXT("Audio_MicSource4_Setting", Audio_UL_Enum[16], Audio_MicSource4_Get,
Audio_MicSource4_Set),//不支持
/////////////////////////////////////////////////////////
/*
Mic ACC/DCC Mode Setting
static int Audio_Mic1_Mode_Select_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s() mAudio_Analog_Mic1_mode = %d\n", __func__, mAudio_Analog_Mic1_mode);
ucontrol->value.integer.value[0] = mAudio_Analog_Mic1_mode;
return 0;
}
static int Audio_Mic1_Mode_Select_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(Audio_AnalogMic_Mode)) {
//Audio_AnalogMic_Mode[] = {"ACCMODE", "DCCMODE", "DMIC", "DCCECMDIFFMODE", "DCCECMSINGLEMODE" };
pr_err("return -EINVAL\n");
return -EINVAL;
}
mAudio_Analog_Mic1_mode = ucontrol->value.integer.value[0];//设置mic模式ACC
pr_warn("%s() mAudio_Analog_Mic1_mode = %d\n", __func__, mAudio_Analog_Mic1_mode);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_MIC1_Mode_Select", Audio_UL_Enum[17], Audio_Mic1_Mode_Select_Get,
Audio_Mic1_Mode_Select_Set),//mic1模式设置控件开关
SOC_ENUM_EXT("Audio_MIC2_Mode_Select", Audio_UL_Enum[18], Audio_Mic2_Mode_Select_Get,
Audio_Mic2_Mode_Select_Set),//mic2模式设置控件开关
SOC_ENUM_EXT("Audio_MIC3_Mode_Select", Audio_UL_Enum[19], Audio_Mic3_Mode_Select_Get,
Audio_Mic3_Mode_Select_Set),//mic3模式设置控件开关
SOC_ENUM_EXT("Audio_MIC4_Mode_Select", Audio_UL_Enum[20], Audio_Mic4_Mode_Select_Get,
Audio_Mic4_Mode_Select_Set),//mic4模式设置控件开关
/////////////////////////////////////////////////////////
/*
static int Audio_Adc_Power_Mode_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s() = %d\n", __func__, mAdc_Power_Mode);
ucontrol->value.integer.value[0] = mAdc_Power_Mode;
return 0;
}
static int Audio_Adc_Power_Mode_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(ADC_power_mode)) {
//static const char * const ADC_power_mode[] = { "normal", "lowpower" };
pr_err("return -EINVAL\n");
return -EINVAL;
}
mAdc_Power_Mode = ucontrol->value.integer.value[0];//设置mic adc供电模式
pr_warn("%s() mAdc_Power_Mode = %d\n", __func__, mAdc_Power_Mode);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_Mic_Power_Mode", Audio_UL_Enum[21], Audio_Adc_Power_Mode_Get,
Audio_Adc_Power_Mode_Set),//mic adc供电模式设置控件开关
/////////////////////////////////////////////////////////
/*
//VOW是一个可以通过3G/4G和WIFI联网的头戴式耳机
static int Audio_Vow_ADC_Func_Switch_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s() = %d\n", __func__, mAudio_Vow_Analog_Func_Enable);
ucontrol->value.integer.value[0] = mAudio_Vow_Analog_Func_Enable;
return 0;
}
static int Audio_Vow_ADC_Func_Switch_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(Audio_VOW_ADC_Function)) {//Audio_VOW_ADC_Function[] = { "Off", "On" }
pr_err("return -EINVAL\n");
return -EINVAL;
}
if (ucontrol->value.integer.value[0])
TurnOnVOWADcPowerACC(mAudio_VOW_Mic_type, true);
else
TurnOnVOWADcPowerACC(mAudio_VOW_Mic_type, false);
mAudio_Vow_Analog_Func_Enable = ucontrol->value.integer.value[0];
pr_warn("%s() mAudio_Vow_Analog_Func_Enable = %d\n", __func__,
mAudio_Vow_Analog_Func_Enable);
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_Vow_ADC_Func_Switch", Audio_UL_Enum[22], Audio_Vow_ADC_Func_Switch_Get,
Audio_Vow_ADC_Func_Switch_Set),//vow耳机控件开关
SOC_ENUM_EXT("Audio_Preamp2_Switch", Audio_UL_Enum[23], Audio_PreAmp2_Get,
Audio_PreAmp2_Set),//mic前置差分放大器2控制开关
SOC_ENUM_EXT("Audio_Vow_Digital_Func_Switch", Audio_UL_Enum[24],
Audio_Vow_Digital_Func_Switch_Get, Audio_Vow_Digital_Func_Switch_Set),
//下面都是vow耳机,暂不分析
SOC_ENUM_EXT("Audio_Vow_MIC_Type_Select", Audio_UL_Enum[25], Audio_Vow_MIC_Type_Select_Get,
Audio_Vow_MIC_Type_Select_Set),
SOC_SINGLE_EXT("Audio VOWCFG0 Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_Cfg0_Get,
Audio_Vow_Cfg0_Set),
SOC_SINGLE_EXT("Audio VOWCFG1 Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_Cfg1_Get,
Audio_Vow_Cfg1_Set),
SOC_SINGLE_EXT("Audio VOWCFG2 Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_Cfg2_Get,
Audio_Vow_Cfg2_Set),
SOC_SINGLE_EXT("Audio VOWCFG3 Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_Cfg3_Get,
Audio_Vow_Cfg3_Set),
SOC_SINGLE_EXT("Audio VOWCFG4 Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_Cfg4_Get,
Audio_Vow_Cfg4_Set),
SOC_SINGLE_EXT("Audio VOWCFG5 Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_Cfg5_Get,
Audio_Vow_Cfg5_Set),
SOC_SINGLE_EXT("Audio_VOW_State", SND_SOC_NOPM, 0, 0x80000, 0, Audio_Vow_State_Get,
Audio_Vow_State_Set),
};
没有实质功能
snd_soc_add_codec_controls(codec, mt6331_Voice_Switch, ARRAY_SIZE(mt6331_Voice_Switch));
static const struct snd_kcontrol_new mt6331_Voice_Switch[] = {
/* SOC_DAPM_ENUM_EXT("Voice Mux", Audio_DL_Enum[10], Voice_Mux_Get, Voice_Mux_Set), */
};
pmic测试控件
snd_soc_add_codec_controls(codec, mt6331_pmic_Test_controls,
ARRAY_SIZE(mt6331_pmic_Test_controls));
static const struct snd_kcontrol_new mt6331_pmic_Test_controls[] = {
SOC_ENUM_EXT("SineTable_DAC_HP", Pmic_Test_Enum[0], SineTable_DAC_HP_Get,
SineTable_DAC_HP_Set),
SOC_ENUM_EXT("DAC_LOOP_DAC_HS", Pmic_Test_Enum[1], ADC_LOOP_DAC_HS_Get,
ADC_LOOP_DAC_HS_Set),
SOC_ENUM_EXT("DAC_LOOP_DAC_HP", Pmic_Test_Enum[2], ADC_LOOP_DAC_HP_Get,
ADC_LOOP_DAC_HP_Set),
SOC_ENUM_EXT("Voice_Call_DAC_DAC_HS", Pmic_Test_Enum[3], Voice_Call_DAC_DAC_HS_Get,
Voice_Call_DAC_DAC_HS_Set),
SOC_ENUM_EXT("SineTable_UL2", Pmic_Test_Enum[4], SineTable_UL2_Get, SineTable_UL2_Set),
SOC_ENUM_EXT("Pmic_Loopback", Pmic_Test_Enum[5], Pmic_Loopback_Get, Pmic_Loopback_Set),
};
#ifdef CONFIG_MTK_SPEAKER
snd_soc_add_codec_controls(codec, mt6331_snd_Speaker_controls,
ARRAY_SIZE(mt6331_snd_Speaker_controls));
#endif
static const struct snd_kcontrol_new mt6331_snd_Speaker_controls[] = {
/////////////////////////////////////////////////////////
/*
static int Audio_Speaker_Class_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
mutex_lock(&Ana_Ctrl_Mutex);
Speaker_mode = ucontrol->value.integer.value[0];
mutex_unlock(&Ana_Ctrl_Mutex);
return 0;
}
static int Audio_Speaker_Class_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = Speaker_mode;//Speaker_mode = AUDIO_SPEAKER_MODE_AB,内置PA为AB类功放
return 0;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_Speaker_class_Switch", Audio_Speaker_Enum[0], Audio_Speaker_Class_Get,
Audio_Speaker_Class_Set),//设置PA为AB类功放
SOC_ENUM_EXT("Audio_Speaker_PGA_gain", Audio_Speaker_Enum[1], Audio_Speaker_Pga_Gain_Get,
Audio_Speaker_Pga_Gain_Set),//设置gain(增益调节)为1db,它是用来调节输入声信号的放大量
/////////////////////////////////////////////////////////
/*
static int Audio_Speaker_OcFlag_Get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
mSpeaker_Ocflag = GetSpeakerOcFlag();
ucontrol->value.integer.value[0] = mSpeaker_Ocflag;
return 0;
}
static int Audio_Speaker_OcFlag_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_warn("%s is not support setting\n", __func__);
return 0;
}
//根据下图配置
bool GetSpeakerOcFlag(void)
{
unsigned int OCregister = 0;
unsigned int bitmask = 1;
bool DmodeFlag = false;
bool ABmodeFlag = false;
bool OCFlag = false;
Ana_Set_Reg(TOP_CKPDN_CON2_CLR, 0x3, 0xffff);
OCregister = Ana_Get_Reg(SPK_CON6);
DmodeFlag = OCregister & (bitmask << 14); /* ; no.14 bit is SPK_D_OC_L_DEG */
ABmodeFlag = OCregister & (bitmask << 15); /* ; no.15 bit is SPK_AB_OC_L_DEG */
pr_warn("OCregister = %d\n", OCregister);
OCFlag = (DmodeFlag | ABmodeFlag);
return OCFlag;
}
*/
/////////////////////////////////////////////////////////
SOC_ENUM_EXT("Audio_Speaker_OC_Falg", Audio_Speaker_Enum[2], Audio_Speaker_OcFlag_Get,
Audio_Speaker_OcFlag_Set),//AB/D类功放选择标志控件
//PA电流检测
SOC_ENUM_EXT("Audio_Speaker_CurrentSensing", Audio_Speaker_Enum[3],
Audio_Speaker_Current_Sensing_Get, Audio_Speaker_Current_Sensing_Set),
SOC_ENUM_EXT("Audio_Speaker_CurrentPeakDetector", Audio_Speaker_Enum[4],
Audio_Speaker_Current_Sensing_Peak_Detector_Get,
Audio_Speaker_Current_Sensing_Peak_Detector_Set),
};
snd_soc_add_codec_controls(codec, Audio_snd_auxadc_controls,
ARRAY_SIZE(Audio_snd_auxadc_controls));
static const struct snd_kcontrol_new Audio_snd_auxadc_controls[] = {
SOC_SINGLE_EXT("Audio AUXADC Data", SND_SOC_NOPM, 0, 0x80000, 0, Audio_AuxAdcData_Get,
Audio_AuxAdcData_Set),//audio adc数据控件
};
static int Audio_AuxAdcData_Get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
#ifdef CONFIG_MTK_SPEAKER
ucontrol->value.integer.value[0] = Audio_AuxAdcData_Get_ext();
//PMIC_IMM_GetOneChannelValue(AUX_ICLASSAB_AP, 1, 0);
#else
ucontrol->value.integer.value[0] = 0;
#endif
pr_warn("%s dMax = 0x%lx\n", __func__, ucontrol->value.integer.value[0]);
return 0;
}
static int Audio_AuxAdcData_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
dAuxAdcChannel = ucontrol->value.integer.value[0];
pr_warn("%s dAuxAdcChannel = 0x%x\n", __func__, dAuxAdcChannel);
return 0;
}
//codec初始化
static void mt6331_codec_init_reg(struct snd_soc_codec *codec)
{
pr_warn("%s\n", __func__);
Ana_Set_Reg(TOP_CLKSQ, 0x0, 0x0001);
/* Disable CLKSQ 26MHz */
Ana_Set_Reg(AUDDEC_ANA_CON8, 0x0002, 0x0002);
/* disable AUDGLB */
Ana_Set_Reg(TOP_CKPDN_CON0_SET, 0x3800, 0x3800);
/* Turn off AUDNCP_CLKDIV engine clock,Turn off AUD 26M */
Ana_Set_Reg(AUDDEC_ANA_CON0, 0xe000, 0xe000);
/* Disable HeadphoneL/HeadphoneR/voice short circuit protection */
Ana_Set_Reg(AUDENC_ANA_CON9, 0x0000, 0x0010);
/* power off mic bias1 */
Ana_Set_Reg(AFE_PMIC_NEWIF_CFG2, 0x8000, 0x8000);
/* Reverse the PMIC clock*/
}
void InitCodecDefault(void)
{
pr_warn("%s\n", __func__);
//设置mic1音量最大值
mCodec_data->mAudio_Ana_Volume[AUDIO_ANALOG_VOLUME_MICAMP1] = 3;
//设置mic2音量最大值
mCodec_data->mAudio_Ana_Volume[AUDIO_ANALOG_VOLUME_MICAMP2] = 3;
mCodec_data->mAudio_Ana_Volume[AUDIO_ANALOG_VOLUME_MICAMP3] = 3;
mCodec_data->mAudio_Ana_Volume[AUDIO_ANALOG_VOLUME_MICAMP4] = 3;
//耳机音量右声道输出最大值
mCodec_data->mAudio_Ana_Volume[AUDIO_ANALOG_VOLUME_HPOUTR] = 8;
mCodec_data->mAudio_Ana_Volume[AUDIO_ANALOG_VOLUME_HPOUTR] = 8;
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC1] =
AUDIO_ANALOG_AUDIOANALOG_INPUT_PREAMP;
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC2] =
AUDIO_ANALOG_AUDIOANALOG_INPUT_PREAMP;
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC3] =
AUDIO_ANALOG_AUDIOANALOG_INPUT_PREAMP;
mCodec_data->mAudio_Ana_Mux[AUDIO_ANALOG_MUX_IN_MIC4] =
AUDIO_ANALOG_AUDIOANALOG_INPUT_PREAMP;
}
static struct snd_soc_dai_driver mtk_6331_dai_codecs[] = {
{
.name = MT_SOC_CODEC_TXDAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_DL1_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_RXDAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.capture = {
.stream_name = MT_SOC_UL1_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_TDMRX_DAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.capture = {
.stream_name = MT_SOC_TDM_CAPTURE_STREAM_NAME,
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE |
SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE |
SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE |
SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE),
},
},
{
.name = MT_SOC_CODEC_I2S0TXDAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
}
},
{
.name = MT_SOC_CODEC_VOICE_MD1DAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_VOICE_MD1_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
.capture = {
.stream_name = MT_SOC_VOICE_MD1_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_VOICE_MD2DAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_VOICE_MD2_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
.capture = {
.stream_name = MT_SOC_VOICE_MD2_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_FMI2S2RXDAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_FM_I2S2_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
.capture = {
.stream_name = MT_SOC_FM_I2S2_RECORD_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_FMMRGTXDAI_DUMMY_DAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_FM_MRGTX_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_ULDLLOOPBACK_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_ULDLLOOPBACK_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
.capture = {
.stream_name = MT_SOC_ULDLLOOPBACK_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_STUB_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_ROUTING_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_RXDAI2_NAME,
.capture = {
.stream_name = MT_SOC_UL1DATA2_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_MRGRX_DAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_MRGRX_STREAM_NAME,
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
.capture = {
.stream_name = MT_SOC_MRGRX_STREAM_NAME,
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_HP_IMPEDANCE_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_HP_IMPEDANCE_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_FM_I2S_DAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_FM_I2S_PLAYBACK_STREAM_NAME,
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
{
.name = MT_SOC_CODEC_TXDAI2_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_DL2_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
},
},
};
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_machine.c
module_init(mt_soc_snd_init)->mt_soc_snd_init(void)
static int __init mt_soc_snd_init(void)
{
int ret;
struct snd_soc_card *card = &snd_soc_card_mt;
pr_debug("mt_soc_snd_init card addr = %p\n", card);
mt_snd_device = platform_device_alloc("soc-audio", -1);
if (!mt_snd_device) {
pr_err("mt6589_probe platform_device_alloc fail\n");
return -ENOMEM;
}
platform_set_drvdata(mt_snd_device, &snd_soc_card_mt);
ret = platform_device_add(mt_snd_device);
if (ret != 0) {
pr_err("mt_soc_snd_init goto put_device fail\n");
goto put_device;
}
pr_debug("mt_soc_snd_init dai_link = %p\n", snd_soc_card_mt.dai_link);
/* create debug file */
mt_sco_audio_debugfs = debugfs_create_file(DEBUG_FS_NAME,
S_IFREG | S_IRUGO, NULL, (void *)DEBUG_FS_NAME,
&mtaudio_debug_ops);
/* create analog debug file */
mt_sco_audio_debugfs = debugfs_create_file(DEBUG_ANA_FS_NAME,
S_IFREG | S_IRUGO, NULL,
(void *)DEBUG_ANA_FS_NAME,
&mtaudio_ana_debug_ops);
return 0;
put_device:
platform_device_put(mt_snd_device);
return ret;
}
下面开始对mt_soc_snd_init函数进行分析。
snd_soc_card_mt通过mt_soc_snd_init函数传入soc_probe函数。
.dai_link = mt_soc_dai_common是struct snd_soc_card数据结构的核心。
static struct snd_soc_card snd_soc_card_mt = {
.name = "mt-snd-card",
.dai_link = mt_soc_dai_common,
.num_links = ARRAY_SIZE(mt_soc_dai_common),
.controls = mt_soc_controls,
.num_controls = ARRAY_SIZE(mt_soc_controls),
};
mt_soc_dai_common通过stream_name 、cpu_dai_name 、platform_name 、codec_dai_name 、codec_name名字绑定在一起
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link mt_soc_dai_common[] = {
/* FrontEnd DAI Links */
{
.name = "MultiMedia1",
.stream_name = MT_SOC_DL1_STREAM_NAME,
.cpu_dai_name = MT_SOC_DL1DAI_NAME,
.platform_name = MT_SOC_DL1_PCM,
.codec_dai_name = MT_SOC_CODEC_TXDAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "MultiMedia2",
.stream_name = MT_SOC_UL1_STREAM_NAME,
.cpu_dai_name = MT_SOC_UL1DAI_NAME,
.platform_name = MT_SOC_UL1_PCM,
.codec_dai_name = MT_SOC_CODEC_RXDAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "Voice_MD1",
.stream_name = MT_SOC_VOICE_MD1_STREAM_NAME,
.cpu_dai_name = MT_SOC_VOICE_MD1_NAME,
.platform_name = MT_SOC_VOICE_MD1,
.codec_dai_name = MT_SOC_CODEC_VOICE_MD1DAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "HDMI_OUT",
.stream_name = MT_SOC_HDMI_STREAM_NAME,
.cpu_dai_name = MT_SOC_HDMI_NAME,
.platform_name = MT_SOC_HDMI_PCM,
.codec_dai_name = MT_SOC_CODEC_HDMI_DUMMY_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "ULDLOOPBACK",
.stream_name = MT_SOC_ULDLLOOPBACK_STREAM_NAME,
.cpu_dai_name = MT_SOC_ULDLLOOPBACK_NAME,
.platform_name = MT_SOC_ULDLLOOPBACK_PCM,
.codec_dai_name = MT_SOC_CODEC_ULDLLOOPBACK_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "I2S0OUTPUT",
.stream_name = MT_SOC_I2S0_STREAM_NAME,
.cpu_dai_name = MT_SOC_I2S0_NAME,
.platform_name = MT_SOC_I2S0_PCM,
.codec_dai_name = MT_SOC_CODEC_I2S0_DUMMY_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "MRGRX",
.stream_name = MT_SOC_MRGRX_STREAM_NAME,
.cpu_dai_name = MT_SOC_MRGRX_NAME,
.platform_name = MT_SOC_MRGRX_PCM,
.codec_dai_name = MT_SOC_CODEC_MRGRX_DAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "MRGRXCAPTURE",
.stream_name = MT_SOC_MRGRX_CAPTURE_STREAM_NAME,
.cpu_dai_name = MT_SOC_MRGRX_NAME,
.platform_name = MT_SOC_MRGRX_AWB_PCM,
.codec_dai_name = MT_SOC_CODEC_MRGRX_DUMMY_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "I2S0DL1OUTPUT",
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.cpu_dai_name = MT_SOC_I2S0DL1_NAME,
.platform_name = MT_SOC_I2S0DL1_PCM,
.codec_dai_name = MT_SOC_CODEC_I2S0TXDAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "DL1AWBCAPTURE",
.stream_name = MT_SOC_DL1_AWB_RECORD_STREAM_NAME,
.cpu_dai_name = MT_SOC_DL1AWB_NAME,
.platform_name = MT_SOC_DL1_AWB_PCM,
.codec_dai_name = MT_SOC_CODEC_DL1AWBDAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "Voice_MD1_BT",
.stream_name = MT_SOC_VOICE_MD1_BT_STREAM_NAME,
.cpu_dai_name = MT_SOC_VOICE_MD1_BT_NAME,
.platform_name = MT_SOC_VOICE_MD1_BT,
.codec_dai_name = MT_SOC_CODEC_VOICE_MD1_BTDAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "VOIP_CALL_BT_PLAYBACK",
.stream_name = MT_SOC_VOIP_BT_OUT_STREAM_NAME,
.cpu_dai_name = MT_SOC_VOIP_CALL_BT_OUT_NAME,
.platform_name = MT_SOC_VOIP_BT_OUT,
.codec_dai_name = MT_SOC_CODEC_VOIPCALLBTOUTDAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "VOIP_CALL_BT_CAPTURE",
.stream_name = MT_SOC_VOIP_BT_IN_STREAM_NAME,
.cpu_dai_name = MT_SOC_VOIP_CALL_BT_IN_NAME,
.platform_name = MT_SOC_VOIP_BT_IN,
.codec_dai_name = MT_SOC_CODEC_VOIPCALLBTINDAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "TDM_Debug_CAPTURE",
.stream_name = MT_SOC_TDM_CAPTURE_STREAM_NAME,
.cpu_dai_name = MT_SOC_TDMRX_NAME,
.platform_name = MT_SOC_TDMRX_PCM,
.codec_dai_name = MT_SOC_CODEC_TDMRX_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "FM_MRG_TX",
.stream_name = MT_SOC_FM_MRGTX_STREAM_NAME,
.cpu_dai_name = MT_SOC_FM_MRGTX_NAME,
.platform_name = MT_SOC_FM_MRGTX_PCM,
.codec_dai_name = MT_SOC_CODEC_FMMRGTXDAI_DUMMY_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "MultiMedia3",
.stream_name = MT_SOC_UL1DATA2_STREAM_NAME,
.cpu_dai_name = MT_SOC_UL2DAI_NAME,
.platform_name = MT_SOC_UL2_PCM,
.codec_dai_name = MT_SOC_CODEC_RXDAI2_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "I2S0_AWB_CAPTURE",
.stream_name = MT_SOC_I2S0AWB_STREAM_NAME,
.cpu_dai_name = MT_SOC_I2S0AWBDAI_NAME,
.platform_name = MT_SOC_I2S0_AWB_PCM,
.codec_dai_name = MT_SOC_CODEC_I2S0AWB_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "Voice_MD2",
.stream_name = MT_SOC_VOICE_MD2_STREAM_NAME,
.cpu_dai_name = MT_SOC_VOICE_MD2_NAME,
.platform_name = MT_SOC_VOICE_MD2,
.codec_dai_name = MT_SOC_CODEC_VOICE_MD2DAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "PLATOFRM_CONTROL",
.stream_name = MT_SOC_ROUTING_STREAM_NAME,
.cpu_dai_name = MT_SOC_ROUTING_DAI_NAME,
.platform_name = MT_SOC_ROUTING_PCM,
.codec_dai_name = MT_SOC_CODEC_DUMMY_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init2,
.ops = &mtmachine_audio_ops2,
},
{
.name = "Voice_MD2_BT",
.stream_name = MT_SOC_VOICE_MD2_BT_STREAM_NAME,
.cpu_dai_name = MT_SOC_VOICE_MD2_BT_NAME,
.platform_name = MT_SOC_VOICE_MD2_BT,
.codec_dai_name = MT_SOC_CODEC_VOICE_MD2_BTDAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "HP_IMPEDANCE",
.stream_name = MT_SOC_HP_IMPEDANCE_STREAM_NAME,
.cpu_dai_name = MT_SOC_HP_IMPEDANCE_NAME,
.platform_name = MT_SOC_HP_IMPEDANCE_PCM,
.codec_dai_name = MT_SOC_CODEC_HP_IMPEDANCE_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "FM_I2S_RX_Playback",
.stream_name = MT_SOC_FM_I2S_PLAYBACK_STREAM_NAME,
.cpu_dai_name = MT_SOC_FM_I2S_NAME,
.platform_name = MT_SOC_FM_I2S_PCM,
.codec_dai_name = MT_SOC_CODEC_FM_I2S_DAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "FM_I2S_RX_Capture",
.stream_name = MT_SOC_FM_I2S_CAPTURE_STREAM_NAME,
.cpu_dai_name = MT_SOC_FM_I2S_NAME,
.platform_name = MT_SOC_FM_I2S_AWB_PCM,
.codec_dai_name = MT_SOC_CODEC_FM_I2S_DUMMY_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
{
.name = "OFFLOAD_GDMA_OUT",
.stream_name = MT_SOC_OFFLOAD_GDMA_STREAM_NAME,
.cpu_dai_name = MT_SOC_OFFLOAD_GDMA_NAME,
.platform_name = MT_SOC_OFFLOAD_GDMA_PCM,
.codec_dai_name = MT_SOC_CODEC_OFFLOAD_GDMA_DAI_NAME,
.codec_name = MT_SOC_CODEC_DUMMY_NAME,
.init = mt_soc_audio_init,
/* .ops = &mt_machine_audio_ops, */
.compr_ops = &mt_machine_audio_compr_ops,
},
{
.name = "MultiMedia_DL2",
.stream_name = MT_SOC_DL2_STREAM_NAME,
.cpu_dai_name = MT_SOC_DL2DAI_NAME,
.platform_name = MT_SOC_DL2_PCM,
.codec_dai_name = MT_SOC_CODEC_TXDAI2_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
};
I2S低抖动功能控件
static const struct snd_kcontrol_new mt_soc_controls[] = {
SOC_ENUM_EXT("I2S low Jitter function", mt_soc_machine_enum[0], mt6735_get_lowjitter,
mt6735_set_lowjitter),
};
static int mt6735_get_lowjitter(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_debug("%s: mt_soc_lowjitter_control = %d\n", __func__, mt_soc_lowjitter_control);
ucontrol->value.integer.value[0] = mt_soc_lowjitter_control;
return 0;
}
static int mt6735_set_lowjitter(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
pr_debug("%s()\n", __func__);
mt_soc_lowjitter_control = ucontrol->value.integer.value[0];
return 0;
}
platform_set_drvdata(mt_snd_device, &snd_soc_card_mt);//将snd_soc_card_mt结构体保存在平台设备mt_snd_device中
ret = platform_device_add(mt_snd_device);//将设备mt_snd_device挂载到platform总线上
下面到kernel-3.18/sound/soc/soc-core.c文件
soc_probe函数本身很简单,Snd_soc_card这个结构体通过函数platform_get_drvdata从平台设备数据结构取取出来。然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
//Snd_soc_card这个结构体通过函数platform_get_drvdata从平台设备数据结构取取出来。
/*
* no card, so machine driver should be registering card
* we should not be here in that case so ret error
*/
if (!card)
return -EINVAL;
dev_warn(&pdev->dev,
"ASoC: machine %s should use snd_soc_register_card()\n",
card->name);
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
return snd_soc_register_card(card);//将取出值结构体Snd_soc_card传给snd_soc_register_card
}
下面进入snd_soc_register_card(card)
/**
* snd_soc_register_card - Register a card with the ASoC core
*
* @card: Card to register
*
*/
int snd_soc_register_card(struct snd_soc_card *card)
{
int i, j, ret;
if (!card->name || !card->dev)
return -EINVAL;
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = snd_soc_init_multicodec(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init multicodec\n");
return ret;
}
for (j = 0; j < link->num_codecs; j++) {
/*
* Codec must be specified by 1 of name or OF node,
* not both or neither.
*/
if (!!link->codecs[j].name ==
!!link->codecs[j].of_node) {
dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/* Codec DAI name must be specified */
if (!link->codecs[j].dai_name) {
dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n",
link->name);
return -EINVAL;
}
}
/*
* Platform may be specified by either name or OF node, but
* can be left unspecified, and a dummy platform will be used.
*/
if (link->platform_name && link->platform_of_node) {
dev_err(card->dev,
"ASoC: Both platform name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/*
* CPU device may be specified by either name or OF node, but
* can be left unspecified, and will be matched based on DAI
* name alone..
*/
if (link->cpu_name && link->cpu_of_node) {
dev_err(card->dev,
"ASoC: Neither/both cpu name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/*
* At least one of CPU DAI name or CPU device name/node must be
* specified
*/
if (!link->cpu_dai_name &&
!(link->cpu_name || link->cpu_of_node)) {
dev_err(card->dev,
"ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
}
dev_set_drvdata(card->dev, card);
snd_soc_initialize_card_lists(card);
soc_init_card_debugfs(card);
card->rtd = devm_kzalloc(card->dev,
sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;
card->num_rtd = 0;
card->rtd_aux = &card->rtd[card->num_links];
for (i = 0; i < card->num_links; i++) {
card->rtd[i].card = card;
> card->rtd[i].dai_link = &card->dai_link[i];
card->rtd[i].codec_dais = devm_kzalloc(card->dev,
sizeof(struct snd_soc_dai *) *
(card->rtd[i].dai_link->num_codecs),
> GFP_KERNEL);
if (card->rtd[i].codec_dais == NULL)
return -ENOMEM;
}
for (i = 0; i < card->num_aux_devs; i++)
card->rtd_aux[i].card = card;
INIT_LIST_HEAD(&card->dapm_dirty);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
ret = snd_soc_instantiate_card(card);
if (ret != 0)
soc_cleanup_card_debugfs(card);
/* deactivate pins to sleep state */
for (i = 0; i < card->num_rtd; i++) {
struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int j;
for (j = 0; j < rtd->num_codecs; j++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
if (!codec_dai->active)
pinctrl_pm_select_sleep_state(codec_dai->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
}
return ret;
}
下面来进行分析snd_soc_register_card.
static struct snd_soc_card snd_soc_card_mt = {
.name = "mt-snd-card",
.dai_link = mt_soc_dai_common,
.num_links = ARRAY_SIZE(mt_soc_dai_common),
.controls = mt_soc_controls,
.num_controls = ARRAY_SIZE(mt_soc_controls),
};
static struct snd_soc_dai_link mt_soc_dai_common[] = {
/* FrontEnd DAI Links */
{
.name = "MultiMedia1",
.stream_name = MT_SOC_DL1_STREAM_NAME,
.cpu_dai_name = MT_SOC_DL1DAI_NAME,
.platform_name = MT_SOC_DL1_PCM,
.codec_dai_name = MT_SOC_CODEC_TXDAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
....
}
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = snd_soc_init_multicodec(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init multicodec\n");
return ret;
}
上面通过for循环将 snd_soc_card_mt 结构体成员mt_soc_dai_common数组元素一一传入 snd_soc_init_multicodec函数里。下面进行分析这个函数。
static struct snd_soc_dai_link mt_soc_dai_common[] = {
/* FrontEnd DAI Links */
{
.name = "MultiMedia1",
.stream_name = MT_SOC_DL1_STREAM_NAME,
.cpu_dai_name = MT_SOC_DL1DAI_NAME,
.platform_name = MT_SOC_DL1_PCM,
.codec_dai_name = MT_SOC_CODEC_TXDAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
.init = mt_soc_audio_init,
.ops = &mt_machine_audio_ops,
},
.........
}
static int snd_soc_init_multicodec(struct snd_soc_card *card,
struct snd_soc_dai_link *dai_link)
{
/* Legacy codec/codec_dai link is a single entry in multicodec */
if (dai_link->codec_name || dai_link->codec_of_node ||
dai_link->codec_dai_name) {
dai_link->num_codecs = 1;
dai_link->codecs = devm_kzalloc(card->dev,
sizeof(struct snd_soc_dai_link_component),
GFP_KERNEL);
if (!dai_link->codecs)
return -ENOMEM;
dai_link->codecs[0].name = dai_link->codec_name;
dai_link->codecs[0].of_node = dai_link->codec_of_node;
dai_link->codecs[0].dai_name = dai_link->codec_dai_name;
}
if (!dai_link->codecs) {
dev_err(card->dev, "ASoC: DAI link has no CODECs\n");
return -EINVAL;
}
return 0;
}
这是一个链表,此函数将codec_name、codec_of_node、codec_dai_name取出来保存在snd_soc_dai_link结构体元素codecs[0]中
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = snd_soc_init_multicodec(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init multicodec\n");
return ret;
}
这里就是要将snd_soc_card_mt结构体成员mt_soc_dai_common数组所有成员的codec_name、codec_of_node、codec_dai_name取出来保存在snd_soc_dai_link结构体元素codecs[0]中
dev_set_drvdata(card->dev, card);//用来存储驱动中要用到的私有数据card
snd_soc_initialize_card_lists(card);
static inline void snd_soc_initialize_card_lists(struct snd_soc_card *card)
{
INIT_LIST_HEAD(&card->codec_dev_list);
INIT_LIST_HEAD(&card->widgets);
INIT_LIST_HEAD(&card->paths);
INIT_LIST_HEAD(&card->dapm_list);
}
card->rtd = devm_kzalloc(card->dev,
sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;
card->num_rtd = 0;
card->rtd_aux = &card->rtd[card->num_links];
for (i = 0; i < card->num_links; i++) {
card->rtd[i].card = card;
card->rtd[i].dai_link = &card->dai_link[i];
card->rtd[i].codec_dais = devm_kzalloc(card->dev,
sizeof(struct snd_soc_dai *) *
(card->rtd[i].dai_link->num_codecs),
GFP_KERNEL);
if (card->rtd[i].codec_dais == NULL)
return -ENOMEM;
}
for (i = 0; i < card->num_aux_devs; i++)
card->rtd_aux[i].card = card;
INIT_LIST_HEAD(&card->dapm_dirty);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
为snd_soc_pcm_runtime结构体数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中.
struct snd_soc_card {
…
struct snd_soc_pcm_runtime *rtd;
//指向struct snd_soc_pcm_runtime实体,共有
ARRAY_SIZE(mt_soc_dai_common)=23个.
…
}
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
struct snd_soc_dai_link *dai_link;
int ret, i, order, dai_fmt;
mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, i);
if (ret != 0)
goto base_error;
}
/* bind aux_devs too */
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_bind_aux_dev(card, i);
if (ret != 0)
goto base_error;
}
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
ret = snd_soc_init_codec_cache(codec);
if (ret < 0)
goto base_error;
}
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
if (ret < 0) {
dev_err(card->dev,
"ASoC: can't create sound card for card %s: %d\n",
card->name, ret);
goto base_error;
}
card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);
#ifdef CONFIG_DEBUG_FS
snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif
#ifdef CONFIG_PM_SLEEP
/* deferred resume work */
INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif
if (card->dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets);
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_components(card, i, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_dais(card, i, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_probe_aux_dev(card, i);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to add auxiliary devices %d\n",
ret);
goto probe_aux_dev_err;
}
}
snd_soc_dapm_link_dai_widgets(card);
snd_soc_dapm_connect_dai_link_widgets(card);
if (card->controls)
snd_soc_add_card_controls(card, card->controls, card->num_controls);
if (card->dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);
for (i = 0; i < card->num_links; i++) {
struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
dai_link = &card->dai_link[i];
dai_fmt = dai_link->dai_fmt;
if (dai_fmt) {
struct snd_soc_dai **codec_dais = rtd->codec_dais;
int j;
for (j = 0; j < rtd->num_codecs; j++) {
struct snd_soc_dai *codec_dai = codec_dais[j];
ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
if (ret != 0 && ret != -ENOTSUPP)
dev_warn(codec_dai->dev,
"ASoC: Failed to set DAI format: %d\n",
ret);
}
}
/* If this is a regular CPU link there will be a platform */
if (dai_fmt &&
(dai_link->platform_name || dai_link->platform_of_node)) {
ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
dai_fmt);
if (ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].cpu_dai->dev,
"ASoC: Failed to set DAI format: %d\n",
ret);
} else if (dai_fmt) {
/* Flip the polarity for the "CPU" end */
dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
switch (dai_link->dai_fmt &
SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
break;
case SND_SOC_DAIFMT_CBM_CFS:
dai_fmt |= SND_SOC_DAIFMT_CBS_CFM;
break;
case SND_SOC_DAIFMT_CBS_CFM:
dai_fmt |= SND_SOC_DAIFMT_CBM_CFS;
break;
case SND_SOC_DAIFMT_CBS_CFS:
dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
break;
}
ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
dai_fmt);
if (ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].cpu_dai->dev,
"ASoC: Failed to set DAI format: %d\n",
ret);
}
}
snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
"%s", card->name);
snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
"%s", card->long_name ? card->long_name : card->name);
snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),
"%s", card->driver_name ? card->driver_name : card->name);
for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {
switch (card->snd_card->driver[i]) {
case '_':
case '-':
case '\0':
break;
default:
if (!isalnum(card->snd_card->driver[i]))
card->snd_card->driver[i] = '_';
break;
}
}
if (card->late_probe) {
ret = card->late_probe(card);
if (ret < 0) {
dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
}
if (card->fully_routed)
snd_soc_dapm_auto_nc_pins(card);
snd_soc_dapm_new_widgets(card);
ret = snd_card_register(card->snd_card);
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
ret);
goto probe_aux_dev_err;
}
#ifdef CONFIG_SND_SOC_AC97_BUS
/* register any AC97 codecs */
for (i = 0; i < card->num_rtd; i++) {
ret = soc_register_ac97_dai_link(&card->rtd[i]);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to register AC97: %d\n", ret);
while (--i >= 0)
soc_unregister_ac97_dai_link(&card->rtd[i]);
goto probe_aux_dev_err;
}
}
#endif
card->instantiated = 1;
snd_soc_dapm_sync(&card->dapm);
mutex_unlock(&card->mutex);
return 0;
probe_aux_dev_err:
for (i = 0; i < card->num_aux_devs; i++)
soc_remove_aux_dev(card, i);
probe_dai_err:
soc_remove_dai_links(card);
card_probe_error:
if (card->remove)
card->remove(card);
snd_card_free(card->snd_card);
base_error:
mutex_unlock(&card->mutex);
return ret;
}
下面进行分析此函数
ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, i);//假设card->num_links=23,所以for循环会执行23次soc_bind_dai_link函数,而且通过i的变化把所有mt_soc_dai_common中的dai_link遍历到。
if (ret != 0)
goto base_error;
}
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
/*
struct snd_soc_pcm_runtime {// 有23个这样的数据结构对象,列出重要的成员
…
struct snd_soc_dai_link *dai_link;//指struct snd_soc_dai_link mt_soc_dai_common[i](i=0~22)
struct snd_soc_codec *codec;// rtd->codec = codec; from codec_list
struct snd_soc_platform *platform;// rtd->platform = platform; from platform_list
struct snd_soc_dai *codec_dai;// rtd->codec_dai = codec_dai; from dai_list
struct snd_soc_dai *cpu_dai;// rtd->cpu_dai = cpu_dai; from dai_list
…
}
*/
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_dai_link_component *codecs = dai_link->codecs;
struct snd_soc_dai_link_component cpu_dai_component;
struct snd_soc_dai **codec_dais = rtd->codec_dais;
struct snd_soc_platform *platform;
const char *platform_name;
int i;
dev_dbg(card->dev, "ASoC: binding %s at idx %d\n", dai_link->name, num);
cpu_dai_component.name = dai_link->cpu_name;
cpu_dai_component.of_node = dai_link->cpu_of_node;
cpu_dai_component.dai_name = dai_link->cpu_dai_name;
/* snd_soc_find_dai
// Find CPU DAI from registered DAIs
list_for_each_entry(component, &component_list, list) {
if (dlc->of_node && component->dev->of_node != dlc->of_node)
continue;
if (dlc->name && strcmp(component->name, dlc->name))
continue;
list_for_each_entry(dai, &component->dai_list, list) {
if (dlc->dai_name && strcmp(dai->name, dlc->dai_name))
continue;
*/
rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
if (!rtd->cpu_dai) {
dev_err(card->dev, "ASoC: CPU DAI %s not registered\n",
dai_link->cpu_dai_name);
return -EPROBE_DEFER;
}
rtd->num_codecs = dai_link->num_codecs;
/* Find CODEC from registered CODECs */
for (i = 0; i < rtd->num_codecs; i++) {
codec_dais[i] = snd_soc_find_dai(&codecs[i]);
if (!codec_dais[i]) {
dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n",
codecs[i].dai_name);
return -EPROBE_DEFER;
}
}
/* Single codec links expect codec and codec_dai in runtime data */
rtd->codec_dai = codec_dais[0];
rtd->codec = rtd->codec_dai->codec;
/* if there's no platform we match on the empty platform */
platform_name = dai_link->platform_name;
if (!platform_name && !dai_link->platform_of_node)
platform_name = "snd-soc-dummy";
/* find one from the set of registered platforms */
list_for_each_entry(platform, &platform_list, list) {
if (dai_link->platform_of_node) {
if (platform->dev->of_node !=
dai_link->platform_of_node)
continue;
} else {
if (strcmp(platform->component.name, platform_name))
continue;
}
rtd->platform = platform;
}
if (!rtd->platform) {
dev_err(card->dev, "ASoC: platform %s not registered\n",
dai_link->platform_name);
return -EPROBE_DEFER;
}
card->num_rtd++;
return 0;
}
到这里为止,machine就把所需要的codec和platform绑定
声卡下面有多种device,最重要的是pcm和control两类,snd_card_new 会
自动调用snd_ctl_create创建control逻辑设备(controlC0)
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
/**
* snd_card_new - create and initialize a soundcard structure
* @parent: the parent device object
* @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
* @xid: card identification (ASCII string)
* @module: top level module for locking
* @extra_size: allocate this extra size after the main soundcard structure
* @card_ret: the pointer to store the created card instance
*
* Creates and initializes a soundcard structure.
*
* The function allocates snd_card instance via kzalloc with the given
* space for the driver to use freely. The allocated struct is stored
* in the given card_ret pointer.
*
* Return: Zero if successful or a negative error code.
*/
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
struct snd_card *card;
int err;
if (snd_BUG_ON(!card_ret))
return -EINVAL;
*card_ret = NULL;
if (extra_size < 0)
extra_size = 0;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
if (!card)
return -ENOMEM;
if (extra_size > 0)
card->private_data = (char *)card + sizeof(struct snd_card);
if (xid)
strlcpy(card->id, xid, sizeof(card->id));
err = 0;
mutex_lock(&snd_card_mutex);
if (idx < 0) /* first check the matching module-name slot */
idx = get_slot_from_bitmask(idx, module_slot_match, module);
if (idx < 0) /* if not matched, assign an empty slot */
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
if (idx < 0)
err = -ENODEV;
else if (idx < snd_ecards_limit) {
if (test_bit(idx, snd_cards_lock))
err = -EBUSY; /* invalid */
} else if (idx >= SNDRV_CARDS)
err = -ENODEV;
if (err < 0) {
mutex_unlock(&snd_card_mutex);
dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n",
idx, snd_ecards_limit - 1, err);
kfree(card);
return err;
}
set_bit(idx, snd_cards_lock); /* lock it */
if (idx >= snd_ecards_limit)
snd_ecards_limit = idx + 1; /* increase the limit */
mutex_unlock(&snd_card_mutex);
card->dev = parent;
card->number = idx;
card->module = module;
INIT_LIST_HEAD(&card->devices);
init_rwsem(&card->controls_rwsem);
rwlock_init(&card->ctl_files_rwlock);
mutex_init(&card->user_ctl_lock);
INIT_LIST_HEAD(&card->controls);
INIT_LIST_HEAD(&card->ctl_files);
spin_lock_init(&card->files_lock);
INIT_LIST_HEAD(&card->files_list);
#ifdef CONFIG_PM
mutex_init(&card->power_lock);
init_waitqueue_head(&card->power_sleep);
#endif
device_initialize(&card->card_dev);
card->card_dev.parent = parent;
card->card_dev.class = sound_class;
card->card_dev.release = release_card_device;
card->card_dev.groups = card_dev_attr_groups;
err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
if (err < 0)
goto __error;
/* the control interface cannot be accessed from the user space until */
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
/////////////////////////////////////////////////////////////////////
/*
* create control core:
* called from init.c
*/
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}
/////////////////////////////////////////////////////////////////////
err = snd_ctl_create(card);//注册control设备
if (err < 0) {
dev_err(parent, "unable to register control minors\n");
goto __error;
}
err = snd_info_card_create(card);
if (err < 0) {
dev_err(parent, "unable to create card info\n");
goto __error_ctl;
}
*card_ret = card;
return 0;
__error_ctl:
snd_device_free_all(card);
__error:
put_device(&card->card_dev);
return err;
}
//snd_control通过链表挂到snd_card上面。到后面我们会看到snd_card注册的时候会调用每一挂在上面的设备的注册函数,对用snd_control来说,是snd_ctl_dev_register。
snd_ctl_create 创建control逻辑设备
a)调用snd_device_new,传递snd_device_ops ops,这个ops很关键 i.snd_device_new接口把control逻辑设备放在了card->devices里
b)ops中的snd_ctl_dev_register接口,实际会被下边的snd_card_register接口调用到,snd_ctl_dev_register接口调用snd_register_device,传递snd_ctl_f_ops,这个ops就是实际使用到的control设备的文件操作.
/*
* create control core:
* called from init.c
*/
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
int err, cardnum;
char name[16];
if (snd_BUG_ON(!card))
return -ENXIO;
cardnum = card->number;
if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
return -ENXIO;
sprintf(name, "controlC%i", cardnum);
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, name)) < 0)
return err;
return 0;
}
/**
* snd_device_new - create an ALSA device component
* @card: the card instance
* @type: the device type, SNDRV_DEV_XXX
* @device_data: the data pointer of this device
* @ops: the operator table
*
* Creates a new device component for the given data pointer.
* The device will be assigned to the card and managed together
* by the card.
*
* The data pointer plays a role as the identifier, too, so the
* pointer address must be unique and unchanged.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_device_new(struct snd_card *card, enum snd_device_type type,
void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
struct list_head *p;
if (snd_BUG_ON(!card || !device_data || !ops))
return -ENXIO;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
dev_err(card->dev, "Cannot allocate device, type=%d\n", type);
return -ENOMEM;
}
INIT_LIST_HEAD(&dev->list);
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
/* insert the entry in an incrementally sorted list */
list_for_each_prev(p, &card->devices) {
struct snd_device *pdev = list_entry(p, struct snd_device, list);
if ((unsigned int)pdev->type <= (unsigned int)type)
break;
}
list_add(&dev->list, p);
return 0;
}
创建damp控件
if (card->dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets);
/**
* snd_soc_dapm_new_controls - create new dapm controls
* @dapm: DAPM context
* @widget: widget array
* @num: number of widgets
*
* Creates new DAPM controls based upon the templates.
*
* Returns 0 for success else error.
*/
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget,
int num)
{
struct snd_soc_dapm_widget *w;
int i;
int ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = 0; i < num; i++) {
/*
有很多种控件进行选择创建
case snd_soc_dapm_regulator_supply
case snd_soc_dapm_clock_supply
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
case snd_soc_dapm_mux:
case snd_soc_dapm_dai_out:
case snd_soc_dapm_dai_in:
case snd_soc_dapm_adc:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_dac:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
case snd_soc_dapm_input:
case snd_soc_dapm_output:
case snd_soc_dapm_micbias:
case snd_soc_dapm_spk:
case snd_soc_dapm_hp:
case snd_soc_dapm_mic:
case snd_soc_dapm_line:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_clock_supply:
case snd_soc_dapm_kcontrol:
*/
w = snd_soc_dapm_new_control(dapm, widget);
if (!w) {
dev_err(dapm->dev,
"ASoC: Failed to create DAPM control %s\n",
widget->name);
ret = -ENOMEM;
break;
}
widget++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
soc_probe_link_components->soc_probe_codec->soc_probe_platform
/* initialise the sound card only once */
if (card->probe) {//依次调用各个子结构的probe函数
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_components(card, i, order);//card->num_links=23,所以for循环会执行23次soc_probe_link_components函数
,该函数出了挨个调用了codec,dai和platform驱动的probe函数
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_dais(card, i, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
1)先看下 soc_probe_link_components
static int soc_probe_link_components(struct snd_soc_card *card, int num,
int order)
{
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_component *component;
int i, ret;
/* probe the ==CPU-side== component, if it is a CODEC */
component = rtd->cpu_dai->component;
if (component->driver->probe_order == order) {
ret = soc_probe_component(card, component);
if (ret < 0)
return ret;
}
codec->driver-> probe(codec)这个probe(codec)就是前面分析的.probe = mt6331_codec_probe
/* probe the ==CODEC-side== components */
for (i = 0; i < rtd->num_codecs; i++) {
component = rtd->codec_dais[i]->component;
if (component->driver->probe_order == order) {
ret = soc_probe_component(card, component);
if (ret < 0)
return ret;
}
}
//调用platform probe
/* probe the ==platform== */
if (platform->component.driver->probe_order == order) {
ret = soc_probe_component(card, &platform->component);
if (ret < 0)
return ret;
}
return 0;
}
soc_probe_component就是添加前面调用codec、platform的probe函数里的control、widgets等。
static int soc_probe_component(struct snd_soc_card *card,
struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct snd_soc_dai *dai;
int ret;
if (component->probed)
return 0;
component->card = card;
dapm->card = card;
soc_set_name_prefix(card, component);
if (!try_module_get(component->dev->driver->owner))
return -ENODEV;
soc_init_component_debugfs(component);
if (component->dapm_widgets) {
ret = snd_soc_dapm_new_controls(dapm, component->dapm_widgets,
component->num_dapm_widgets);//添加新的control
if (ret != 0) {
dev_err(component->dev,
"Failed to create new controls %d\n", ret);
goto err_probe;
}
}
list_for_each_entry(dai, &component->dai_list, list) {
ret = snd_soc_dapm_new_dai_widgets(dapm, dai);//添加新的widgets
if (ret != 0) {
dev_err(component->dev,
"Failed to create DAI widgets %d\n", ret);
goto err_probe;
}
}
if (component->probe) {
ret = component->probe(component);
if (ret < 0) {
dev_err(component->dev,
"ASoC: failed to probe component %d\n", ret);
goto err_probe;
}
WARN(dapm->idle_bias_off &&
dapm->bias_level != SND_SOC_BIAS_OFF,
"codec %s can not start from non-off bias with idle_bias_off==1\n",
component->name);
}
if (component->controls)
snd_soc_add_component_controls(component, component->controls,
component->num_controls);//添加component controls
if (component->dapm_routes)
snd_soc_dapm_add_routes(dapm, component->dapm_routes,
component->num_dapm_routes);//添加dapm路径
component->probed = 1;
list_add(&dapm->list, &card->dapm_list);
/* This is a HACK and will be removed soon */
if (component->codec)
list_add(&component->codec->card_list, &card->codec_dev_list);
return 0;
err_probe:
soc_cleanup_component_debugfs(component);
module_put(component->dev->driver->owner);
return ret;
}
2)下面看下soc_probe_link_dais
static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int i, ret;
dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n",
card->name, num, order);
/* config components */
cpu_dai->platform = platform;
cpu_dai->card = card;
for (i = 0; i < rtd->num_codecs; i++)
rtd->codec_dais[i]->card = card;
/* set default power off timeout */
rtd->pmdown_time = pmdown_time;
//调用cpu_dai probe
/* probe the cpu_dai */
if (!cpu_dai->probed &&
cpu_dai->driver->probe_order == order) {
if (cpu_dai->driver->probe) {
ret = cpu_dai->driver->probe(cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev,
"ASoC: failed to probe CPU DAI %s: %d\n",
cpu_dai->name, ret);
return ret;
}
}
cpu_dai->probed = 1;
}
//调用codec_dai probe
/* probe the CODEC DAI */
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order);
if (ret)
return ret;
}
/* complete DAI probe during last probe */
if (order != SND_SOC_COMP_ORDER_LAST)
return 0;
/* do machine specific initialization */
if (dai_link->init) {
ret = dai_link->init(rtd);
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to init %s: %d\n",
dai_link->name, ret);
return ret;
}
}
ret = soc_post_component_init(rtd, dai_link->name);
if (ret)
return ret;
#ifdef CONFIG_DEBUG_FS
/* add DPCM sysfs entries */
if (dai_link->dynamic) {
ret = soc_dpcm_debugfs_add(rtd);
if (ret < 0) {
dev_err(rtd->dev,
"ASoC: failed to add dpcm sysfs entries: %d\n",
ret);
return ret;
}
}
#endif
ret = device_create_file(rtd->dev, &dev_attr_pmdown_time);
if (ret < 0)
dev_warn(rtd->dev, "ASoC: failed to add pmdown_time sysfs: %d\n",
ret);
if (cpu_dai->driver->compress_dai) {
/*create compress_device"*/
ret = soc_new_compress(rtd, num);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create compress %s\n",
dai_link->stream_name);
return ret;
}
} else {
if (!dai_link->params) {
/* create the pcm */
ret = soc_new_pcm(rtd, num);//创建PCM设备
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
} else {
INIT_DELAYED_WORK(&rtd->delayed_work,
codec2codec_close_delayed_work);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link, rtd);
if (ret)
return ret;
}
}
/* add platform data for AC97 devices */
for (i = 0; i < rtd->num_codecs; i++) {
if (rtd->codec_dais[i]->driver->ac97_control)
snd_ac97_dev_add_pdata(rtd->codec_dais[i]->codec->ac97,
rtd->cpu_dai->ac97_pdata);
}
return 0;
}
前面已经创建了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内存分配;
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
int i;
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
playback = rtd->dai_link->dpcm_playback;
capture = rtd->dai_link->dpcm_capture;
} else {
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min)
capture = 1;
}
capture = capture && cpu_dai->driver->capture.channels_min;
playback = playback && cpu_dai->driver->playback.channels_min;
}
if (rtd->dai_link->playback_only) {
playback = 1;
capture = 0;
}
if (rtd->dai_link->capture_only) {
playback = 0;
capture = 1;
}
/* create the PCM */
if (rtd->dai_link->no_pcm) {
snprintf(new_name, sizeof(new_name), "(%s)",
rtd->dai_link->stream_name);
ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
playback, capture, &pcm);
} else {
if (rtd->dai_link->dynamic)
snprintf(new_name, sizeof(new_name), "%s (*)",
rtd->dai_link->stream_name);
else
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name,
(rtd->num_codecs > 1) ?
"multicodec" : rtd->codec_dai->name, num);
/*
snd_pcm_new->_snd_pcm_new
*/
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
capture, &pcm);//创建pcm实例
}
if (ret < 0) {
dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
rtd->dai_link->name);
return ret;
}
dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
rtd->pcm = pcm;
pcm->private_data = rtd;
if (rtd->dai_link->no_pcm) {
if (playback)
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
if (capture)
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
goto out;
}
/* 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.ioctl = soc_pcm_ioctl;
} else {
//snd_pcm实例最后是通过它的子流Substream->ops直接或者间接(pcm中间层函数)对具体驱动进行调用
//Substream->ops =rtd->ops
//Substream->ops.open = soc_pcm_open = platform->driver->ops->open = mtk_capture_pcm_open
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.ioctl = soc_pcm_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;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
dev_err(platform->dev,
"ASoC: pcm constructor failed: %d\n",
ret);
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
out:
dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
(rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
cpu_dai->name);
return ret;
}
snd_pcm_new
_snd_pcm_new
snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)
snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)
snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
rtd->ops.open = soc_pcm_open;
首先看下_snd_pcm_new
ALSA已经为我们实现了功能强劲的PCM中间层,自己的驱动中只要实现一些底层的需要访问硬件的函数即可。一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (pcm == NULL) {
dev_err(card->dev, "Cannot allocate PCM\n");
return -ENOMEM;
}
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
struct snd_pcm_substream *substream;
struct snd_pcm_notify *notify;
char str[16];
struct snd_pcm *pcm;
struct device *dev;
if (snd_BUG_ON(!device || !device->device_data))
return -ENXIO;
pcm = device->device_data;
mutex_lock(®ister_mutex);
err = snd_pcm_add(pcm);
if (err) {
mutex_unlock(®ister_mutex);
return err;
}
for (cidx = 0; cidx < 2; cidx++) {
int devtype = -1;
if (pcm->streams[cidx].substream == NULL || pcm->internal)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
//pcmC%iD%ip pcmC%iD%ic末尾p和c分别代表播放和录音,可以再ls dev/snd下看到所有声卡设备
/* device pointer to use, pcm->dev takes precedence if
* it is assigned, otherwise fall back to card's device
* if possible */
dev = pcm->dev;
if (!dev)
dev = snd_card_get_device_link(pcm->card);
/* register pcm */
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev);//注册pcm设备
/*
const struct file_operations snd_pcm_f_ops[2] = {
{
.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,
}
};
*/
if (err < 0) {
list_del(&pcm->list);
mutex_unlock(®ister_mutex);
return err;
}
dev = snd_get_device(devtype, pcm->card, pcm->device);
if (dev) {
err = sysfs_create_groups(&dev->kobj,
pcm_dev_attr_groups);
if (err < 0)
dev_warn(dev,
"pcm %d:%d: cannot create sysfs groups\n",
pcm->card->number, pcm->device);
put_device(dev);
}
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
snd_pcm_timer_init(substream);
}
list_for_each_entry(notify, &snd_pcm_notify_list, list)
notify->n_register(pcm);
mutex_unlock(®ister_mutex);
return 0;
}
从这HAL层的PCM的一些api就可以调用操作kernel的声卡设备。
默认情况下,驱动不会通过snd_soc_route来主动定义dai widget和stream widget之间的连接关系,实际上,他们之间的连接关系是由ASoc负责的,在声卡的初始化函数中,使用snd_soc_dapm_link_dai_widgets函数来建立他们之间的连接关系,接着,再次从头遍历声卡中所有的widget,找出能与dai widget相连接的stream widget,第一个前提条件是这两个widget必须位于同一个dapm context中
snd_soc_dapm_link_dai_widgets(card);//建立dai widget和stream widget之间的连接关系
snd_soc_dapm_connect_dai_link_widgets(card);
我们再来分析一下snd_soc_dapm_link_dai_widgets函数,看看它是如何连接这两种widget的,它先是遍历声卡中所有的widget,找出类型为snd_soc_dapm_dai_in和snd_soc_dapm_dai_out的widget,通过widget的priv字段,取出widget对应的snd_soc_dai结构指针
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *dai_w, *w;
struct snd_soc_dapm_widget *src, *sink;
struct snd_soc_dai *dai;
//我们再来分析一下snd_soc_dapm_link_dai_widgets函数,看看它是如何连接这两种widget的,它先是遍历声卡中所有的widget,找出类型为snd_soc_dapm_dai_in和snd_soc_dapm_dai_out的widget,通过widget的priv字段,取出widget对应的snd_soc_dai结构指针
/* For each DAI widget... */
list_for_each_entry(dai_w, &card->widgets, list) {
switch (dai_w->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
break;
default:
continue;
}
dai = dai_w->priv;
//接着,再次从头遍历声卡中所有的widget,找出能与dai widget相连接的stream widget,第一个前提条件是这两个widget必须位于同一个dapm context中
/* ...find all widgets with the same stream and link them */
list_for_each_entry(w, &card->widgets, list) {
if (w->dapm != dai_w->dapm)
continue;
//dai widget不会与dai widget相连,所以跳过它们
switch (w->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
continue;
default:
break;
}
//dai widget的名字没有出现在要连接的widget的stream name中,跳过这个widget
if (!w->sname || !strstr(w->sname, dai_w->name))
continue;
if (dai_w->id == snd_soc_dapm_dai_in) {
src = dai_w;
sink = w;
} else {
src = w;
sink = dai_w;
}
dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
}
}
return 0;
}
下面看下snd_soc_dapm_connect_dai_link_widgets(card)
void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd = card->rtd;
int i;
/* for each BE DAI link... */
for (i = 0; i < card->num_rtd; i++) {
rtd = &card->rtd[i];
/*
* dynamic FE links have no fixed DAI mapping.
* CODEC<->CODEC links have no direct connection.
*/
if (rtd->dai_link->dynamic || rtd->dai_link->params)
continue;
dapm_connect_dai_link_widgets(card, rtd);
}
}
//如果widget的stream name包含了dai的stream name,则匹配成功,连接这两个widget
static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dapm_widget *sink, *source;
int i;
for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
/* there is no point in connecting BE DAI links with dummies */
if (snd_soc_dai_is_dummy(codec_dai) ||
snd_soc_dai_is_dummy(cpu_dai))
continue;
/* connect BE DAI playback if widgets are valid */
if (codec_dai->playback_widget && cpu_dai->playback_widget) {
source = cpu_dai->playback_widget;
sink = codec_dai->playback_widget;
dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n",
cpu_dai->component->name, source->name,
codec_dai->component->name, sink->name);
snd_soc_dapm_add_path(&card->dapm, source, sink,
NULL, NULL);
}
/* connect BE DAI capture if widgets are valid */
if (codec_dai->capture_widget && cpu_dai->capture_widget) {
source = codec_dai->capture_widget;
sink = cpu_dai->capture_widget;
dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n",
codec_dai->component->name, source->name,
cpu_dai->component->name, sink->name);
snd_soc_dapm_add_path(&card->dapm, source, sink,
NULL, NULL);
}
}
}
由此可见,dai widget和stream widget是通过stream name进行匹配的,所以,我们在定义codec的stream widget时,
它们的stream name必须要包含dai的stream name,这样才能让ASoc自动把这两种widget连接在一起,只有把它们连接
在一起,ASoc中的播放、录音和停止等事件,才能通过dai widget传递到codec中,使得codec中的widget能根据目前的
播放状态,动态地开启或关闭音频路径上所有widget的电源。
snd_soc_add_card_controls(card, card->controls, card->num_controls)
/**
* snd_soc_add_card_controls - add an array of controls to a SoC card.
* Convenience function to add a list of controls.
*
* @soc_card: SoC card to add controls to
* @controls: array of controls to add
* @num_controls: number of elements in the array
*
* Return 0 for success, else error.
*/
int snd_soc_add_card_controls(struct snd_soc_card *soc_card,
const struct snd_kcontrol_new *controls, int num_controls)
{
struct snd_card *card = soc_card->snd_card;
return snd_soc_add_controls(card, soc_card->dev, controls, num_controls,
NULL, soc_card);
}
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));//将创建的control进行添加实例化到声卡中
if (err < 0) {
dev_err(dev, "ASoC: Failed to add %s: %d\n",
control->name, err);
return err;
}
}
return 0;
}
注册machine级别的路径连接信息
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);
/**
* snd_soc_dapm_add_routes - Add routes between DAPM widgets
* @dapm: DAPM context
* @route: audio routes
* @num: number of routes
*
* Connects 2 dapm widgets together via a named audio path. The sink is
* the widget receiving the audio signal, whilst the source is the sender
* of the audio signal.
*
* Returns 0 for success else error. On error all resources can be freed
* with a call to snd_soc_card_free().
*/
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route, int num)
{
int i, r, ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = 0; i < num; i++) {
r = snd_soc_dapm_add_route(dapm, route);
if (r < 0) {
dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
route->source,
route->control ? route->control : "direct",
route->sink);
ret = r;
}
route++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我们进入snd_soc_dapm_add_route函数看看:
static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route)
{
struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
const char *sink;
const char *source;
char prefixed_sink[80];
char prefixed_source[80];
const char *prefix;
int ret;
prefix = soc_dapm_prefix(dapm);
if (prefix) {
snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
prefix, route->sink);
sink = prefixed_sink;
snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
prefix, route->source);
source = prefixed_source;
} else {
sink = route->sink;
source = route->source;
}
/*
* find src and dest widgets over all widgets but favor a widget from
* current DAPM context
*/
list_for_each_entry(w, &dapm->card->widgets, list) {
if (!wsink && !(strcmp(w->name, sink))) {
wtsink = w;
if (w->dapm == dapm)
wsink = w;
continue;
}
if (!wsource && !(strcmp(w->name, source))) {
wtsource = w;
if (w->dapm == dapm)
wsource = w;
}
}
//上面代码用widget的名字来比较,遍历声卡的widgets链表,找出源widget和目的widget的指针
//如果在本dapm context中没有找到,则使用别的dapm context中找到的widget
/* use widget from another DAPM context if not found from this */
if (!wsink)
wsink = wtsink;
if (!wsource)
wsource = wtsource;
if (wsource == NULL) {
dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
route->source);
return -ENODEV;
}
if (wsink == NULL) {
dev_err(dapm->dev, "ASoC: no sink widget found for %s\n",
route->sink);
return -ENODEV;
}
/*
snd_soc_dapm_add_path函数是整个调用链条中的关键
*/
ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
route->connected);
if (ret)
goto err;
return 0;
err:
dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
source, route->control, sink);
return ret;
}
static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
const char *control,
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink))
{
struct snd_soc_dapm_path *path;
int ret;
path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
if (!path)
return -ENOMEM;
path->source = wsource;
path->sink = wsink;
path->connected = connected;
INIT_LIST_HEAD(&path->list);
INIT_LIST_HEAD(&path->list_kcontrol);
INIT_LIST_HEAD(&path->list_source);
INIT_LIST_HEAD(&path->list_sink);
//函数的一开始,首先为这个连接分配了一个snd_soc_path结构,path的source和sink字段分别指向源widget和目的
widget,connected字段保存connected回调函数,初始化几个snd_soc_path结构中的几个链表。
/* check for external widgets */
if (wsink->id == snd_soc_dapm_input) {
if (wsource->id == snd_soc_dapm_micbias ||
wsource->id == snd_soc_dapm_mic ||
wsource->id == snd_soc_dapm_line ||
wsource->id == snd_soc_dapm_output)
wsink->ext = 1;
}
if (wsource->id == snd_soc_dapm_output) {
if (wsink->id == snd_soc_dapm_spk ||
wsink->id == snd_soc_dapm_hp ||
wsink->id == snd_soc_dapm_line ||
wsink->id == snd_soc_dapm_input)
wsource->ext = 1;
}
//这段代码用于判断是否有外部连接关系,如果有,置位widget的ext字段。判断方法从代码中可以方便地看出:
目的widget是一个输入脚,如果源widget是mic、line、micbias或output,则认为目的widget具有外部连接关系。
源widget是一个输出脚,如果目的widget是spk、hp、line或input,则认为源widget具有外部连接关系。
dapm_mark_dirty(wsource, "Route added");
dapm_mark_dirty(wsink, "Route added");
/* connect static paths */
if (control == NULL) {
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = 1;
return 0;
}
//因为增加了连结关系,所以把源widget和目的widget加入到dapm_dirty链表中。如果没有kcontrol来控制该连接关系,
则这是一个静态连接,直接用path把它们连接在一起。在接着往下看:
/* connect dynamic paths */
switch (wsink->id) {
case snd_soc_dapm_adc:
case snd_soc_dapm_dac:
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
case snd_soc_dapm_input:
case snd_soc_dapm_output:
case snd_soc_dapm_siggen:
case snd_soc_dapm_micbias:
case snd_soc_dapm_vmid:
case snd_soc_dapm_pre:
case snd_soc_dapm_post:
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_clock_supply:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_kcontrol:
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = 1;
return 0;
//按照目的widget来判断,如果属于以上这些类型,直接把它们连接在一起即可,这段感觉有点多余,因为通常以上这
些类型的widget本来也没有kcontrol,直接用上一段代码就可以了,也许是dapm的作者们想着以后可能会有所扩展吧。
case snd_soc_dapm_mux:
ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
&wsink->kcontrol_news[0]);
if (ret != 0)
goto err;
break;
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
if (ret != 0)
goto err;
break;
//目的widget如果是mixer和mux类型,分别用dapm_connect_mixer和dapm_connect_mux函数完成连接工作
case snd_soc_dapm_hp:
case snd_soc_dapm_mic:
case snd_soc_dapm_line:
case snd_soc_dapm_spk:
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = 0;
return 0;
}
//hp、mic、line和spk这几种widget属于外部器件,也只是简单地连接在一起,不过connect字段默认为是未连接状态。
return 0;
err:
kfree(path);
return ret;
}
到这里为止,我们为声卡创建并初始化好了所需的widget,各个widget也通过path连接在了一起,接下来,dapm等待用户的指令,一旦某个dapm kcontrol被用户空间改变,利用这些连接关系,dapm会重新创建音频路径,脱离音频路径的widget会被下电,加入音频路径的widget会被上电,所有的上下电动作都会自动完成,用户空间的应用程序无需关注这些变化,它只管按需要改变某个dapm kcontrol即可。
注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,前面提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
/**
* snd_card_register - register the soundcard
* @card: soundcard structure
*
* This function registers all the devices assigned to the soundcard.
* Until calling this, the ALSA control interface is blocked from the
* external accesses. Thus, you should call this function at the end
* of the initialization of the card.
*
* Return: Zero otherwise a negative error code if the registration failed.
*/
int snd_card_register(struct snd_card *card)
{
int err;
if (snd_BUG_ON(!card))
return -EINVAL;
//声卡的class将会出现在文件系统的/sys/class/sound/下面,并且,device_add(&card->card_dev)也决定了相应的设备节点也将会出现在/dev/snd/下面
if (!card->registered) {
err = device_add(&card->card_dev);
if (err < 0)
return err;
card->registered = true;
}
if ((err = snd_device_register_all(card)) < 0)//通过snd_device_register_all()注册所有挂在该声卡下的逻辑设备,snd_device_register_all()实际上是通过snd_card的devices链表,遍历所有的snd_device,并且调用snd_device的ops->dev_register()来实现各自设备的注册的
return err;
mutex_lock(&snd_card_mutex);
if (snd_cards[card->number]) {
/* already registered */
mutex_unlock(&snd_card_mutex);
return 0;
}
if (*card->id) {
/* make a unique id name from the given string */
char tmpid[sizeof(card->id)];
memcpy(tmpid, card->id, sizeof(card->id));
snd_card_set_id_no_lock(card, tmpid, tmpid);
} else {
/* create an id from either shortname or longname */
const char *src;
src = *card->shortname ? card->shortname : card->longname;
snd_card_set_id_no_lock(card, src,
retrieve_id_from_card_name(src));
}
snd_cards[card->number] = card;
mutex_unlock(&snd_card_mutex);
init_info_for_card(card);
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
return 0;
}