zynq audio pcm DMA

本文详细介绍了在Linux内核中注册基于DMA引擎的PCM设备的过程。从DMA控制器的配置开始,逐步解析了DMA通道的请求及PCM设备的初始化过程。深入探讨了设备树在DMA通道分配中的作用。

本篇接着上一篇,概述pcm调用DMA操作流程,

接着ynq alsa说起

 ​181 static int axi_i2s_probe(struct platform_device *pdev)
182 {
183     struct resource *res;
184     struct axi_i2s *i2s;

...

239     ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
240     if (ret)

....
}

注册一个基于dmaengine的PCM设备,并且也注册了注销接口。

​140 int devm_snd_dmaengine_pcm_register(struct device *dev,
141     const struct snd_dmaengine_pcm_config *config, unsigned int flags)
142 {
143     struct device **ptr;
144     int ret;
145 
146     ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);
147     if (!ptr)
148         return -ENOMEM;
149 
150     ret = snd_dmaengine_pcm_register(dev, config, flags);
151     if (ret == 0) {
152         *ptr = dev;
153         devres_add(dev, ptr);
154     } else {
155         devres_free(ptr);
156     }
157 
158     return ret;
159 }
160 EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register);

devres_alloc注册了释放接口。zynqzed传递进来的阐述如下:

 ​ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);

snd_dmaengine_pcm_register注册一个基于dmaengine的PCM设备。dev是linux内核表示设备的通用结构体。config参数是NULL,flags参数是0.

​408 /**
409  * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
410  * @dev: The parent device for the PCM device
411  * @config: Platform specific PCM configuration
412  * @flags: Platform specific quirks
413  */
414 int snd_dmaengine_pcm_register(struct device *dev,
415     const struct snd_dmaengine_pcm_config *config, unsigned int flags)
416 {
417     struct dmaengine_pcm *pcm;
418     int ret;
419 
420     pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
421     if (!pcm)
422         return -ENOMEM;
423 
424     pcm->config = config;
425     pcm->flags = flags;
426 
427     ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
428     if (ret)
429         goto err_free_dma;
430 
431     ret = snd_soc_add_platform(dev, &pcm->platform,
432         &dmaengine_pcm_platform);
433     if (ret)
434         goto err_free_dma;
435 
436     return 0;
437 
438 err_free_dma:
439     dmaengine_pcm_release_chan(pcm);
440     kfree(pcm);
441     return ret;
442 }
443 EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register);
首先该函数分配了dmaengine_pcm的内存空间。然后以申请到的pcm为参数调用dmaengine_pcm_request_chan_of,一个DMA控制器有8个channel,每两个作为一组,既可以输出也可以输入,但它们的控制代码是一样的,并且在DMA注册过程中,已经使DMA处于可用状态了,这里直接申请一个channel作为PCM传递的DMA通道。

 ​struct dmaengine_pcm {
	struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1];
	const struct snd_dmaengine_pcm_config *config;
	struct snd_soc_platform platform;
	unsigned int flags;
};
其相关的数据结构体拓扑图如下:

接着来看dmaengine_pcm_request_chan_of函数。

344 static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
345     struct device *dev, const struct snd_dmaengine_pcm_config *config)
346 {
347     unsigned int i;
348     const char *name;
...
368     for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE;
369          i++) {
370         if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
371             name = "rx-tx";
372         else
373             name = dmaengine_pcm_dma_channel_names[i];
374         if (config && config->chan_names[i])
375             name = config->chan_names[i];
376         chan = dma_request_slave_channel_reason(dev, name);
377         if (IS_ERR(chan)) {
378             if (PTR_ERR(chan) == -EPROBE_DEFER)
379                 return -EPROBE_DEFER;
380             pcm->chan[i] = NULL;
381         } else {
382             pcm->chan[i] = chan;
383         }
384         if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
385             break;
386     }

388     if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
389         pcm->chan[1] = pcm->chan[0];
390 
391     return 0;
392 }<br data-mce-bogus="1" />
368行,pcm对应playback和capture两种PCM数据流。

