翼度科技»论坛 云主机 LINUX 查看内容

Linux 内核 ASoC DMA 引擎驱动程序

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
Linux 内核 ASoC 框架,在概念上将嵌入式音频系统拆分为多个可复用的组件驱动程序,包括 Codec 类驱动程序、平台类驱动程序和机器类驱动程序。在实现上,机器类驱动程序用 struct snd_soc_card 和 struct snd_soc_dai_link 结构描述,属于平台类驱动程序的 DMA 引擎驱动程序由 struct snd_soc_component_driver 结构描述,codec 类驱动程序和 I2S 等驱动程序,由 struct snd_soc_component_driver、struct snd_soc_dai_driver 和 struct snd_soc_dai_ops 等结构描述。除平台类驱动程序外的各种驱动程序都通过 component 抽象组织在一起,即这些驱动程序都作为 struct snd_soc_component_driver 注册给 Linux 内核 ASoC 框架,Linux 内核 ASoC 框架为它们各自创建 struct snd_soc_component 结构对象,并保存在 sound/soc/soc-core.c 文件中定义的全局链表 component_list 中。
一个 DMA 驱动程序的示例为 soc/pxa/pxa2xx-pcm.c:
  1. static const struct snd_soc_component_driver pxa2xx_soc_platform = {
  2.         .pcm_construct        = pxa2xx_soc_pcm_new,
  3.         .pcm_destruct        = pxa2xx_soc_pcm_free,
  4.         .open                = pxa2xx_soc_pcm_open,
  5.         .close                = pxa2xx_soc_pcm_close,
  6.         .hw_params        = pxa2xx_soc_pcm_hw_params,
  7.         .hw_free        = pxa2xx_soc_pcm_hw_free,
  8.         .prepare        = pxa2xx_soc_pcm_prepare,
  9.         .trigger        = pxa2xx_soc_pcm_trigger,
  10.         .pointer        = pxa2xx_soc_pcm_pointer,
  11.         .mmap                = pxa2xx_soc_pcm_mmap,
  12. };
  13. static int pxa2xx_soc_platform_probe(struct platform_device *pdev)
  14. {
  15.         return devm_snd_soc_register_component(&pdev->dev, &pxa2xx_soc_platform,
  16.                                                NULL, 0);
  17. }
  18. static struct platform_driver pxa_pcm_driver = {
  19.         .driver = {
  20.                 .name = "pxa-pcm-audio",
  21.         },
  22.         .probe = pxa2xx_soc_platform_probe,
  23. };
  24. module_platform_driver(pxa_pcm_driver);
复制代码
DMA 引擎驱动程序和 I2S 或 Codec 驱动程序一样,通过 devm_snd_soc_register_component() 函数以 struct snd_soc_component_driver 的形式注册给 Linux 内核 ASoC 框架,但比较特别的地方在于,它的 dai driver 参数为空。
机器类驱动程序定义的 struct snd_soc_dai_link 结构对象通过 struct snd_soc_dai_link_component 描述它引用的其它类型的驱动程序,如机器类驱动程序 sound/soc/pxa/e800_wm9712.c 有如下的代码片段:
  1. SND_SOC_DAILINK_DEFS(ac97,
  2.         DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),
  3.         DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")),
  4.         DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
  5. SND_SOC_DAILINK_DEFS(ac97_aux,
  6.         DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),
  7.         DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-aux")),
  8.         DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
  9. static struct snd_soc_dai_link e800_dai[] = {
  10.         {
  11.                 .name = "AC97",
  12.                 .stream_name = "AC97 HiFi",
  13.                 SND_SOC_DAILINK_REG(ac97),
  14.         },
  15.         {
  16.                 .name = "AC97 Aux",
  17.                 .stream_name = "AC97 Aux",
  18.                 SND_SOC_DAILINK_REG(ac97_aux),
  19.         },
  20. };
  21. static struct snd_soc_card e800 = {
  22.         .name = "Toshiba e800",
  23.         .owner = THIS_MODULE,
  24.         .dai_link = e800_dai,
  25.         .num_links = ARRAY_SIZE(e800_dai),
  26.         .dapm_widgets = e800_dapm_widgets,
  27.         .num_dapm_widgets = ARRAY_SIZE(e800_dapm_widgets),
  28.         .dapm_routes = audio_map,
  29.         .num_dapm_routes = ARRAY_SIZE(audio_map),
  30. };
