我们已经知道,ASoC中的driver分为三部分
- Codec ASoC的codec端,主要作用于codec dai
- Platform ASoC的Platform端,主要作用于cpu dai
- Machine 完成platform和codec的串联,使之协同工作
0 Prepare 几个重要的结构体
在Alsa中,大量运用了面向对象的思想,下面先来认识几个重要的结构体:
snd_soc_card
、platform_device
、snd_soc_codec
、snd_soc_codec_driver
、snd_soc_dai_link
snd_soc_card代表着Machine驱动,snd_soc_platform则代表着Platform驱动,snd_soc_codec和snd_soc_codec_driver代表了Codec驱动,而snd_soc_dai_link则将ASoC的各个成员(Platform, codec, dai)通过名字串联在一起。
snd_soc_dai
、snd_soc_dai_ops
、snd_soc_dai_driver
、snd_pcm_substream
snd_soc_dai是snd_soc_platform和snd_soc_codec的数字音频接口,snd_soc_codec的dai为codec_dai,snd_soc_platform的dai为cpu_dai,snd_pcm是snd_soc_card实例化后注册的声卡类型.snd_soc_dai_ops
是snd_soc_dai_driver
所需的用来播放或者录音时调用的,在snd_soc_register_codec时注册到系统中。
clk
、snd_soc_ops
、snd_soc_dapm_widget
、snd_soc_dapm_route
、snd_soc_pcm_runtime
、
clk用来保存alsa所用时钟信息,具体请参见[Alsa Document]7, clocking.txt,snd_soc_ops是ASoC machine driver operations,snd_soc_dapm_widget和snd_soc_dapm_route分别用来声明dapm要用到的widget和route,然后把这些信息填充到snd_soc_codec_driver结构体。
1 Codec
Codec可以理解为解码芯片,具体来说就是wm8524,wm8978等,一般来说,其源码放在sound/soc/codec
目录下,但是在实际看代码的过程中,可以看到,基本每个芯片的初始化注册流程都不一样,而且platform也放在这个文件里。
static const struct snd_soc_codec_driver soc_codec_dev_wm8524 = {
.probe = wm8524_probe,
.component_driver = {
.dapm_widgets = wm8524_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm8524_dapm_widgets),
.dapm_routes = wm8524_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(wm8524_dapm_routes),
},
};
2 Platform
Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音频信号。ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据的codec dai,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。
static struct snd_soc_dai_driver wm8524_dai = {
.name = "wm8524-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = WM8524_RATES,
.formats = WM8524_FORMATS,
},
.ops = &wm8524_dai_ops,
};
static const struct of_device_id wm8524_of_match[] = {
{ .compatible = "wlf,wm8524" },
{ /* sentinel*/ }
};
MODULE_DEVICE_TABLE(of, wm8524_of_match);
static struct platform_driver wm8524_codec_driver = {
.probe = wm8524_codec_probe,
.driver = {
.name = "wm8524-codec",
.of_match_table = wm8524_of_match,
},
};
module_platform_driver(wm8524_codec_driver);
关于module_platform_driver注册platform可以看这篇文章:
Linux驱动:module_platform_driver
驱动中使用module_platform_driver 来注册驱动 跟自定义module_init &&module_exit 的结果是一致的。
啰嗦一句,不要天真的以为只有一个platform,要注意module_platform_driver
只是注册了一个platform到系统中待调用,举个例子,在sound/soc/fsl/
目录下的文件中:
module_platform_driver(fsl_asrc_driver);
module_platform_driver(fsl_soc_dma_driver);
module_platform_driver(fsl_esai_driver);
module_platform_driver(fsl_sai_driver);
module_platform_driver(fsl_spdif_driver);
module_platform_driver(fsl_ssi_driver);
static int __init imx_audmux_init(void)
{
return platform_driver_register(&imx_audmux_driver);
}
subsys_initcall(imx_audmux_init);
等等,此处就不一一列举,可以看到,此处有多处platform,所以说platform,不是一个,数据通信要用SAI Platform,数据通信要用DMA Platform。比如我的板子platform如下:
3 Machine
说完了platform和codec,接下来需要将这两部分串联在一起,这部分内容的最重要的一个函数是snd_soc_register_card
,只需要查找一下这个函数就可以快速定位machine驱动的位置,如果你的驱动中没有这个函数,那么很有可能是在devm_snd_soc_register_card
,这个函数是对snd_soc_register_card
的进一步封装会自动释放未注册的card,一般来说这个工作是在probe函数中进行的,例如:
static int imx_wm8524_probe(struct platform_device *pdev)
{
struct device_node *cpu_np, *codec_np = NULL;
struct platform_device *cpu_pdev;
struct imx_priv *priv;
struct platform_device *codec_pdev = NULL;
int ret;
struct i2c_client * codec_client = NULL;
const char *dma_name;
...
snd_soc_card_set_drvdata(&priv->card, priv);
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto fail;
}
...
}
static int imx_wm8962_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np = NULL, *codec_np = NULL;
struct platform_device *cpu_pdev;
struct imx_priv *priv = &card_priv;
struct i2c_client *codec_dev;
struct imx_wm8962_data *data;
int int_port, ext_port, tmp_port;
int ret;
struct platform_device *asrc_pdev = NULL;
struct device_node *asrc_np;
u32 width;
...
platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
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;
}
...
}
再啰嗦一句,一般来说,Codec驱动和Platform驱动一般放在一起,因为他们和平台无关,一般会随kernel提供,放在sound/soc/codecs/目录下,而Machine一般和平台息息相关,比如接口是I2S、PCM还是AC97等,例如wm8524的设备树中关于Machine驱动的部分:
sound-wm8524 { compatible = "fsl,imx-audio-wm8524"; model = "wm8524-audio"; audio-cpu = <&sai3>; audio-codec = <&wm8524>; audio-routing = "Line Out Jack", "LINEVOUTL", "Line Out Jack", "LINEVOUTR"; };
这个一般不会随内核提供,都需要自己来写,那到底怎么来写呢?请关注下面的内容。