本篇接着上一篇,概述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;
}