复制代码
SND_SOC_DAILINK_DEFS() 宏用于为 struct snd_soc_dai_link 方便地定义引用的 cpus、codecs 和 platforms 等 struct snd_soc_dai_link_component 数组,其中用于定义 platforms 的 DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")) 引用了上面我们看到的 DMA 引擎驱动。
Linux 内核 ASoC 框架提供了一个通用的 DMA 引擎驱动程序,位于文件 sound/soc/soc-generic-dmaengine-pcm 中。这个驱动程序本身不会主动向 Linux 内核 ASoC 框架注册自己,需要使用 DMA 引擎在设备和内存之间传数据的驱动程序要在 probe 时注册它,如 sound/soc/rockchip/rockchip_pcm.c:
  1. static const struct snd_pcm_hardware snd_rockchip_hardware = {
  2.         .info                        = SNDRV_PCM_INFO_MMAP |
  3.                                   SNDRV_PCM_INFO_MMAP_VALID |
  4.                                   SNDRV_PCM_INFO_PAUSE |
  5.                                   SNDRV_PCM_INFO_RESUME |
  6.                                   SNDRV_PCM_INFO_INTERLEAVED,
  7.         .period_bytes_min        = 32,
  8.         .period_bytes_max        = 8192,
  9.         .periods_min                = 1,
  10.         .periods_max                = 52,
  11.         .buffer_bytes_max        = 64 * 1024,
  12.         .fifo_size                = 32,
  13. };
  14. static const struct snd_dmaengine_pcm_config rk_dmaengine_pcm_config = {
  15.         .pcm_hardware = &snd_rockchip_hardware,
  16.         .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
  17.         .prealloc_buffer_size = 32 * 1024,
  18. };
  19. int rockchip_pcm_platform_register(struct device *dev)
  20. {
  21.         return devm_snd_dmaengine_pcm_register(dev, &rk_dmaengine_pcm_config,
  22.                 SND_DMAENGINE_PCM_FLAG_COMPAT);
  23. }
  24. EXPORT_SYMBOL_GPL(rockchip_pcm_platform_register);
复制代码
这里的 rockchip_pcm_platform_register() 函数在 I2S 驱动程序的 probe 操作 (位于 sound/soc/rockchip/rockchip_i2s.c) 中调用:
  1. static int rockchip_i2s_probe(struct platform_device *pdev)
  2. {
  3. . . . . . .
  4.         ret = devm_snd_soc_register_component(&pdev->dev,
  5.                                               &rockchip_i2s_component,
  6.                                               soc_dai, 1);
  7.         if (ret) {
  8.                 dev_err(&pdev->dev, "Could not register DAI\n");
  9.                 goto err_suspend;
  10.         }
  11.         ret = rockchip_pcm_platform_register(&pdev->dev);
  12.         if (ret) {
  13.                 dev_err(&pdev->dev, "Could not register PCM\n");
  14.                 goto err_suspend;
  15.         }
  16.         return 0;
  17. . . . . . .
  18.         return ret;
  19. }
复制代码
rockchip_pcm_platform_register() 函数调用 devm_snd_dmaengine_pcm_register() 函数注册通用 DMA 引擎驱动程序。devm_snd_dmaengine_pcm_register() 函数定义 (位于 sound/soc/soc-devres.c) 如下:
  1. static void devm_dmaengine_pcm_release(struct device *dev, void *res)
  2. {
  3.         snd_dmaengine_pcm_unregister(*(struct device **)res);
  4. }
  5. /**
  6. * devm_snd_dmaengine_pcm_register - resource managed dmaengine PCM registration
  7. * @dev: The parent device for the PCM device
  8. * @config: Platform specific PCM configuration
  9. * @flags: Platform specific quirks
  10. *
  11. * Register a dmaengine based PCM device with automatic unregistration when the
  12. * device is unregistered.
  13. */
  14. int devm_snd_dmaengine_pcm_register(struct device *dev,
  15.         const struct snd_dmaengine_pcm_config *config, unsigned int flags)
  16. {
  17.         struct device **ptr;
  18.         int ret;
  19.         ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);
  20.         if (!ptr)
  21.                 return -ENOMEM;
  22.         ret = snd_dmaengine_pcm_register(dev, config, flags);
  23.         if (ret == 0) {
  24.                 *ptr = dev;
  25.                 devres_add(dev, ptr);
  26.         } else {
  27.                 devres_free(ptr);
  28.         }
  29.         return ret;
  30. }
  31. EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register);
  32. #endif
