Linux 内核 ASoC DMA 引擎驱动程序

news/2024/12/23 8:48:33/

Linux 内核 ASoC 框架,在概念上将嵌入式音频系统拆分为多个可复用的组件驱动程序,包括 Codec 类驱动程序、平台类驱动程序和机器类驱动程序。在实现上,机器类驱动程序用 struct snd_soc_cardstruct snd_soc_dai_link 结构描述,属于平台类驱动程序的 DMA 引擎驱动程序由 struct snd_soc_component_driver 结构描述,codec 类驱动程序和 I2S 等驱动程序,由 struct snd_soc_component_driverstruct snd_soc_dai_driverstruct 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

static const struct snd_soc_component_driver pxa2xx_soc_platform = {.pcm_construct	= pxa2xx_soc_pcm_new,.pcm_destruct	= pxa2xx_soc_pcm_free,.open		= pxa2xx_soc_pcm_open,.close		= pxa2xx_soc_pcm_close,.hw_params	= pxa2xx_soc_pcm_hw_params,.hw_free	= pxa2xx_soc_pcm_hw_free,.prepare	= pxa2xx_soc_pcm_prepare,.trigger	= pxa2xx_soc_pcm_trigger,.pointer	= pxa2xx_soc_pcm_pointer,.mmap		= pxa2xx_soc_pcm_mmap,
};static int pxa2xx_soc_platform_probe(struct platform_device *pdev)
{return devm_snd_soc_register_component(&pdev->dev, &pxa2xx_soc_platform,NULL, 0);
}static struct platform_driver pxa_pcm_driver = {.driver = {.name = "pxa-pcm-audio",},.probe = pxa2xx_soc_platform_probe,
};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 有如下的代码片段:

SND_SOC_DAILINK_DEFS(ac97,DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")),DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));SND_SOC_DAILINK_DEFS(ac97_aux,DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-aux")),DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));static struct snd_soc_dai_link e800_dai[] = {{.name = "AC97",.stream_name = "AC97 HiFi",SND_SOC_DAILINK_REG(ac97),},{.name = "AC97 Aux",.stream_name = "AC97 Aux",SND_SOC_DAILINK_REG(ac97_aux),},
};static struct snd_soc_card e800 = {.name = "Toshiba e800",.owner = THIS_MODULE,.dai_link = e800_dai,.num_links = ARRAY_SIZE(e800_dai),.dapm_widgets = e800_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(e800_dapm_widgets),.dapm_routes = audio_map,.num_dapm_routes = ARRAY_SIZE(audio_map),
};

SND_SOC_DAILINK_DEFS() 宏用于为 struct snd_soc_dai_link 方便地定义引用的 cpuscodecsplatformsstruct snd_soc_dai_link_component 数组,其中用于定义 platformsDAILINK_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

static const struct snd_pcm_hardware snd_rockchip_hardware = {.info			= SNDRV_PCM_INFO_MMAP |SNDRV_PCM_INFO_MMAP_VALID |SNDRV_PCM_INFO_PAUSE |SNDRV_PCM_INFO_RESUME |SNDRV_PCM_INFO_INTERLEAVED,.period_bytes_min	= 32,.period_bytes_max	= 8192,.periods_min		= 1,.periods_max		= 52,.buffer_bytes_max	= 64 * 1024,.fifo_size		= 32,
};static const struct snd_dmaengine_pcm_config rk_dmaengine_pcm_config = {.pcm_hardware = &snd_rockchip_hardware,.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,.prealloc_buffer_size = 32 * 1024,
};int rockchip_pcm_platform_register(struct device *dev)
{return devm_snd_dmaengine_pcm_register(dev, &rk_dmaengine_pcm_config,SND_DMAENGINE_PCM_FLAG_COMPAT);
}
EXPORT_SYMBOL_GPL(rockchip_pcm_platform_register);

这里的 rockchip_pcm_platform_register() 函数在 I2S 驱动程序的 probe 操作 (位于 sound/soc/rockchip/rockchip_i2s.c) 中调用:

