[Alsa]7, wm8524 Alsa driver

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。 https://blog.csdn.net/wangyijieonline/article/details/88310364

我们已经知道,ASoC中的driver分为三部分

  • Codec ASoC的codec端,主要作用于codec dai
  • Platform ASoC的Platform端,主要作用于cpu dai
  • Machine 完成platform和codec的串联,使之协同工作
    ASoC架构

0 Prepare 几个重要的结构体

在Alsa中,大量运用了面向对象的思想,下面先来认识几个重要的结构体:
snd_soc_cardplatform_devicesnd_soc_codecsnd_soc_codec_driversnd_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_daisnd_soc_dai_opssnd_soc_dai_driversnd_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_opssnd_soc_dai_driver所需的用来播放或者录音时调用的,在snd_soc_register_codec时注册到系统中。

clksnd_soc_opssnd_soc_dapm_widgetsnd_soc_dapm_routesnd_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";
 };

这个一般不会随内核提供,都需要自己来写,那到底怎么来写呢?请关注下面的内容。

猜你喜欢

转载自blog.csdn.net/wangyijieonline/article/details/88310364