370行根据传递进来的flag,解析playback和capture是否共用DMA,如果共用,则将channel的名字设置为rx-tx。否则先处理“tx”后处理“rx”,

376行尝试分配独享的DMA通道,name是slave的名称,分配依据设备树来。

设备树
115         axi_i2s_0: axi-i2s@0x77600000 {  
116             compatible = "adi,axi-i2s-1.00.a";  
117             reg = <0x77600000 0x1000>;  
118             dmas = <&dmac_s 1 &dmac_s 2>;  
119             dma-names = "tx", "rx";  
120             clocks = <&clkc 15>, <&audio_clock>;  
121             clock-names = "axi", "ref";  
122         };  
<pre name="code" class="plain">324         dmac_s: dmac@f8003000 {  
325             compatible = "arm,pl330", "arm,primecell";  
326             reg = <0xf8003000 0x1000>;  
327             interrupt-parent = <&intc>;  
328             interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3",  
329                 "dma4", "dma5", "dma6", "dma7";  
330             interrupts = <0 13 4>,  
331                          <0 14 4>, <0 15 4>,  
332                          <0 16 4>, <0 17 4>,  
333                          <0 40 4>, <0 41 4>,  
334                          <0 42 4>, <0 43 4>;  
335             #dma-cells = <1>;  
336             #dma-channels = <8>;  
337             #dma-requests = <4>;  
338             clocks = <&clkc 27>;  
339             clock-names = "apb_pclk";  
340         };  


设备树的119行,明确给出了设备树的名称。

<pre name="code" class="cpp">struct dma_chan *dma_request_slave_channel_reason(struct device *dev,
						  const char *name)
{
	/* If device-tree is present get slave info from here */
	if (dev->of_node)
		return of_dma_request_slave_channel(dev->of_node, name);

	/* If device was enumerated by ACPI get slave info from here */
	if (ACPI_HANDLE(dev))
		return acpi_dma_request_slave_chan_by_name(dev, name);

	return ERR_PTR(-ENODEV);
}

of_dma_request_slave_channel就是根据设备树,获得对应的dma channel,由于其细节部分主要是解析设备树,这里不展开。

dmaengine_pcm_request_chan_of的382行将取得的DMAchannel存放在pcm的数据结构里。

snd_dmaengine_pcm_register对platform进行赋值。首先看dmaengine_pcm_platform这个参数的意义。

<pre name="code" class="cpp">static const struct snd_pcm_ops dmaengine_pcm_ops = {
	.open		= dmaengine_pcm_open,
	.close		= snd_dmaengine_pcm_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= dmaengine_pcm_hw_params,
	.hw_free	= snd_pcm_lib_free_pages,
	.trigger	= snd_dmaengine_pcm_trigger,
	.pointer	= dmaengine_pcm_pointer,
};

static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
	.component_driver = {
		.probe_order = SND_SOC_COMP_ORDER_LATE,
	},
	.ops		= &dmaengine_pcm_ops,
	.pcm_new	= dmaengine_pcm_new,
};

上面的字段主要就是给了dmaengine pcm 平台驱动和平台驱动的调用方法。

2855 int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
2856         const struct snd_soc_platform_driver *platform_drv)
2857 {
2858     int ret;
2859 
2860     ret = snd_soc_component_initialize(&platform->component,
2861             &platform_drv->component_driver, dev);
2862     if (ret)
2863         return ret;
2864 
2865     platform->dev = dev;
2866     platform->driver = platform_drv;
2867 
2868     if (platform_drv->probe)
2869         platform->component.probe = snd_soc_platform_drv_probe;
2870     if (platform_drv->remove)
2871         platform->component.remove = snd_soc_platform_drv_remove;
2872 
2873 #ifdef CONFIG_DEBUG_FS
2874     platform->component.debugfs_prefix = "platform";
2875 #endif
2876 
2877     mutex_lock(&client_mutex);
2878     snd_soc_component_add_unlocked(&platform->component);
2879     list_add(&platform->list, &platform_list);
2880     mutex_unlock(&client_mutex);
2881 
2882     dev_dbg(dev, "ASoC: Registered platform '%s'\n",
2883         platform->component.name);
2884 
2885     return 0;
2886 }
2887 EXPORT_SYMBOL_GPL(snd_soc_add_platform);