static int rockchip_i2s_probe(struct platform_device *pdev)
{. . . . . .ret = devm_snd_soc_register_component(&pdev->dev,&rockchip_i2s_component,soc_dai, 1);if (ret) {dev_err(&pdev->dev, "Could not register DAI\n");goto err_suspend;}ret = rockchip_pcm_platform_register(&pdev->dev);if (ret) {dev_err(&pdev->dev, "Could not register PCM\n");goto err_suspend;}return 0;. . . . . .return ret;
}

rockchip_pcm_platform_register() 函数调用 devm_snd_dmaengine_pcm_register() 函数注册通用 DMA 引擎驱动程序。devm_snd_dmaengine_pcm_register() 函数定义 (位于 sound/soc/soc-devres.c) 如下:

static void devm_dmaengine_pcm_release(struct device *dev, void *res)
{snd_dmaengine_pcm_unregister(*(struct device **)res);
}/*** devm_snd_dmaengine_pcm_register - resource managed dmaengine PCM registration* @dev: The parent device for the PCM device* @config: Platform specific PCM configuration* @flags: Platform specific quirks** Register a dmaengine based PCM device with automatic unregistration when the* device is unregistered.*/
int devm_snd_dmaengine_pcm_register(struct device *dev,const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{struct device **ptr;int ret;ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);if (!ptr)return -ENOMEM;ret = snd_dmaengine_pcm_register(dev, config, flags);if (ret == 0) {*ptr = dev;devres_add(dev, ptr);} else {devres_free(ptr);}return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register);#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) 如下:

static const struct snd_soc_component_driver dmaengine_pcm_component = {.name		= SND_DMAENGINE_PCM_DRV_NAME,.probe_order	= SND_SOC_COMP_ORDER_LATE,.open		= dmaengine_pcm_open,.close		= dmaengine_pcm_close,.hw_params	= dmaengine_pcm_hw_params,.trigger	= dmaengine_pcm_trigger,.pointer	= dmaengine_pcm_pointer,.pcm_construct	= dmaengine_pcm_new,
};static const struct snd_soc_component_driver dmaengine_pcm_component_process = {.name		= SND_DMAENGINE_PCM_DRV_NAME,.probe_order	= SND_SOC_COMP_ORDER_LATE,.open		= dmaengine_pcm_open,.close		= dmaengine_pcm_close,.hw_params	= dmaengine_pcm_hw_params,.trigger	= dmaengine_pcm_trigger,.pointer	= dmaengine_pcm_pointer,.copy_user	= dmaengine_copy_user,.pcm_construct	= dmaengine_pcm_new,
};static const char * const dmaengine_pcm_dma_channel_names[] = {[SNDRV_PCM_STREAM_PLAYBACK] = "tx",[SNDRV_PCM_STREAM_CAPTURE] = "rx",
};static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,struct device *dev, const struct snd_dmaengine_pcm_config *config)
{unsigned int i;const char *name;struct dma_chan *chan;if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || (!dev->of_node &&!(config && config->dma_dev && config->dma_dev->of_node)))return 0;if (config && config->dma_dev) {/** If this warning is seen, it probably means that your Linux* device structure does not match your HW device structure.* It would be best to refactor the Linux device structure to* correctly match the HW structure.*/dev_warn(dev, "DMA channels sourced from device %s",dev_name(config->dma_dev));dev = config->dma_dev;}for_each_pcm_streams(i) {if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)name = "rx-tx";elsename = dmaengine_pcm_dma_channel_names[i];if (config && config->chan_names[i])name = config->chan_names[i];chan = dma_request_chan(dev, name);if (IS_ERR(chan)) {/** Only report probe deferral errors, channels* might not be present for devices that* support only TX or only RX.*/if (PTR_ERR(chan) == -EPROBE_DEFER)return -EPROBE_DEFER;pcm->chan[i] = NULL;} else {pcm->chan[i] = chan;}if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)break;}if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)pcm->chan[1] = pcm->chan[0];return 0;
}static void dmaengine_pcm_release_chan(struct dmaengine_pcm *pcm)
{unsigned int i;for_each_pcm_streams(i) {if (!pcm->chan[i])continue;dma_release_channel(pcm->chan[i]);if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)break;}
}/*** snd_dmaengine_pcm_register - Register a dmaengine based PCM device* @dev: The parent device for the PCM device* @config: Platform specific PCM configuration* @flags: Platform specific quirks*/
int snd_dmaengine_pcm_register(struct device *dev,const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{const struct snd_soc_component_driver *driver;struct dmaengine_pcm *pcm;int ret;pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);if (!pcm)return -ENOMEM;#ifdef CONFIG_DEBUG_FSpcm->component.debugfs_prefix = "dma";
#endifpcm->config = config;pcm->flags = flags;ret = dmaengine_pcm_request_chan_of(pcm, dev, config);if (ret)goto err_free_dma;if (config && config->process)driver = &dmaengine_pcm_component_process;elsedriver = &dmaengine_pcm_component;ret = snd_soc_component_initialize(&pcm->component, driver, dev);if (ret)goto err_free_dma;ret = snd_soc_add_component(&pcm->component, NULL, 0);if (ret)goto err_free_dma;return 0;err_free_dma:dmaengine_pcm_release_chan(pcm);kfree(pcm);return ret;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register);/*** snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device* @dev: Parent device the PCM was register with** Removes a dmaengine based PCM device previously registered with* snd_dmaengine_pcm_register.*/
void snd_dmaengine_pcm_unregister(struct device *dev)
{struct snd_soc_component *component;struct dmaengine_pcm *pcm;component = snd_soc_lookup_component(dev, SND_DMAENGINE_PCM_DRV_NAME);if (!component)return;pcm = soc_component_to_pcm(component);snd_soc_unregister_component_by_driver(dev, component->driver);dmaengine_pcm_release_chan(pcm);kfree(pcm);
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister);MODULE_LICENSE("GPL");

snd_dmaengine_pcm_register() 函数的执行过程如下:

  1. 动态分配一个 struct dmaengine_pcm 结构对象,并初始化其 configflags 等字段,struct dmaengine_pcm 结构定义 (位于 include/sound/dmaengine_pcm.h) 如下:
struct dmaengine_pcm {struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1];const struct snd_dmaengine_pcm_config *config;struct snd_soc_component component;unsigned int flags;
};

