前言
平台:正点原子阿尔法开发板imx6ull
内核:4.1.15
参考链接:DroidPhone的音频子系统
为了更好地分析和查看,把CONFIG_DYNAMIC_DEBUG 宏打开了,并且修改了 dev_dbg() 的定义,如下所示:
#if defined(CONFIG_DYNAMIC_DEBUG)
#define dev_dbg(dev, format, arg...) \
dev_printk(KERN_DEBUG, dev, format, ##arg)
#else
#define dev_dbg(dev, format, arg...) \
({ \
if (0) \
dev_printk(KERN_DEBUG, dev, format, ##arg); \
})
#endif
1、设备树配置
先看下设备树中关于wm8960的描述
&i2c2 {
...
codec: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clks IMX6UL_CLK_SAI2>;
clock-names = "mclk";
wlf,shared-lrclk;
};
...
};
sound {
compatible = "fsl,imx6ul-evk-wm8960",
"fsl,imx-audio-wm8960";
model = "wm8960-audio";
cpu-dai = <&sai2>;
audio-codec = <&codec>;
asrc-controller = <&asrc>;
codec-master;
...
};
sai2: sai@0202c000 {
compatible = "fsl,imx6ul-sai",
"fsl,imx6sx-sai";
reg = <0x0202c000 0x4000>;
interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_SAI2_IPG>,
<&clks IMX6UL_CLK_DUMMY>,
<&clks IMX6UL_CLK_SAI2>,
<&clks 0>, <&clks 0>;
clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3";
dma-names = "rx", "tx";
dmas = <&sdma 37 24 0>, <&sdma 38 24 0>;
status = "disabled";
};
从设备树的配置可以看出wm8960通过imx6ull的i2c2和sai2进行通信和数据传输,并且通过原子给出的原理图可以看出imx6ull将在通信中作为主机产生MCLK、BCLK和LRCK时钟。
2、打印信息分析(按打印先后顺序)
2.1、wm8960_i2c(Codec)
wm8960 1-001a: no default pinctrl state
wm8960 1-001a: probe
wm8960 1-001a: wm8960_i2c_probe
wm8960 1-001a: Initializing rbtree cache
这部分打印是wm8960声卡驱动的相关打印,通过注册i2c驱动和设备树的"wlf,wm8960"字段匹配触发,i2c驱动定义在文件sound/soc/codecs/wm8960.c中。
static const struct of_device_id wm8960_of_match[] = {
{ .compatible = "wlf,wm8960", },
{ }
};
MODULE_DEVICE_TABLE(of, wm8960_of_match);
static struct i2c_driver wm8960_i2c_driver = {
.driver = {
.name = "wm8960",
.owner = THIS_MODULE,
.of_match_table = wm8960_of_match,
},
.probe = wm8960_i2c_probe,
.remove = wm8960_i2c_remove,
.id_table = wm8960_i2c_id,
};
在i2c匹配后会调用wm8960_i2c_probe() 函数,这个probe函数的主要内容如下:
static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
.probe = wm8960_probe,
.set_bias_level = wm8960_set_bias_level,
.suspend_bias_off = true,
};
static struct snd_soc_dai_driver wm8960_dai = {
.name = "wm8960-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,},
.ops = &wm8960_dai_ops,
.symmetric_rates = 1,
};
...
static int wm8960_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap);
if (IS_ERR(wm8960->regmap))
return PTR_ERR(wm8960->regmap);
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm8960, &wm8960_dai, 1);
return ret;
}
其中比较重要的是codec驱动 struct snd_soc_codec_driver ,以及dai驱动struct snd_soc_dai_driver。
这部分代码对应ASoC架构中的Codec。
wm8960 1-001a: ASoC: dai register 1-001a #1
wm8960 1-001a: ASoC: Registered DAI ‘wm8960-hifi’
wm8960 1-001a: ASoC: Registered codec ‘wm8960.1-001a’
通过snd_soc_register_codec() 函数进行sai和codec的注册.
在snd_soc_register_codec() 函数中,又创建了两个结构实例,一个是struct snd_soc_codec ,另一个是struct snd_soc_dai 。在实际注册会把codec驱动和dai驱动和这两个结构关联,通过这两个结构进行注册。
注册完的struct snd_soc_codec 会链接到全局链表codec_list,而struct snd_soc_dai结构会链接到struct snd_soc_codec->component->dai_list,再通过struct snd_soc_codec->component->dai_list链接到全局链表component_list中(没有在这个版本的内核中看到dai_list,而是使用component->dai_list间接和全局链表component_list关联,这点和参考链接有些不同)。
2.2、fsl-sai(Platform)
fsl-sai 202c000.sai: Initializing flat cache
fsl-sai 202c000.sai: ASoC: dai register 202c000.sai #1
fsl-sai 202c000.sai: ASoC: Registered DAI ‘202c000.sai’
fsl-sai 202c000.sai: ASoC: Registered platform ‘202c000.sai’
这段打印对应设备树中关于sai2的描述,也就是wm8960使用的接口。
代码路径:sound/soc/fsl/fsl_sai.c
static struct snd_soc_dai_driver fsl_sai_dai = {
.probe = fsl_sai_dai_probe,
.playback = {
.stream_name = "CPU-Playback",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.capture = {
.stream_name = "CPU-Capture",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.ops = &fsl_sai_pcm_dai_ops,
};
...
static int fsl_sai_probe(struct platform_device *pdev)
{
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
&fsl_sai_dai, 1);
if (ret)
return ret;
if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size))
buffer_size = IMX_SAI_DMABUF_SIZE;
if (sai->sai_on_imx)
return imx_pcm_dma_init(pdev, buffer_size);
else
return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
}
static const struct of_device_id fsl_sai_ids[] = {
{ .compatible = "fsl,vf610-sai", },
{ .compatible = "fsl,imx6sx-sai", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_sai_ids);
static struct platform_driver fsl_sai_driver = {
.probe = fsl_sai_probe,
.driver = {
.name = "fsl-sai",
.pm = &fsl_sai_pm_ops,
.of_match_table = fsl_sai_ids,
},
};
module_platform_driver(fsl_sai_driver);
首先通过compatible属性 “fsl,imx6sx-sai” 和设备树进行匹配,在probe函数中调用了devm_snd_soc_register_component() 函数对struct snd_soc_dai_driver进行注册。
DMA方面的则是通过dmaengine实现。
这部分代码对应ASoC架构中的Platform。 包含了DMA和DAI的相关代码。参考链接
2.3、card 注册(Machine)
imx-wm8960 sound: no default pinctrl state
imx_wm8960_probe
start devm_snd_soc_register_card!
imx-wm8960 sound: ASoC: binding HiFi at idx 0
imx-wm8960 sound: ASoC: binding HiFi-ASRC-FE at idx 1
imx-wm8960 sound: ASoC: binding HiFi-ASRC-BE at idx 2
这部分打印通过对sound节点的compatiable属性“fsl,imx-audio-wm8960”匹配触发。主要代码如下:
代码路径:sound/soc/fsl/imx-wm8960.c
static int imx_wm8960_probe(struct platform_device *pdev)
{
...
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto fail;
}
...
}
static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
static struct platform_driver imx_wm8960_driver = {
.driver = {
.name = "imx-wm8960",
.pm = &snd_soc_pm_ops,
.of_match_table = imx_wm8960_dt_ids,
},
.probe = imx_wm8960_probe,
.remove = imx_wm8960_remove,
};
module_platform_driver(imx_wm8960_driver);
probe函数中调用了devm_snd_soc_register_card() 函数去注册声卡实例,这是ASoC的核心函数之一。
这部分代码对应ASoC架构中的Machine。关于Machine层和相关注册流程参考此链接。
上面的匹配方式都是采用设备树来匹配,以往未使用设备树方式的时候,Machine层使用platform的name字段 “soc-audio” 名进行匹配,随后在probe函数中使用snd_soc_register_card()函数进行声卡实例的注册,相关代码在sound/soc/soc-core.c文件中。
3、总结
整理下经常遇到的结构,如下:
结构 | 所在文件 | 所属类型 |
---|---|---|
struct snd_soc_card | sound/soc/fsl/imx-wm8960.c | Machine |
struct snd_soc_dai_driver | sound/soc/fsl/fsl_sai.c | Platform |
struct snd_soc_codec_driver | sound/soc/codecs/wm8960.c | Codec |
struct snd_soc_codec | sound/soc/soc-core.c | Codec |
struct snd_soc_dai_driver | sound/soc/codecs/wm8960.c | Codec |
struct snd_soc_dai | sound/soc/soc-core.c | Codec |
snd_soc_dai_link | sound/soc/fsl/imx-wm8960.c | 链接Platform和Codec |
LIST_HEAD(platform_list) | sound/soc/soc-core.c | 关联platform实例(源码中只看到fsl-asrc和snd-soc-dummy有使用) |
LIST_HEAD(codec_list) | sound/soc/soc-core.c | 关联snd_soc_codec实例 |
LIST_HEAD(component_list) | sound/soc/soc-core.c | 关联snd_soc_dai实例 |
对于ALSA,本人目前接触不深,都是自己学习网上的知识,如果哪里有错漏,请大佬们帮忙指正。