复制代码
devm_snd_dmaengine_pcm_register() 函数和之前看到的devm_snd_soc_register_card() 与 devm_snd_soc_register_component() 函数一样,只是它封装的是 snd_dmaengine_pcm_register() 函数。snd_dmaengine_pcm_register() 函数定义 (位于 sound/soc/soc-generic-dmaengine-pcm.c) 如下:
  1. static const struct snd_soc_component_driver dmaengine_pcm_component = {
  2.         .name                = SND_DMAENGINE_PCM_DRV_NAME,
  3.         .probe_order        = SND_SOC_COMP_ORDER_LATE,
  4.         .open                = dmaengine_pcm_open,
  5.         .close                = dmaengine_pcm_close,
  6.         .hw_params        = dmaengine_pcm_hw_params,
  7.         .trigger        = dmaengine_pcm_trigger,
  8.         .pointer        = dmaengine_pcm_pointer,
  9.         .pcm_construct        = dmaengine_pcm_new,
  10. };
  11. static const struct snd_soc_component_driver dmaengine_pcm_component_process = {
  12.         .name                = SND_DMAENGINE_PCM_DRV_NAME,
  13.         .probe_order        = SND_SOC_COMP_ORDER_LATE,
  14.         .open                = dmaengine_pcm_open,
  15.         .close                = dmaengine_pcm_close,
  16.         .hw_params        = dmaengine_pcm_hw_params,
  17.         .trigger        = dmaengine_pcm_trigger,
  18.         .pointer        = dmaengine_pcm_pointer,
  19.         .copy_user        = dmaengine_copy_user,
  20.         .pcm_construct        = dmaengine_pcm_new,
  21. };
  22. static const char * const dmaengine_pcm_dma_channel_names[] = {
  23.         [SNDRV_PCM_STREAM_PLAYBACK] = "tx",
  24.         [SNDRV_PCM_STREAM_CAPTURE] = "rx",
  25. };
  26. static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
  27.         struct device *dev, const struct snd_dmaengine_pcm_config *config)
  28. {
  29.         unsigned int i;
  30.         const char *name;
  31.         struct dma_chan *chan;
  32.         if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || (!dev->of_node &&
  33.             !(config && config->dma_dev && config->dma_dev->of_node)))
  34.                 return 0;
  35.         if (config && config->dma_dev) {
  36.                 /*
  37.                  * If this warning is seen, it probably means that your Linux
  38.                  * device structure does not match your HW device structure.
  39.                  * It would be best to refactor the Linux device structure to
  40.                  * correctly match the HW structure.
  41.                  */
  42.                 dev_warn(dev, "DMA channels sourced from device %s",
  43.                          dev_name(config->dma_dev));
  44.                 dev = config->dma_dev;
  45.         }
  46.         for_each_pcm_streams(i) {
  47.                 if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
  48.                         name = "rx-tx";
  49.                 else
  50.                         name = dmaengine_pcm_dma_channel_names[i];
  51.                 if (config && config->chan_names[i])
  52.                         name = config->chan_names[i];
  53.                 chan = dma_request_chan(dev, name);
  54.                 if (IS_ERR(chan)) {
  55.                         /*
  56.                          * Only report probe deferral errors, channels
  57.                          * might not be present for devices that
  58.                          * support only TX or only RX.
  59.                          */
  60.                         if (PTR_ERR(chan) == -EPROBE_DEFER)
  61.                                 return -EPROBE_DEFER;
  62.                         pcm->chan[i] = NULL;
  63.                 } else {
  64.                         pcm->chan[i] = chan;
  65.                 }
  66.                 if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
  67.                         break;
  68.         }
  69.         if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
  70.                 pcm->chan[1] = pcm->chan[0];
  71.         return 0;
  72. }
  73. static void dmaengine_pcm_release_chan(struct dmaengine_pcm *pcm)
  74. {
  75.         unsigned int i;
  76.         for_each_pcm_streams(i) {
  77.                 if (!pcm->chan[i])
  78.                         continue;
  79.                 dma_release_channel(pcm->chan[i]);
  80.                 if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
  81.                         break;
  82.         }
  83. }
  84. /**
  85. * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
  86. * @dev: The parent device for the PCM device
  87. * @config: Platform specific PCM configuration
  88. * @flags: Platform specific quirks
  89. */
  90. int snd_dmaengine_pcm_register(struct device *dev,
  91.         const struct snd_dmaengine_pcm_config *config, unsigned int flags)
  92. {
  93.         const struct snd_soc_component_driver *driver;
  94.         struct dmaengine_pcm *pcm;
  95.         int ret;
  96.         pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
  97.         if (!pcm)
  98.                 return -ENOMEM;
  99. #ifdef CONFIG_DEBUG_FS
  100.         pcm->component.debugfs_prefix = "dma";
  101. #endif
  102.         pcm->config = config;
  103.         pcm->flags = flags;
  104.         ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
  105.         if (ret)
  106.                 goto err_free_dma;
  107.         if (config && config->process)
  108.                 driver = &dmaengine_pcm_component_process;
  109.         else
  110.                 driver = &dmaengine_pcm_component;
  111.         ret = snd_soc_component_initialize(&pcm->component, driver, dev);
  112.         if (ret)
  113.                 goto err_free_dma;
  114.         ret = snd_soc_add_component(&pcm->component, NULL, 0);
  115.         if (ret)
  116.                 goto err_free_dma;
  117.         return 0;
  118. err_free_dma:
  119.         dmaengine_pcm_release_chan(pcm);
  120.         kfree(pcm);
  121.         return ret;
  122. }
  123. EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register);
  124. /**
  125. * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device
  126. * @dev: Parent device the PCM was register with
  127. *
  128. * Removes a dmaengine based PCM device previously registered with
  129. * snd_dmaengine_pcm_register.
  130. */
  131. void snd_dmaengine_pcm_unregister(struct device *dev)
  132. {
  133.         struct snd_soc_component *component;
  134.         struct dmaengine_pcm *pcm;
  135.         component = snd_soc_lookup_component(dev, SND_DMAENGINE_PCM_DRV_NAME);
  136.         if (!component)
  137.                 return;
  138.         pcm = soc_component_to_pcm(component);
  139.         snd_soc_unregister_component_by_driver(dev, component->driver);
  140.         dmaengine_pcm_release_chan(pcm);
  141.         kfree(pcm);
  142. }
  143. EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister);
  144. MODULE_LICENSE("GPL");