这个结构有一个 struct snd_soc_component 结构成员;

  1. 为数据的发送和接收申请 DMA 通道,这个需要在设备树的设备节点定义中,指定发送和接收引用的 DMA 通道,像下面这样:
	i2s0_8ch: i2s@fe470000 {. . . . . .dmas = <&dmac0 0>, <&dmac0 1>;dma-names = "tx", "rx";. . . . . .};
  1. 根据传入的 config 参数,选择 struct snd_soc_component_driverdmaengine_pcm_component_processdmaengine_pcm_component 仅有的区别是,前者多定义了一个 copy_user 操作;

  2. 初始化并添加 struct snd_soc_component 结构对象,有一个函数 (位于 include/sound/dmaengine_pcm.h) 可以通过 struct snd_soc_component 结构对象获得 struct dmaengine_pcm 结构对象:

static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p)
{return container_of(p, struct dmaengine_pcm, component);
}

通用 DMA 引擎驱动程序支持的操作由 struct snd_soc_component_driver 定义。在全局 component 链表中,相应的 struct snd_soc_component 由注册它的 dev 标识。

使用了通用 DMA 引擎驱动程序的 ASoC 机器驱动程序,示例 (位于 sound/soc/rockchip/rockchip_rt5645.c) 如下:

SND_SOC_DAILINK_DEFS(pcm,DAILINK_COMP_ARRAY(COMP_EMPTY()),DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5645-aif1")),DAILINK_COMP_ARRAY(COMP_EMPTY()));static struct snd_soc_dai_link rk_dailink = {.name = "rt5645",.stream_name = "rt5645 PCM",.init = rk_init,.ops = &rk_aif1_ops,/* set rt5645 as slave */.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |SND_SOC_DAIFMT_CBS_CFS,SND_SOC_DAILINK_REG(pcm),
};static struct snd_soc_card snd_soc_card_rk = {.name = "I2S-RT5650",.owner = THIS_MODULE,.dai_link = &rk_dailink,.num_links = 1,.dapm_widgets = rk_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets),.dapm_routes = rk_audio_map,.num_dapm_routes = ARRAY_SIZE(rk_audio_map),.controls = rk_mc_controls,.num_controls = ARRAY_SIZE(rk_mc_controls),
};. . . . . .
static int snd_rk_mc_probe(struct platform_device *pdev)
{. . . . . .rk_dailink.cpus->of_node = of_parse_phandle(np,"rockchip,i2s-controller", 0);if (!rk_dailink.cpus->of_node) {dev_err(&pdev->dev,"Property 'rockchip,i2s-controller' missing or invalid\n");ret = -EINVAL;goto put_codec_of_node;}rk_dailink.platforms->of_node = rk_dailink.cpus->of_node;. . . . . .
}