后续音频数据的操作就是通过这里的dmaengine_pcm_ops完成的。

<4>[<c0015868>] (unwind_backtrace) from [<c0012594>] (show_stack+0x10/0x14)
<4>[<c0012594>] (show_stack) from [<c01a254c>] (dump_stack+0x80/0xcc)
<4>[<c01a254c>] (dump_stack) from [<c03c10d8>] (dmaengine_pcm_open+0x2c/0x1ec)
<4>[<c03c10d8>] (dmaengine_pcm_open) from [<c03bd51c>] (soc_pcm_open+0x118/0x7a8)
<4>[<c03bd51c>] (soc_pcm_open) from [<c03aad44>] (snd_pcm_open_substream+0x48/0x90)
<4>[<c03aad44>] (snd_pcm_open_substream) from [<c03aae30>] (snd_pcm_open+0xa4/0x1cc)
<4>[<c03aae30>] (snd_pcm_open) from [<c03aaff0>] (snd_pcm_playback_open+0x3c/0x5c)
<4>[<c03aaff0>] (snd_pcm_playback_open) from [<c00cc6a8>] (chrdev_open+0x138/0x164)
<4>[<c00cc6a8>] (chrdev_open) from [<c00c7470>] (do_dentry_open+0x1d8/0x2ec)
<4>[<c00c7470>] (do_dentry_open) from [<c00d4014>] (path_openat+0xaf4/0xd90)
<4>[<c00d4014>] (path_openat) from [<c00d5830>] (do_filp_open+0x38/0x84)
<4>[<c00d5830>] (do_filp_open) from [<c00c8670>] (do_sys_open+0x11c/0x1bc)
<4>[<c00c8670>] (do_sys_open) from [<c000edc0>] (ret_fast_syscall+0x0/0x3c)
在应用程序打开pcm设备时,


就是打开这里的pcm设备。

/proc/asound/下的内容是声卡相关的信息。

一个简单的alsa的应用程序如下:

#define ALSA_PCM_NEW_HW_PARAMS_API    
    
#include <alsa/asoundlib.h>    
int main()    
{    
    long loops;    
    int rc;    
    int size;    
    snd_pcm_t *handle;    
    snd_pcm_hw_params_t *params;    
    unsigned int val;    
    int dir;    
    snd_pcm_uframes_t frames;    
    char *buffer;    
    
    /* Open PCM device for playback */    
    rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);    
    if (rc < 0) {    
        printf("unable to open pcm device: %s\n", snd_strerror(rc));    
        exit(1);    
    }    
    
    snd_pcm_hw_params_alloca(¶ms);    
    
    snd_pcm_hw_params_any(handle, params);    
    
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    
    
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    
    
    snd_pcm_hw_params_set_channels(handle, params, 2);    
    
    val = 44100;    
    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    
    
    frames = 32;    
    snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);    
    
    rc = snd_pcm_hw_params(handle, params);    
    if (rc < 0) {    
        printf("unable to set hw parameters: %s\n", snd_strerror(rc));    
        exit(1);    
    }    
    
    snd_pcm_hw_params_get_period_size(params, &frames, &dir);    
    
    size = frames * 4;    
    buffer = (char *)malloc(size);    
    
    snd_pcm_hw_params_get_period_time(params, &val, &dir);    
    loops = 5000000 / val;    
    
    while (loops > 0) {    
        loops--;    
        rc = read(0, buffer, size);    
    
        rc = snd_pcm_writei(handle, buffer, frames);    
        if (rc == -EPIPE) {    
            printf("underrun occured\n");    
        }    
        else if (rc < 0) {    
            printf("error from writei: %s\n", snd_strerror(rc));    
        }    
    }    
    
    snd_pcm_drain(handle);    
    snd_pcm_close(handle);    
    free(buffer);    
    
    return 0;    
}  




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值