复制代码
snd_dmaengine_pcm_register() 函数的执行过程如下:

  • 动态分配一个 struct dmaengine_pcm 结构对象,并初始化其 config 和 flags 等字段,struct dmaengine_pcm 结构定义 (位于 include/sound/dmaengine_pcm.h) 如下:
  1. struct dmaengine_pcm {
  2.         struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1];
  3.         const struct snd_dmaengine_pcm_config *config;
  4.         struct snd_soc_component component;
  5.         unsigned int flags;
  6. };
复制代码
这个结构有一个 struct snd_soc_component 结构成员;

  • 为数据的发送和接收申请 DMA 通道,这个需要在设备树的设备节点定义中,指定发送和接收引用的 DMA 通道,像下面这样:
  1.         i2s0_8ch: i2s@fe470000 {
  2. . . . . . .
  3.                 dmas = <&dmac0 0>, <&dmac0 1>;
  4.                 dma-names = "tx", "rx";
  5. . . . . . .
  6.         };
复制代码

  • 根据传入的 config 参数,选择 struct snd_soc_component_driver,dmaengine_pcm_component_process 和 dmaengine_pcm_component 仅有的区别是,前者多定义了一个 copy_user 操作;
  • 初始化并添加 struct snd_soc_component 结构对象,有一个函数 (位于 include/sound/dmaengine_pcm.h) 可以通过 struct snd_soc_component 结构对象获得 struct dmaengine_pcm 结构对象:
  1. static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p)
  2. {
  3.         return container_of(p, struct dmaengine_pcm, component);
  4. }