这里为 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_orderSND_SOC_COMP_ORDER_LATE,这使得它的 probeinit 操作,相对而言,执行的更晚,同时它在 snd_soc_card 中的位置也更靠后一点。如在 soc_probe_link_components() 函数 (位于 sound/soc/soc-core.c) 中:

static int soc_probe_component(struct snd_soc_card *card,struct snd_soc_component *component)
{struct snd_soc_dapm_context *dapm =snd_soc_component_get_dapm(component);struct snd_soc_dai *dai;int probed = 0;int ret;if (!strcmp(component->name, "snd-soc-dummy"))return 0;if (component->card) {if (component->card != card) {dev_err(component->dev,"Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n",card->name, component->card->name);return -ENODEV;}return 0;}ret = snd_soc_component_module_get_when_probe(component);if (ret < 0)return ret;component->card = card;soc_set_name_prefix(card, component);soc_init_component_debugfs(component);snd_soc_dapm_init(dapm, card, component);ret = snd_soc_dapm_new_controls(dapm,component->driver->dapm_widgets,component->driver->num_dapm_widgets);if (ret != 0) {dev_err(component->dev,"Failed to create new controls %d\n", ret);goto err_probe;}for_each_component_dais(component, dai) {ret = snd_soc_dapm_new_dai_widgets(dapm, dai);if (ret != 0) {dev_err(component->dev,"Failed to create DAI widgets %d\n", ret);goto err_probe;}}ret = snd_soc_component_probe(component);if (ret < 0) {dev_err(component->dev,"ASoC: failed to probe component %d\n", ret);goto err_probe;}WARN(dapm->idle_bias_off &&dapm->bias_level != SND_SOC_BIAS_OFF,"codec %s can not start from non-off bias with idle_bias_off==1\n",component->name);probed = 1;/** machine specific init* see*	snd_soc_component_set_aux()*/ret = snd_soc_component_init(component);if (ret < 0)goto err_probe;ret = snd_soc_add_component_controls(component,component->driver->controls,component->driver->num_controls);if (ret < 0)goto err_probe;ret = snd_soc_dapm_add_routes(dapm,component->driver->dapm_routes,component->driver->num_dapm_routes);if (ret < 0) {if (card->disable_route_checks) {dev_info(card->dev,"%s: disable_route_checks set, ignoring errors on add_routes\n",__func__);} else {dev_err(card->dev,"%s: snd_soc_dapm_add_routes failed: %d\n",__func__, ret);goto err_probe;}}/* see for_each_card_components */list_add(&component->card_list, &card->component_dev_list);err_probe:if (ret < 0)soc_remove_component(component, probed);return ret;
}. . . . . .
static int soc_probe_link_components(struct snd_soc_card *card)
{struct snd_soc_component *component;struct snd_soc_pcm_runtime *rtd;int i, ret, order;for_each_comp_order(order) {for_each_card_rtds(card, rtd) {for_each_rtd_components(rtd, i, component) {if (component->driver->probe_order != order)continue;ret = soc_probe_component(card, component);if (ret < 0)return ret;}}}return 0;
}

这里用到的 for_each_comp_order() 宏定义 (位于 include/sound/soc-component.h) 如下:

#define SND_SOC_COMP_ORDER_FIRST	-2
#define SND_SOC_COMP_ORDER_EARLY	-1
#define SND_SOC_COMP_ORDER_NORMAL	 0
#define SND_SOC_COMP_ORDER_LATE		 1
#define SND_SOC_COMP_ORDER_LAST		 2#define for_each_comp_order(order)		\for (order  = SND_SOC_COMP_ORDER_FIRST;	\order <= SND_SOC_COMP_ORDER_LAST;	\order++)

