arm平臺:IMX6Q
內核:Linux4.1.15
系統:Android6.0
codec:wm8960
主要內容爲Machine、Platform和Codec三大部分之間的關係和實現。文章中的舉例和代碼均爲imx6q-Android6.0源碼和文件。
文章中如有錯誤和不嚴謹內容,歡迎指正。
一、註冊platform device
kernel_imx/sound/soc/fsl/imx-wm8960.c
在文件中包含關於wm8960相關的設備結構體。設備結構體重包含snd_soc_card結構體。
struct imx_wm8960_data {
struct snd_soc_card card; //主要內容
struct clk *codec_clk;
unsigned int clk_frequency;
bool is_codec_master;
bool is_stream_in_use[2];
bool is_stream_opened[2];
struct regmap *gpr;
unsigned int hp_det[2];
u32 asrc_rate;
u32 asrc_format;
};
在imx-wm8960.c文件中註冊platform driver和設備樹中的Platform Device進行匹配。然後執行imx_wm8960_probe函數,註冊聲卡。
static struct snd_soc_ops imx_hifi_ops = {
.hw_params = imx_hifi_hw_params,
.hw_free = imx_hifi_hw_free,
.startup = imx_hifi_startup,
.shutdown = imx_hifi_shutdown,
};
static struct snd_soc_dai_link imx_wm8960_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "wm8960-hifi",
.ops = &imx_hifi_ops,
},
{
.name = "HiFi-ASRC-FE",
.stream_name = "HiFi-ASRC-FE",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "HiFi-ASRC-BE",
.stream_name = "HiFi-ASRC-BE",
.codec_dai_name = "wm8960-hifi",
.platform_name = "snd-soc-dummy",
.no_pcm = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &imx_hifi_ops,
.be_hw_params_fixup = be_hw_params_fixup,
},
};
struct imx_wm8960_data *data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
data->card.dai_link = imx_wm8960_dai;
/*把snd_soc_card結構體放到platform_device的dev.drvdata字段*/
platform_set_drvdata(pdev, &data->card);
/*把data結構體保存到snd_soc_card結構體下的drvdata字段*/
snd_soc_card_set_drvdata(&data->card, data);
/*註冊聲卡到asoc核心*/
devm_snd_soc_register_card(&pdev->dev, &data->card);
machine部分的平臺設備驅動代碼主要是snd_soc_card結構體。有引出了machine驅動的另外兩個數據結構(snd_soc_dai_link、snd_soc_ops)。在snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍後在Machine平臺驅動層中將會利用這些名字去匹配已經在系統中註冊的platform、codec、dai,這些註冊的部件都是在另外相應的Platform驅動和Codec驅動的代碼文件中定義的,這樣看來,Machine驅動的設備初始化代碼無非就是選擇合適Platform和Codec以及dai,用他們填充以上幾個數據結構,然後註冊Platform設備即可。當然還要實現連接Platform和Codec的dai_link對應的ops實現。
二、snd_soc_dai_link
在snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字。
codec_dai_name、cpu_dai_name兩個名字必須指定。其他可以沒有。
struct snd_soc_dai_link {
/* config - must be set by machine driver */
const char *name; /* Codec name */
const char *stream_name; /* Stream name */
const char *cpu_name; /*cpu控制接口name*/
const char *cpu_dai_name; /*cpu數據傳輸接口name*/
const char *codec_name; /*codec設備name*/
const char *codec_dai_name; /*codec數據傳輸接口name*/
/*
* You MAY specify the link's platform/PCM/DMA driver, either by
* device name, or by DT/OF node, but not both. Some forms of link
* do not need a platform.
*/
const char *platform_name;
...
};
/*在wm8960的machine部分中的定義*/
static struct snd_soc_dai_link imx_wm8960_dai[] = {
...
{
.name = "HiFi-ASRC-BE",
.stream_name = "HiFi-ASRC-BE",
.codec_dai_name = "wm8960-hifi",
.platform_name = "snd-soc-dummy",
.no_pcm = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &imx_hifi_ops,
.be_hw_params_fixup = be_hw_params_fixup,
},
};
/*在imx_wm8960_probe中*/
imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
三、snd_soc_register_card
kernel_imx/sound/soc/soc-core.c
目的:
- 檢測snd_soc_dai_link結構體中的codec_dai_name、cpu_dai_name是否存在。
- 初始化有關鏈表
- 創建snd_soc_pcm_runtime結構體
- 初始化聲卡(重要)
int snd_soc_register_card(struct snd_soc_card *card)
{
int i, j, ret;
if (!card->name || !card->dev)
return -EINVAL;
/*遍歷snd_soc_card下的dai_link,確保codec_dai_name、cpu_dai_name存在*/
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);
/*內聯函數
INIT_LIST_HEAD(&card->codec_dev_list);
INIT_LIST_HEAD(&card->widgets);
INIT_LIST_HEAD(&card->paths);
INIT_LIST_HEAD(&card->dapm_list);
初始化各個部件的鏈表
*/
snd_soc_initialize_card_lists(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];
/*把平臺數據snd_soc_card結構體中的dai_link拷貝到snd_soc_pcm_runtime結構體的對應成員中*/
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)
return ret;
/* deactivate pins to sleep state */
...
...
return ret;
}
四、snd_soc_instantiate_card
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
...
/*
ASoC定義了三個全局的鏈表頭變量:codec_list、dai_list、platform_list,系統中所有的Codec、DAI、
Platform都在註冊時連接到這三個全局鏈表上。soc_bind_dai_link函數逐個掃描這三個鏈表,根據
card->dai_link[]中的名稱(name)進行匹配,匹配成功後把相應的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);
}
/* bind aux_devs too */
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_bind_aux_dev(card, i);
}
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
snd_soc_init_codec_cache(codec);
}
/*創建聲卡實例*/
snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets);
/* 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);
if (card->of_dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
card->num_of_dapm_routes);
snd_soc_dapm_new_widgets(card);
snd_card_register(card->snd_card);
...
...
}
五、soc_probe_link_dais
static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
/*執行cpu_dai下的probe函數*/
ret = soc_probe_dai(cpu_dai, order);
if (ret)
return ret;
/* probe the CODEC DAI */
/*執行codec_dai下的prove函數*/
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_probe_dai(rtd->codec_dais[i], order);
if (ret)
return ret;
}
/* 調用標準的alsa的接口函數snd_pcm_new()函數用於創建標準alsa驅動的pcm邏輯設備 */
soc_new_pcm(rtd, num);
}
六、soc_new_pcm
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
ret = snd_pcm_new(rtd->card->snd_card, new_name,
num, playback, capture, &pcm); // 創建標準alsa的pcm邏輯設備
rtd->pcm = pcm; // 讓snd_soc_pcm_runtime中的pcm指向剛剛創建的pcm
pcm->private_data = rtd;
/* 設置真正操作底層聲卡的操作函數!!一共有3條路徑 */
(1):用platform中的驅動層的ops來填充
if (platform->driver->ops) {
soc_pcm_ops.mmap = platform->driver->ops->mmap;
soc_pcm_ops.pointer = platform->driver->ops->pointer;
soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
soc_pcm_ops.copy = platform->driver->ops->copy;
soc_pcm_ops.silence = platform->driver->ops->silence;
soc_pcm_ops.ack = platform->driver->ops->ack;
soc_pcm_ops.page = platform->driver->ops->page;
}
(2):用標準的alsa的api接口來創建ops
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);
(3):調用pcm_new函數,來創建一個
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
}
}