复制代码
通用 DMA 引擎驱动程序支持的操作由 struct snd_soc_component_driver 定义。在全局  component 链表中,相应的 struct snd_soc_component 由注册它的 dev 标识。
使用了通用 DMA 引擎驱动程序的 ASoC 机器驱动程序,示例 (位于 sound/soc/rockchip/rockchip_rt5645.c) 如下:
  1. SND_SOC_DAILINK_DEFS(pcm,
  2.         DAILINK_COMP_ARRAY(COMP_EMPTY()),
  3.         DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5645-aif1")),
  4.         DAILINK_COMP_ARRAY(COMP_EMPTY()));
  5. static struct snd_soc_dai_link rk_dailink = {
  6.         .name = "rt5645",
  7.         .stream_name = "rt5645 PCM",
  8.         .init = rk_init,
  9.         .ops = &rk_aif1_ops,
  10.         /* set rt5645 as slave */
  11.         .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
  12.                 SND_SOC_DAIFMT_CBS_CFS,
  13.         SND_SOC_DAILINK_REG(pcm),
  14. };
  15. static struct snd_soc_card snd_soc_card_rk = {
  16.         .name = "I2S-RT5650",
  17.         .owner = THIS_MODULE,
  18.         .dai_link = &rk_dailink,
  19.         .num_links = 1,
  20.         .dapm_widgets = rk_dapm_widgets,
  21.         .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets),
  22.         .dapm_routes = rk_audio_map,
  23.         .num_dapm_routes = ARRAY_SIZE(rk_audio_map),
  24.         .controls = rk_mc_controls,
  25.         .num_controls = ARRAY_SIZE(rk_mc_controls),
  26. };
  27. . . . . . .
  28. static int snd_rk_mc_probe(struct platform_device *pdev)
  29. {
  30. . . . . . .
  31.         rk_dailink.cpus->of_node = of_parse_phandle(np,
  32.                         "rockchip,i2s-controller", 0);
  33.         if (!rk_dailink.cpus->of_node) {
  34.                 dev_err(&pdev->dev,
  35.                         "Property 'rockchip,i2s-controller' missing or invalid\n");
  36.                 ret = -EINVAL;
  37.                 goto put_codec_of_node;
  38.         }
  39.         rk_dailink.platforms->of_node = rk_dailink.cpus->of_node;
  40. . . . . . .
  41. }