Linux 内核 ASoC 通用 DMA 引擎驱动程序的操作

通用 DMA 引擎驱动程序提供的操作如下:

static const struct snd_soc_component_driver dmaengine_pcm_component = {.name		= SND_DMAENGINE_PCM_DRV_NAME,.probe_order	= SND_SOC_COMP_ORDER_LATE,.open		= dmaengine_pcm_open,.close		= dmaengine_pcm_close,.hw_params	= dmaengine_pcm_hw_params,.trigger	= dmaengine_pcm_trigger,.pointer	= dmaengine_pcm_pointer,.pcm_construct	= dmaengine_pcm_new,
};static const struct snd_soc_component_driver dmaengine_pcm_component_process = {.name		= SND_DMAENGINE_PCM_DRV_NAME,.probe_order	= SND_SOC_COMP_ORDER_LATE,.open		= dmaengine_pcm_open,.close		= dmaengine_pcm_close,.hw_params	= dmaengine_pcm_hw_params,.trigger	= dmaengine_pcm_trigger,.pointer	= dmaengine_pcm_pointer,.copy_user	= dmaengine_copy_user,.pcm_construct	= dmaengine_pcm_new,
};

这些操作中,最早被调用的是 pcm_construct 操作,也就是 dmaengine_pcm_new() 函数。在机器类驱动程序中,调用 devm_snd_soc_register_card() 函数注册声卡,在这个函数中有如下调用过程,snd_soc_register_card() -> snd_soc_bind_card() -> soc_init_pcm_runtime() -> soc_new_pcm() -> snd_soc_pcm_component_new()snd_soc_pcm_component_new() 函数执行各个 struct snd_soc_component_driverpcm_construct 操作。snd_soc_pcm_component_new() 函数定义 (位于 sound/soc/soc-component.c) 如下:

int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd)
{struct snd_soc_component *component;int ret;int i;for_each_rtd_components(rtd, i, component) {if (component->driver->pcm_construct) {ret = component->driver->pcm_construct(component, rtd);if (ret < 0)return soc_component_ret(component, ret);}}return 0;
}

回到通用 DMA 引擎驱动程序的 pcm_construct 操作 dmaengine_pcm_new() 函数,这个函数定义 (位于 sound/soc/soc-generic-dmaengine-pcm.c) 如下:

static int dmaengine_pcm_new(struct snd_soc_component *component,struct snd_soc_pcm_runtime *rtd)
{struct dmaengine_pcm *pcm = soc_component_to_pcm(component);const struct snd_dmaengine_pcm_config *config = pcm->config;struct device *dev = component->dev;struct snd_pcm_substream *substream;size_t prealloc_buffer_size;size_t max_buffer_size;unsigned int i;if (config && config->prealloc_buffer_size) {prealloc_buffer_size = config->prealloc_buffer_size;max_buffer_size = config->pcm_hardware->buffer_bytes_max;} else {prealloc_buffer_size = 512 * 1024;max_buffer_size = SIZE_MAX;}for_each_pcm_streams(i) {substream = rtd->pcm->streams[i].substream;if (!substream)continue;if (!pcm->chan[i] && config && config->chan_names[i])pcm->chan[i] = dma_request_slave_channel(dev,config->chan_names[i]);if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) {pcm->chan[i] = dmaengine_pcm_compat_request_channel(component, rtd, substream);}if (!pcm->chan[i]) {dev_err(component->dev,"Missing dma channel for stream: %d\n", i);return -EINVAL;}snd_pcm_set_managed_buffer(substream,SNDRV_DMA_TYPE_DEV_IRAM,dmaengine_dma_dev(pcm, substream),prealloc_buffer_size,max_buffer_size);if (!dmaengine_pcm_can_report_residue(dev, pcm->chan[i]))pcm->flags |= SND_DMAENGINE_PCM_FLAG_NO_RESIDUE;if (rtd->pcm->streams[i].pcm->name[0] == '\0') {strscpy_pad(rtd->pcm->streams[i].pcm->name,rtd->pcm->streams[i].pcm->id,sizeof(rtd->pcm->streams[i].pcm->name));}}return 0;
}