复制代码
这里为 dai link 定义的 cpus dai 数组和 platforms 数组中都只有一个空元素,但在 probe 操作中,根据设备树中设备节点的定义,查找了对应的 of_node。尽管 cpus dai 和 platforms 引用了相同的 of_node,但在 snd_soc_add_pcm_runtime() 函数中,为 CPU DAI 添加 struct snd_soc_component 的过程是,先查找对应的 struct snd_soc_dai,再从 struct snd_soc_dai 获得 struct snd_soc_component,这也就意味着,为 CPU DAI 查找  struct snd_soc_component 时,不会找到没有 struct snd_soc_dai 的通用 DMA 引擎的 struct snd_soc_component。为 platforms 添加 struct snd_soc_component 的过程,则是直接查找所有匹配的 struct snd_soc_component 并添加。
在为 PCM 创建 struct snd_soc_pcm_runtime 时,即在机器类驱动程序的 probe 操作中,如果为 snd_soc_dai_link 做了类似于这里的处理,使 CPU DAI 和 platform 指向相同的 of_node,且对应于 of_node 的设备驱动程序注册了通用 DMA 引擎驱动程序,通用 DMA 引擎的 struct snd_soc_component 将会包含在它的 component 链表中。
通用 DMA 引擎驱动程序的 struct snd_soc_component_driver 还有一个特别的地方是,指定了 probe_order 为 SND_SOC_COMP_ORDER_LATE,这使得它的 probe 和 init 操作,相对而言,执行的更晚,同时它在 snd_soc_card 中的位置也更靠后一点。如在 soc_probe_link_components() 函数 (位于 sound/soc/soc-core.c) 中:
  1. static int soc_probe_component(struct snd_soc_card *card,
  2.                                struct snd_soc_component *component)
  3. {
  4.         struct snd_soc_dapm_context *dapm =
  5.                 snd_soc_component_get_dapm(component);
  6.         struct snd_soc_dai *dai;
  7.         int probed = 0;
  8.         int ret;
  9.         if (!strcmp(component->name, "snd-soc-dummy"))
  10.                 return 0;
  11.         if (component->card) {
  12.                 if (component->card != card) {
  13.                         dev_err(component->dev,
  14.                                 "Trying to bind component to card "%s" but is already bound to card "%s"\n",
  15.                                 card->name, component->card->name);
  16.                         return -ENODEV;
  17.                 }
  18.                 return 0;
  19.         }
  20.         ret = snd_soc_component_module_get_when_probe(component);
  21.         if (ret < 0)
  22.                 return ret;
  23.         component->card = card;
  24.         soc_set_name_prefix(card, component);
  25.         soc_init_component_debugfs(component);
  26.         snd_soc_dapm_init(dapm, card, component);
  27.         ret = snd_soc_dapm_new_controls(dapm,
  28.                                         component->driver->dapm_widgets,
  29.                                         component->driver->num_dapm_widgets);
  30.         if (ret != 0) {
  31.                 dev_err(component->dev,
  32.                         "Failed to create new controls %d\n", ret);
  33.                 goto err_probe;
  34.         }
  35.         for_each_component_dais(component, dai) {
  36.                 ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
  37.                 if (ret != 0) {
  38.                         dev_err(component->dev,
  39.                                 "Failed to create DAI widgets %d\n", ret);
  40.                         goto err_probe;
  41.                 }
  42.         }
  43.         ret = snd_soc_component_probe(component);
  44.         if (ret < 0) {
  45.                 dev_err(component->dev,
  46.                         "ASoC: failed to probe component %d\n", ret);
  47.                 goto err_probe;
  48.         }
  49.         WARN(dapm->idle_bias_off &&
  50.              dapm->bias_level != SND_SOC_BIAS_OFF,
  51.              "codec %s can not start from non-off bias with idle_bias_off==1\n",
  52.              component->name);
  53.         probed = 1;
  54.         /*
  55.          * machine specific init
  56.          * see
  57.          *        snd_soc_component_set_aux()
  58.          */
  59.         ret = snd_soc_component_init(component);
  60.         if (ret < 0)
  61.                 goto err_probe;
  62.         ret = snd_soc_add_component_controls(component,
  63.                                              component->driver->controls,
  64.                                              component->driver->num_controls);
  65.         if (ret < 0)
  66.                 goto err_probe;
  67.         ret = snd_soc_dapm_add_routes(dapm,
  68.                                       component->driver->dapm_routes,
  69.                                       component->driver->num_dapm_routes);
  70.         if (ret < 0) {
  71.                 if (card->disable_route_checks) {
  72.                         dev_info(card->dev,
  73.                                  "%s: disable_route_checks set, ignoring errors on add_routes\n",
  74.                                  __func__);
  75.                 } else {
  76.                         dev_err(card->dev,
  77.                                 "%s: snd_soc_dapm_add_routes failed: %d\n",
  78.                                 __func__, ret);
  79.                         goto err_probe;
  80.                 }
  81.         }
  82.         /* see for_each_card_components */
  83.         list_add(&component->card_list, &card->component_dev_list);
  84. err_probe:
  85.         if (ret < 0)
  86.                 soc_remove_component(component, probed);
  87.         return ret;
  88. }
  89. . . . . . .
  90. static int soc_probe_link_components(struct snd_soc_card *card)
  91. {
  92.         struct snd_soc_component *component;
  93.         struct snd_soc_pcm_runtime *rtd;
  94.         int i, ret, order;
  95.         for_each_comp_order(order) {
  96.                 for_each_card_rtds(card, rtd) {
  97.                         for_each_rtd_components(rtd, i, component) {
  98.                                 if (component->driver->probe_order != order)
  99.                                         continue;
  100.                                 ret = soc_probe_component(card, component);
  101.                                 if (ret < 0)
  102.                                         return ret;
  103.                         }
  104.                 }
  105.         }
  106.         return 0;
  107. }