这个函数分别为播放和录制申请 DMA 通道,并分配 DMA buffer 用于用户空间应用程序、内核 ALSA/ASoc 框架和硬件设备之间的数据交换。snd_pcm_set_managed_buffer() 函数分配 DMA 缓冲区。snd_pcm_set_managed_buffer() 函数定义 (位于 sound/core/pcm_memory.c) 如下:

static void preallocate_pages(struct snd_pcm_substream *substream,int type, struct device *data,size_t size, size_t max, bool managed)
{if (snd_BUG_ON(substream->dma_buffer.dev.type))return;substream->dma_buffer.dev.type = type;substream->dma_buffer.dev.dev = data;if (size > 0 && preallocate_dma && substream->number < maximum_substreams)preallocate_pcm_pages(substream, size);if (substream->dma_buffer.bytes > 0)substream->buffer_bytes_max = substream->dma_buffer.bytes;substream->dma_max = max;if (max > 0)preallocate_info_init(substream);if (managed)substream->managed_buffer_alloc = 1;
}. . . . . .
void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,struct device *data, size_t size, size_t max)
{preallocate_pages(substream, type, data, size, max, true);
}
EXPORT_SYMBOL(snd_pcm_set_managed_buffer);

open 操作 dmaengine_pcm_open() 函数完成数据传输之前的准备工作,这个函数定义如下:

static int
dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component,struct snd_pcm_substream *substream)
{struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);struct dmaengine_pcm *pcm = soc_component_to_pcm(component);struct device *dma_dev = dmaengine_dma_dev(pcm, substream);struct dma_chan *chan = pcm->chan[substream->stream];struct snd_dmaengine_dai_dma_data *dma_data;struct snd_pcm_hardware hw;if (rtd->num_cpus > 1) {dev_err(rtd->dev,"%s doesn't support Multi CPU yet\n", __func__);return -EINVAL;}if (pcm->config && pcm->config->pcm_hardware)return snd_soc_set_runtime_hwparams(substream,pcm->config->pcm_hardware);dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);memset(&hw, 0, sizeof(hw));hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |SNDRV_PCM_INFO_INTERLEAVED;hw.periods_min = 2;hw.periods_max = UINT_MAX;hw.period_bytes_min = 256;hw.period_bytes_max = dma_get_max_seg_size(dma_dev);hw.buffer_bytes_max = SIZE_MAX;hw.fifo_size = dma_data->fifo_size;if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE)hw.info |= SNDRV_PCM_INFO_BATCH;/*** FIXME: Remove the return value check to align with the code* before adding snd_dmaengine_pcm_refine_runtime_hwparams* function.*/snd_dmaengine_pcm_refine_runtime_hwparams(substream,dma_data,&hw,chan);return snd_soc_set_runtime_hwparams(substream, &hw);
}static int dmaengine_pcm_open(struct snd_soc_component *component,struct snd_pcm_substream *substream)
{struct dmaengine_pcm *pcm = soc_component_to_pcm(component);struct dma_chan *chan = pcm->chan[substream->stream];int ret;ret = dmaengine_pcm_set_runtime_hwparams(component, substream);if (ret)return ret;return snd_dmaengine_pcm_open(substream, chan);
}

这里调用 snd_soc_dai_get_dma_data() 函数获得类型为 struct snd_dmaengine_dai_dma_data 的 DMA 数据。这是通用 DMA 引擎驱动程序和 I2S 等 DAI 驱动程序的一个约定,即需要在 DAI 驱动程序的 probe 操作中设置 DMA 数据,类似于下面这样:

struct i2s_dev {struct device *dev;. . . . . .struct snd_dmaengine_dai_dma_data capture_dma_data;struct snd_dmaengine_dai_dma_data playback_dma_data;. . . . . .
}. . . . . .
static int i2s_dai_probe(struct snd_soc_dai *dai)
{struct i2s_dev *i2s = snd_soc_dai_get_drvdata(dai);snd_soc_dai_init_dma_data(dai,i2s->has_playback ? &i2s->playback_dma_data : NULL,i2s->has_capture  ? &i2s->capture_dma_data  : NULL);return 0;
}. . . . . .
static int i2s_init_dai(struct i2s_dev *i2s, struct resource *res,struct snd_soc_dai_driver **dp)
{. . . . . .i2s->playback_dma_data.addr = res->start + I2S_TXFIFODATA;i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;i2s->playback_dma_data.maxburst = 8;. . . . . .i2s->capture_dma_data.addr = res->start + I2S_RXFIFODATA;i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;i2s->capture_dma_data.maxburst = 8;. . . . . .
}

dmaengine_pcm_open() 函数主要是为流设置了运行时硬件参数。

对于其它操作,暂时不做太多说明。

Done.


http://www.ppmy.cn/news/987668.html

相关文章

自动化测试——环境

一、搭建环境 1、安装Slenium pip install selenium 2、安装浏览器驱动-》查询浏览器版本-》下载对应版本驱动-》在path路径中配置&#xff08;浏览器更新需要重新下载&#xff09; pip install webdriver -helper&#xff08;自动化&#xff09;python3.9以上 pip install 安…

iperf3 编译安装及网讯WX1860千兆网口测试

iperf3 编译安装及网讯1860千兆网口测试 编译安装 安装包下载地址:https://github.com/esnet/iperf/archive/refs/tags/3.8.tar.gz 将安装包iperf-3.8.tar.gz拷贝测试系统盘桌面,使用如下命令进行编译安装: tar zxvf iperf-3.8.tar.gz cd iperf-3.8 ./configure make s…

1400*C. Computer Game

Example input 6 15 5 3 2 15 5 4 3 15 5 2 1 15 5 5 1 16 7 5 2 20 5 7 3 output 4 -1 5 2 0 1 解析&#xff1a; k个电&#xff0c; 第一种为 k>a 时&#xff0c;只玩游戏 k-a; 第二种&#xff0c;k>b,一边玩一边充电 k-b 问完成n轮游戏的情况下&#xff0c;优先第…

建木-进阶使用-自动化部署-流程自动化部署-节点化部署-将jar包运行在指定的服务器上-ssh文件书写-docker镜像创建

阿丹&#xff1a; 建木的搭建以及在jar包中的dockerfile文件的书写。已经完毕。现在开始使用建木的流程化部署来完成自动化部署。 进入我们自己配置的建木页面 新建一个分组&#xff0c;方便我们不同项目的管理 起名要求见名知意 使用图形项目 开始新建一个流程&#xff0c;…

JAVA基础-多线程入门(详解)

目录 引言 一&#xff0c;线程概念 二&#xff0c;创建线程 2.1&#xff0c;继承Thread类&#xff0c;重写run方法 2.2&#xff0c;实现Runnable接口&#xff0c;重写run方法&#xff0c;实现Runnable接口的实现类的实例对象作为Thread构造函 数的target 2.3&#xff0c;通…

带你读论文第三期:微软研究员、北大博士陈琪,荣获NeurIPS杰出论文奖

Datawhale干货 来源&#xff1a;WhalePaper&#xff0c;负责人&#xff1a;芙蕖 WhalePaper简介 由Datawhale团队成员发起&#xff0c;对目前学术论文中比较成熟的 Topic 和开源方案进行分享&#xff0c;通过一起阅读、分享论文学习的方式帮助大家更好地“高效全面自律”学习&…

论文分享:PowerTCP: Pushing the Performance Limits of Datacenter Networks

1 原论文的题目&#xff08;中英文&#xff09;、题目中包含了哪些关键词&#xff1f;这些关键词的相关知识分别是什么&#xff1f; 题目&#xff1a;PowerTCP: Pushing the Performance Limits of Datacenter Networks PowerTCP&#xff1a;逼近数据中心的网络性能极限 2 论…

HTML5 特性

HTML5 特性 1. 语义化标签 <header>&#xff1a;表示网页或某个区域的页眉部分&#xff0c;通常包含网站的标志、导航菜单等内容。<nav>&#xff1a;表示导航区域&#xff0c;用于包含网站的主要导航链接。<main>&#xff1a;表示网页的主要内容区域&#…