复制代码
这里用到的 for_each_comp_order() 宏定义 (位于 include/sound/soc-component.h) 如下:
  1. #define SND_SOC_COMP_ORDER_FIRST        -2
  2. #define SND_SOC_COMP_ORDER_EARLY        -1
  3. #define SND_SOC_COMP_ORDER_NORMAL         0
  4. #define SND_SOC_COMP_ORDER_LATE                 1
  5. #define SND_SOC_COMP_ORDER_LAST                 2
  6. #define for_each_comp_order(order)                \
  7.         for (order  = SND_SOC_COMP_ORDER_FIRST;        \
  8.              order <= SND_SOC_COMP_ORDER_LAST;        \
  9.              order++)
复制代码
回到通用 DMA 引擎驱动程序的 pcm_construct 操作 dmaengine_pcm_new() 函数,这个函数定义 (位于 sound/soc/soc-generic-dmaengine-pcm.c) 如下:
  1. static const struct snd_soc_component_driver dmaengine_pcm_component = {
  2.         .name                = SND_DMAENGINE_PCM_DRV_NAME,
  3.         .probe_order        = SND_SOC_COMP_ORDER_LATE,
  4.         .open                = dmaengine_pcm_open,
  5.         .close                = dmaengine_pcm_close,
  6.         .hw_params        = dmaengine_pcm_hw_params,
  7.         .trigger        = dmaengine_pcm_trigger,
  8.         .pointer        = dmaengine_pcm_pointer,
  9.         .pcm_construct        = dmaengine_pcm_new,
  10. };
  11. static const struct snd_soc_component_driver dmaengine_pcm_component_process = {
  12.         .name                = SND_DMAENGINE_PCM_DRV_NAME,
  13.         .probe_order        = SND_SOC_COMP_ORDER_LATE,
  14.         .open                = dmaengine_pcm_open,
  15.         .close                = dmaengine_pcm_close,
  16.         .hw_params        = dmaengine_pcm_hw_params,
  17.         .trigger        = dmaengine_pcm_trigger,
  18.         .pointer        = dmaengine_pcm_pointer,
  19.         .copy_user        = dmaengine_copy_user,
  20.         .pcm_construct        = dmaengine_pcm_new,
  21. };
复制代码
这个函数分别为播放和录制申请 DMA 通道,并分配 DMA buffer 用于用户空间应用程序、内核 ALSA/ASoc 框架和硬件设备之间的数据交换。snd_pcm_set_managed_buffer() 函数分配 DMA 缓冲区。snd_pcm_set_managed_buffer() 函数定义 (位于 sound/core/pcm_memory.c) 如下:
  1. int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd)
  2. {
  3.         struct snd_soc_component *component;
  4.         int ret;
  5.         int i;
  6.         for_each_rtd_components(rtd, i, component) {
  7.                 if (component->driver->pcm_construct) {
  8.                         ret = component->driver->pcm_construct(component, rtd);
  9.                         if (ret < 0)
  10.                                 return soc_component_ret(component, ret);
  11.                 }
  12.         }
  13.         return 0;
  14. }
复制代码
open 操作 dmaengine_pcm_open() 函数完成数据传输之前的准备工作,这个函数定义如下:
  1. static int dmaengine_pcm_new(struct snd_soc_component *component,
  2.                              struct snd_soc_pcm_runtime *rtd)
  3. {
  4.         struct dmaengine_pcm *pcm = soc_component_to_pcm(component);
  5.         const struct snd_dmaengine_pcm_config *config = pcm->config;
  6.         struct device *dev = component->dev;
  7.         struct snd_pcm_substream *substream;
  8.         size_t prealloc_buffer_size;
  9.         size_t max_buffer_size;
  10.         unsigned int i;
  11.         if (config && config->prealloc_buffer_size) {
  12.                 prealloc_buffer_size = config->prealloc_buffer_size;
  13.                 max_buffer_size = config->pcm_hardware->buffer_bytes_max;
  14.         } else {
  15.                 prealloc_buffer_size = 512 * 1024;
  16.                 max_buffer_size = SIZE_MAX;
  17.         }
  18.         for_each_pcm_streams(i) {
  19.                 substream = rtd->pcm->streams[i].substream;
  20.                 if (!substream)
  21.                         continue;
  22.                 if (!pcm->chan[i] && config && config->chan_names[i])
  23.                         pcm->chan[i] = dma_request_slave_channel(dev,
  24.                                 config->chan_names[i]);
  25.                 if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) {
  26.                         pcm->chan[i] = dmaengine_pcm_compat_request_channel(
  27.                                 component, rtd, substream);
  28.                 }
  29.                 if (!pcm->chan[i]) {
  30.                         dev_err(component->dev,
  31.                                 "Missing dma channel for stream: %d\n", i);
  32.                         return -EINVAL;
  33.                 }
  34.                 snd_pcm_set_managed_buffer(substream,
  35.                                 SNDRV_DMA_TYPE_DEV_IRAM,
  36.                                 dmaengine_dma_dev(pcm, substream),
  37.                                 prealloc_buffer_size,
  38.                                 max_buffer_size);
  39.                 if (!dmaengine_pcm_can_report_residue(dev, pcm->chan[i]))
  40.                         pcm->flags |= SND_DMAENGINE_PCM_FLAG_NO_RESIDUE;
  41.                 if (rtd->pcm->streams[i].pcm->name[0] == '\0') {
  42.                         strscpy_pad(rtd->pcm->streams[i].pcm->name,
  43.                                     rtd->pcm->streams[i].pcm->id,
  44.                                     sizeof(rtd->pcm->streams[i].pcm->name));
  45.                 }
  46.         }
  47.         return 0;
  48. }
复制代码
这里调用 snd_soc_dai_get_dma_data() 函数获得类型为 struct snd_dmaengine_dai_dma_data 的 DMA 数据。这是通用 DMA 引擎驱动程序和 I2S 等 DAI 驱动程序的一个约定,即需要在 DAI 驱动程序的 probe 操作中设置 DMA 数据,类似于下面这样:
  1. static void preallocate_pages(struct snd_pcm_substream *substream,
  2.                               int type, struct device *data,
  3.                               size_t size, size_t max, bool managed)
  4. {
  5.         if (snd_BUG_ON(substream->dma_buffer.dev.type))
  6.                 return;
  7.         substream->dma_buffer.dev.type = type;
  8.         substream->dma_buffer.dev.dev = data;
  9.         if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
  10.                 preallocate_pcm_pages(substream, size);
  11.         if (substream->dma_buffer.bytes > 0)
  12.                 substream->buffer_bytes_max = substream->dma_buffer.bytes;
  13.         substream->dma_max = max;
  14.         if (max > 0)
  15.                 preallocate_info_init(substream);
  16.         if (managed)
  17.                 substream->managed_buffer_alloc = 1;
  18. }
  19. . . . . . .
  20. void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,
  21.                                 struct device *data, size_t size, size_t max)
  22. {
  23.         preallocate_pages(substream, type, data, size, max, true);
  24. }
  25. EXPORT_SYMBOL(snd_pcm_set_managed_buffer);
复制代码
dmaengine_pcm_open() 函数主要是为流设置了运行时硬件参数。
对于其它操作,暂时不做太多说明。
Done.

来源:https://www.cnblogs.com/wolfcs/p/17658297.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具