当前位置: 首页 > wzjs >正文

有偷菜餐厅城市建设的网站百度推广服务费3000元

有偷菜餐厅城市建设的网站,百度推广服务费3000元,wordpress变慢了,网页制作与网站建设考试答案文章目录 1. 前言2. ASoC 声卡驱动框架2.1 框架概览2.2 声卡的建立2.2.1 注册声卡组件2.2.1.1 注册 CPU 侧 DAI2.2.1.2 注册 CODEC 2.2.2 组装 ASoC 声卡 2.3 声卡的内存管理2.3.1 【DMA 内存管理组件】的注册和初始化2.3.1.1 公版的【DMA 内存管理组件】的注册和初始化2.3.1.2…

文章目录

  • 1. 前言
  • 2. ASoC 声卡驱动框架
    • 2.1 框架概览
    • 2.2 声卡的建立
      • 2.2.1 注册声卡组件
        • 2.2.1.1 注册 CPU 侧 DAI
        • 2.2.1.2 注册 CODEC
      • 2.2.2 组装 ASoC 声卡
    • 2.3 声卡的内存管理
      • 2.3.1 【DMA 内存管理组件】的注册和初始化
        • 2.3.1.1 `公版`的`【DMA 内存管理组件】`的注册和初始化
        • 2.3.1.2 `自定义`的`【DMA 内存管理组件】`的注册和初始化
      • 2.3.2 【DMA 内存管理组件】的绑定
      • 2.3.3 通过【DMA 内存管理组件】分配 DMA 内存
        • 2.3.3.1 注册声卡时预分配 DMA 内存
        • 2.3.3.2 ioctl(SNDRV_PCM_IOCTL_HW_PARAMS) 触发 DMA 内存分配
      • 2.3.4 设定 DMA 传输源、目的地址
      • 2.3.5 DMA 数据传输
        • 2.3.5.1 DMA 传输准备工作
        • 2.3.5.2 启动 DMA 传输
        • 2.3.5.3 循环 DMA 传输
  • 3. 其它
    • 3.1 声卡其它组成部分
    • 3.2 声卡用户空间接口
    • 3.2 ASoC 声卡框架目录组织

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. ASoC 声卡驱动框架

2.1 框架概览

ASoC(ALSA System-on-Chip) 声卡驱动框架,是对 ALSA 声卡驱动框架的解耦,常见于嵌入式设备,其包含部分:

1. CPU DAI 驱动也称为 Platform 驱动,驱动一般由 CPU 芯片厂家提供。2. CODEC 驱动一般是外接的 CODEC,驱动一般由 CODEC 厂家提供。3. Machine 驱动粘合层,负责将 CPU DAI 驱动 和 CODEC 驱动 组合到一起形成声卡驱动。

ASoC 声卡驱动框架如下图:

在这里插入图片描述

从前面可以看到,CPU DAI 驱动CODEC 驱动是彼此独立的,它们通过 Machine 驱动组合起来。在物理连接上,CPU 一侧的 DAICODEC 一侧的 DAI 相连。当然,一个声卡可以包含多个 CPU DAI + CODEC 的组合,本文不讨论这种场景。

2.2 声卡的建立

ASoC 声卡的建立,包含两大步:

1. 注册声卡组件包括注册 CPU DAI 和 CODEC 。2. 组装声卡将 CPU DAI 和 相连的 CODEC 组装成声卡。

2.2.1 注册声卡组件

ASoC 声卡组件用结构体 struct snd_soc_component 描述:

/* include/sound/soc.h */struct snd_soc_component {const char *name;...struct snd_soc_card *card; /* 关联的声卡对象 */...const struct snd_soc_component_driver *driver; /* ASoC 声卡组件驱动 */...struct snd_soc_codec *codec;/* 接口 */int (*probe)(struct snd_soc_component *);...
};

注册 ASoC 声卡组件,包括 CPU DAICODEC 的注册。ASoC 框架提供 API 接口 devm_snd_soc_register_component() / snd_soc_register_component() 注册声卡组件。

2.2.1.1 注册 CPU 侧 DAI

以全志的 I2S 接口为例,说明 CPU 侧 DAI 的注册过程:

/* sound/soc/sunxi/sun4i-i2s.c */static struct snd_soc_dai_driver sun4i_i2s_dai = {.probe = sun4i_i2s_dai_probe,.capture = {.stream_name = "Capture",.channels_min = 2,.channels_max = 2,.rates = SNDRV_PCM_RATE_8000_192000,.formats = SNDRV_PCM_FMTBIT_S16_LE,},.playback = {.stream_name = "Playback",.channels_min = 2,.channels_max = 2,.rates = SNDRV_PCM_RATE_8000_192000,.formats = SNDRV_PCM_FMTBIT_S16_LE,},.ops = &sun4i_i2s_dai_ops,.symmetric_rates = 1,
};static const struct snd_soc_component_driver sun4i_i2s_component = {.name	= "sun4i-dai",
};...static int sun4i_i2s_probe(struct platform_device *pdev)
{.../* 注册 CPU DAI */ret = devm_snd_soc_register_component(&pdev->dev,&sun4i_i2s_component,&sun4i_i2s_dai, 1);.../* 声卡 CPU DAI 的 DMA 初始化,这个后面 2.3 小节分析 */ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);...
}
/* sound/soc/soc-devres.c */int devm_snd_soc_register_component(struct device *dev,const struct snd_soc_component_driver *cmpnt_drv,struct snd_soc_dai_driver *dai_drv, int num_dai)
{...ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);...
}
/* sound/soc/soc-core.c */int snd_soc_register_component(struct device *dev,const struct snd_soc_component_driver *component_driver,struct snd_soc_dai_driver *dai_drv,int num_dai)
{struct snd_soc_component *component;int ret;/* 新建 ASoC 声卡组件对象 */component = kzalloc(sizeof(*component), GFP_KERNEL);.../* 初始化 ASoC 声卡组件对象 */ret = snd_soc_component_initialize(component, component_driver, dev);.../* 添加 CPU/CODEC DAI 到 ASoC 声卡组件对象 */ret = snd_soc_register_dais(component, dai_drv, num_dai, true);.../* 添加 ASoC 声卡组件对象 到 全局组件对象列表 @component_list */snd_soc_component_add(component);return 0;...
}

这样,一个 CPU DAI 组件就注册到系统,具体是添加到了 ASoC 声卡组件全局列表 component_list 中。

2.2.1.2 注册 CODEC

ASoC 用结构体 struct snd_soc_codec 描述 CODEC,该结构体包含一个 struct snd_soc_component 成员,所以也是一个 ASoC 组件对象,这有点类似于 C++ 的子类继承:

/* SoC Audio Codec device */
struct snd_soc_codec {...const struct snd_soc_codec_driver *driver;.../* component */struct snd_soc_component component;
};

ASoC 提供 API 接口 snd_soc_register_codec() 注册 COCEC,这和注册 CPU DAI 使用的 API 接口不一样,但 snd_soc_register_codec()devm_snd_soc_register_component() / snd_soc_register_component() 一样,最终也会添加一个描述 CODECstruct snd_soc_component 对象到 ASoC 声卡组件全局列表 component_list 中。来看一个例子:

/* sound/soc/codecs/pcm5102a.c */static struct snd_soc_dai_driver pcm5102a_dai = {.name = "pcm5102a-hifi",.playback = {.channels_min = 2,.channels_max = 2,.rates = SNDRV_PCM_RATE_8000_192000,.formats = SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_S24_LE |SNDRV_PCM_FMTBIT_S32_LE},
};static struct snd_soc_codec_driver soc_codec_dev_pcm5102a;static int pcm5102a_probe(struct platform_device *pdev)
{/* 注册 CODEC 组件 */return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pcm5102a,&pcm5102a_dai, 1);
}
/* sound/soc/soc-core.c */int snd_soc_register_codec(struct device *dev,const struct snd_soc_codec_driver *codec_drv,struct snd_soc_dai_driver *dai_drv,int num_dai)
{...struct snd_soc_codec *codec;.../* 新建 CODEC 对象 */codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);...codec->component.codec = codec; /* 绑定 snd_soc_component 和 snd_soc_codec *//* 初始化 CODEC 对象 */ret = snd_soc_component_initialize(&codec->component,&codec_drv->component_driver, dev);.../* 添加到 CODEC 全局组件对象列表 @component_list 中 */mutex_lock(&client_mutex);snd_soc_component_add_unlocked(&codec->component);list_add(&codec->list, &codec_list);mutex_unlock(&client_mutex);...return 0;...
}

2.2.2 组装 ASoC 声卡

现在已经注册了 CPU DAI 组件CODEC 组件,我们就可以建立一个 Machine 驱动,作为一个粘合层,将 CPU DAI 组件CODEC 组件拼接到一起,组成一个声卡。

ASoC 声卡用结构体 struct snd_soc_card 来描述,结构体包含一个 ALSA 声卡对象结构体 struct snd_card 引用指针,这也类似 C++ 的子类继承:

/* SoC card */
struct snd_soc_card {const char *name; /* SoC 声卡名称 */...struct snd_card *snd_card;...int (*probe)(struct snd_soc_card *card);...struct snd_soc_dai_link *dai_link;  /* predefined links only */int num_links;  /* predefined links only *//* SoC 声卡所有的 DAI 连接列表, 包括预定义的 @dai_link  */struct list_head dai_link_list; /* all links */int num_dai_links; /* @dai_link_list 列表的长度 */.../* lists of probed devices belonging to this card *//* SoC 声卡使用的 组件对象(snd_soc_component: McASP,I2S, Codec) 列表 */struct list_head component_dev_list;...void *drvdata; /* 特定声卡私有数据 */
};

Machine 驱动 在选择拼接的 CPU DAICODEC 时,既可以通过组件的名称,也可以通过组件的 DTS 节点 phandle,来指定要引用的组件,这里以组件的 DTS 节点 phandle 选择组件的方式来举例说明。

Machine 驱动有时候要我们自己编写,但也可以使用目录 sound/soc/genericASoC 框架提供的公版 Machine 驱动。本文以该公版 Machine 驱动为例进行说明。

先看一下 ASoC 声卡的 DTS 配置(以公版 Machine 驱动为例):

sound_i2s {compatible = "simple-audio-card"; /* 使用 公版 Machine 驱动 */simple-audio-card,name = "I2S-master";simple-audio-card,mclk-fs = <256>;simple-audio-card,format = "i2s";status = "okay";simple-audio-card,cpu {sound-dai = <&i2s0>; /* 声卡使用的 CPU DAI: i2s0 */};simple-audio-card,codec {sound-dai = <&pcm5102a>; /* 声卡使用的 CODEC: pcm5102a */};
};

通过上面的 DTS 配置,将 i2s0 (CPU DAI)pcm5102a (CODEC) 组装为一个声卡,且使用公版 Machine 驱动

/* sound/soc/generic/simple-card.c */struct simple_card_data {struct snd_soc_card snd_card;...
};static int asoc_simple_card_probe(struct platform_device *pdev)
{struct simple_card_data *priv;...struct device *dev = &pdev->dev;struct device_node *np = dev->of_node; /* 声卡 DTS 节点: sound_i2s {...} */struct snd_soc_card *card;.../* Allocate the private data and the DAI link array */priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); /* 新建 ASoC 声卡对象 */.../* Init snd_soc_card */card = simple_priv_to_card(priv);...if (np && of_device_is_available(np)) {/* 从 DTS 配置提取 CPU DAI 和 CODEC */ret = asoc_simple_card_parse_of(priv);...} else {...}/* 注册初始化 ASoC 声卡对象 */ret = devm_snd_soc_register_card(dev, card);...return 0;...
}
/* sound/soc/soc-devres.c */int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{struct snd_soc_card **ptr;...ret = snd_soc_register_card(card);...
}
/* sound/soc/soc-core.c */int snd_soc_register_card(struct snd_soc_card *card)
{...ret = snd_soc_instantiate_card(card);...
}static int snd_soc_instantiate_card(struct snd_soc_card *card)
{.../* bind DAIs */for (i = 0; i < card->num_links; i++) {ret = soc_bind_dai_link(card, &card->dai_link[i]);...}.../* card bind complete so register a sound card *//** ASoC 声卡 snd_soc_card 可理解成是对 snd_card 的继承.* 这里新建并初始化一个声卡对象 (snd_card):* . 设置声卡名, ID* . 新建并初始一个控制类声卡设备(controlC%i), 然后添加到声卡的设备列表* . 建立 /proc/asound/card%i 目录* ...*/ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);.../* initialise the sound card only once */if (card->probe) {ret = card->probe(card); /* ASoC 声卡 probe */...}/* probe all components used by DAI links on this card *//* ASoC 声卡包含 DAI,CODEC 组件 probe */for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;order++) {list_for_each_entry(rtd, &card->rtd_list, list) {ret = soc_probe_link_components(card, rtd, order);...}}.../* * 注册 ASoC 声卡到系统: * . 注册 声卡类 字符设备 节点*   /dev/snd/pcmCxDxp, /dev/snd/pcmCxDxc*   /dev/snd/controlC0* ...*/ret = snd_card_register(card->snd_card);...card->instantiated = 1; /* 标记声卡已经初始化 */...return 0;...
}

注册 ASoC 声卡有很多的细节,如路由、组件对象的建立等等,限于篇幅,就不一一细表。这里只重点说一下 soc_bind_dai_link(),该函数会查找注册 ASoC 声卡指定组件(CPU DAI + CODEC)绑定到声卡:

/* sound/soc/soc-core.c */static int soc_bind_dai_link(struct snd_soc_card *card,struct snd_soc_dai_link *dai_link)
{struct snd_soc_pcm_runtime *rtd;struct snd_soc_dai_link_component *codecs = dai_link->codecs; // CODECstruct snd_soc_dai_link_component cpu_dai_component; // CPU DAIstruct snd_soc_component *component;struct snd_soc_dai **codec_dais;...rtd = soc_new_pcm_runtime(card, dai_link);...cpu_dai_component.name = dai_link->cpu_name;cpu_dai_component.of_node = dai_link->cpu_of_node;cpu_dai_component.dai_name = dai_link->cpu_dai_name;/* 从 ASoC 声卡组件全局列表 @component_list 查找指定 CPU DAI 组件 */rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component); .../* 添加 CPU DAI 组件 ASoC 声卡 */snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);.../* Find CODEC from registered CODECs */codec_dais = rtd->codec_dais;for (i = 0; i < rtd->num_codecs; i++) {/* 从 ASoC 声卡组件全局列表 @component_list 查找指定 CODEC 组件 */codec_dais[i] = snd_soc_find_dai(&codecs[i]);.../* 添加 CODEC 组件 ASoC 声卡 */snd_soc_rtdcom_add(rtd, codec_dais[i]->component);}/* Single codec links expect codec and codec_dai in runtime data */rtd->codec_dai = codec_dais[0];rtd->codec = rtd->codec_dai->codec;...soc_add_pcm_runtime(card, rtd);return 0;...
}

注册完声卡后,已经可以从系统 /dev/snd 目录下看到声卡对应的设备节点。到此,除了声卡内存管理外,我们对 ASoC 声卡框架的描述基本完成,接下来我们来补充对声卡内存管理的说明。

2.3 声卡的内存管理

声卡驱动使用 DMA 方式进行数据传送。以播放为例,看一下声卡数据流动的逻辑框图:

在这里插入图片描述

上图中,DMA Buffer 是通过声卡的 DMA 内存管理组件从系统主存分配的内存;而 FIFOCPU DAI I2S 自带的一块空间。

播放就是用户空间通过 ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES) 将数据写入 DMA Buffer,然后 DMA 硬件再将数据搬运到 I2S FIFO,再从 I2S FIFO 最终传递给 CODEC 的过程。录音的数据传输方向则刚好和播放相反。播放录音都有各自独立DMA Buffer

从前面的分析了解到,ASoC 声卡的主要组成(只关注核心部分):

ASoC 声卡 = 【CPU DAI 组件】 + 【CODEC 组件】

但实际上,我们还需要一个 DMA 内存管理组件,所以:

ASoC 声卡 = 【CPU DAI 组件】 + 【CODEC 组件】 + 【DMA 内存管理组件】

当然,我们也可以认为【DMA 内存管理组件】【CPU DAI 组件】的一部分,因为正是 CPU DAI 内存(如图中的 I2S FIFO)DMA 内存进行交互的,从前面的播放数据流动图中也可以看到这一点。事实上, 通常【DMA 内存管理组件】【CPU DAI 组件】驱动注册。当然,也可以是由独立的驱动注册【DMA 内存管理组件】,如后面的 fsl-dma.c

本小节重点分析【DMA 内存管理组件】的注册和使用过程。【DMA 内存管理组件】主要涉及以下几个部分:

1. 【DMA 内存管理组件】的注册和初始化【DMA 内存管理组件】操作接口和参数的设定,CPU DAI `DMA 传输地址(DAI FIFO 地址)初始化``DMA 传输通道申请`2. 【DMA 内存管理组件】的绑定将【DMA 内存管理组件】绑定到声卡。3. 通过【DMA 内存管理组件】分配 DMA 内存包括`录音、播放`各自的 DMA 传输的内存空间申请。4. 设定 DMA 传输源、目的地址绑定【DMA 内存管理组件】分配 DMA 内存地址 和 CPU DAI 硬件缓冲地址。5. 启动【DMA 内存管理组件】传输数据

2.3.1 【DMA 内存管理组件】的注册和初始化

【DMA 内存管理组件】有一个 ASoC 子系统提供的公版,同时,CPU DAI 驱动也可以定义自己的【DMA 内存管理组件】ASoC 声卡【DMA 内存管理组件】驱动接口抽象如下:

/* ASoC 声卡 【DMA 内存管理组件】 驱动 */
struct snd_soc_platform_driver {int (*probe)(struct snd_soc_platform *);int (*remove)(struct snd_soc_platform *);struct snd_soc_component_driver component_driver;/* pcm creation and destruction *//** 公版  : dmaengine_pcm_new()* 自定义: fsl_dma_new(), ...*/int (*pcm_new)(struct snd_soc_pcm_runtime *);void (*pcm_free)(struct snd_pcm *);/* platform stream pcm ops *//** 公版  : &dmaengine_pcm_ops* 自定义: &fsl_dma_ops, ...*/const struct snd_pcm_ops *ops;/* platform stream compress ops */const struct snd_compr_ops *compr_ops;
};
2.3.1.1 公版【DMA 内存管理组件】的注册和初始化

以全志 I2S 接口驱动为例,看公版【DMA 内存管理组件】的注册和初始化的细节,过程包括:

a. CPU DAI DMA 传输通道的申请
b. DMA 传输 FIFO 一端地址设定
c. 【DMA 内存管理组件】接口和参数设定
d. 其它

首先,CPU DAI 可通过 DTS 配置使用的DMA 传输通道

i2s0: i2s@01c22000 {compatible = "allwinner,sun8i-h3-i2s";...dmas = <&dma 3>, <&dma 3>;dma-names = "rx", "tx";...
};

DMA 传输通道之所以由 CPU DAI 驱动申请,一是因为 DMA 内存是和 CPU DAI FIFO 交互;二是因为硬件设计固定了 CPU DAI 使用的 DMA 传输通道DMA 传输通道也是无法随便更改的。

继续看代码实现的细节:

/* sound/soc/sunxi/sun4i-i2s.c */struct sun4i_i2s {.../** I2S DMA 传输信息:* . FIFO 地址* . DMA 通道名、数据位宽* ...*/struct snd_dmaengine_dai_dma_data	capture_dma_data; /* 录音 */struct snd_dmaengine_dai_dma_data	playback_dma_data; /* 播放 */...
};static int sun4i_i2s_probe(struct platform_device *pdev)
{struct sun4i_i2s *i2s;...i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);.../* 播放时: DMA 数据的目的地址为通道的 FIFO,即数据从 DMA Buffer -> I2S TX FIFO */i2s->playback_dma_data.addr = res->start +i2s->variant->reg_offset_txdata;i2s->playback_dma_data.maxburst = 8;/* 录音时: DMA 数据的目的地址为通道的 FIFO,即数据从 I2S RX FIFO -> DMA Buffer */i2s->capture_dma_data.addr = res->start + SUN4I_I2S_FIFO_RX_REG;i2s->capture_dma_data.maxburst = 8;.../* 注册 CPU DAI 组件,前面 2.2.2.1 已经分析过了 */ret = devm_snd_soc_register_component(&pdev->dev,&sun4i_i2s_component,&sun4i_i2s_dai, 1);.../* ASoC 声卡 【DMA 内存管理组件】 注册和初始化 */ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);...
}
/* sound/soc/soc-generic-dmaengine-pcm.c */int snd_dmaengine_pcm_register(struct device *dev,const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{struct dmaengine_pcm *pcm;int ret;/* 新建 PCM DMA 引擎对象(dmaengine_pcm) */pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);.../* 申请 DMA 通道: Playback + Capture */ret = dmaengine_pcm_request_chan_of(pcm, dev, config);.../* ASoC 声卡 【DMA 内存管理组件】 注册和初始化 */ret = snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform);...return 0;...
}
2.3.1.2 自定义【DMA 内存管理组件】的注册和初始化

自定义【DMA 内存管理组件】的注册和初始化类似,只是定义了不同的组件接口。挑一个 Freescale 例子来看下:

/* sound/soc/fsl/fsl-dma.c */struct dma_object {struct snd_soc_platform_driver dai; /* ASoC 声卡 【DMA 内存管理组件】 驱动 */dma_addr_t ssi_stx_phys; /* 播放 DMA 硬件缓冲物理地址 */dma_addr_t ssi_srx_phys; /* 录音 DMA 硬件缓冲物理地址 */...
};static int fsl_soc_dma_probe(struct platform_device *pdev)
{struct dma_object *dma;...dma = kzalloc(sizeof(*dma), GFP_KERNEL);.../* ASoC 声卡 【DMA 内存管理组件】 驱动接口设定 */dma->dai.ops = &fsl_dma_ops;dma->dai.pcm_new = fsl_dma_new;dma->dai.pcm_free = fsl_dma_free_dma_buffers;/* Store the SSI-specific information that we need *//* 录音、播放 DMA 硬件缓冲地址设定 */dma->ssi_stx_phys = res.start + CCSR_SSI_STX0;dma->ssi_srx_phys = res.start + CCSR_SSI_SRX0;iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);if (iprop)dma->ssi_fifo_depth = be32_to_cpup(iprop);else/* Older 8610 DTs didn't have the fifo-depth property */dma->ssi_fifo_depth = 8;.../* ASoC 声卡 【DMA 内存管理组件】 注册和初始化 */ret = snd_soc_register_platform(&pdev->dev, &dma->dai);...
}
/* sound/soc/soc-core.c */int snd_soc_register_platform(struct device *dev,const struct snd_soc_platform_driver *platform_drv)
{struct snd_soc_platform *platform;...platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);...ret = snd_soc_add_platform(dev, platform, platform_drv);...
}int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,const struct snd_soc_platform_driver *platform_drv)
{...ret = snd_soc_component_initialize(&platform->component,&platform_drv->component_driver, dev);...platform->dev = dev;platform->driver = platform_drv;...mutex_lock(&client_mutex);snd_soc_component_add_unlocked(&platform->component);list_add(&platform->list, &platform_list);mutex_unlock(&client_mutex);...return 0;
}

到此,【DMA 内存管理组件】的注册和初始化已经完成,我们看到,通过 API 接口 snd_dmaengine_pcm_register()snd_soc_register_platform(),将【DMA 内存管理组件】注册到系统,也即添加到全局列表 platform_list

虽然【DMA 内存管理组件】已经注册到了系统,但此时它还是一个独立的个体,还不属于任何声卡设备,在能使用它之前,先要将它绑定到声卡。接下来,就看怎样将【DMA 内存管理组件】绑定到声卡。

2.3.2 【DMA 内存管理组件】的绑定

【公版 Machine 驱动 + 公版【DMA 内存管理组件】】的组合为例,来说明 ASoC 声卡【DMA 内存管理组件】绑定到声卡的过程。先看下使用公版 Machine 驱动的声卡的 DTS 配置(同前面 2.2.2,但这里只截取和此处相关部分):

sound_i2s {compatible = "simple-audio-card"; /* 使用 公版 Machine 驱动 */...simple-audio-card,cpu {sound-dai = <&i2s0>; /* 声卡使用的 CPU DAI: i2s0 */};...
};
static int asoc_simple_card_probe(struct platform_device *pdev)
{struct simple_card_data *priv;struct snd_soc_dai_link *dai_link;.../* Get the number of DAI links */if (np && of_get_child_by_name(np, PREFIX "dai-link"))num = of_get_child_count(np);elsenum = 1; // num == 1/* Allocate the private data and the DAI link array */priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);....../* DAI 连接对象:连接 CPU DAI 和 CODEC */dai_link  = devm_kzalloc(dev, sizeof(*dai_link)  * num, GFP_KERNEL);...priv->dai_link			= dai_link;/* Init snd_soc_card */card = simple_priv_to_card(priv);...card->dai_link		= priv->dai_link; /* 设定声卡的 DAI 连接对象 */card->num_links		= num; /* 设定声卡的 DAI 连接个数: 这里是 1 个 */if (np && of_device_is_available(np)) {ret = asoc_simple_card_parse_of(priv); /* (1) 解析声卡 DTS 配置(包括 DAI 配置) */...} else {...}.../* (2) 注册初始化声卡 */ret = devm_snd_soc_register_card(dev, card);...
}

在上面代码 (1) 解析声卡 DTS 配置(包括 DAI 配置)处,将 DAI 连接对象(struct snd_soc_dai_link) 的 cpu_of_nodeplatform_of_node 都指向了 i2s0,这决定了在声卡注册时,绑定的 CPU DAI i2s0 驱动注册的【DMA 内存管理组件】,后面会看到这个细节。

先看将 DAI 连接对象(struct snd_soc_dai_link) 的 cpu_of_nodeplatform_of_node 都设定为 i2s0 的代码细节:

static int asoc_simple_card_parse_of(struct simple_card_data *priv)
{.../* Single/Muti DAI link(s) & New style of DT node */if (dai_link) {...} else {ret = asoc_simple_card_dai_link_of(node, priv, 0, true);...}...
}static int asoc_simple_card_dai_link_of(struct device_node *node,struct simple_card_data *priv,int idx,bool is_top_level_node)
{.../** 解析 cpu DTS 节点, 如 "simple-audio-card,cpu": * dai_link->cpu_of_node = i2s0*/ret = asoc_simple_card_parse_cpu(cpu, dai_link,DAI, CELL, &single_cpu);.../* dai_link->platform_of_node = dai_link->cpu_of_node; */ret = asoc_simple_card_canonicalize_dailink(dai_link);...
}

最后来看 CPU DAI i2s0 驱动注册的【DMA 内存管理组件】是如何绑定到声卡的:

devm_snd_soc_register_card()snd_soc_register_card()snd_soc_instantiate_card()soc_bind_dai_link()
static int soc_bind_dai_link(struct snd_soc_card *card,struct snd_soc_dai_link *dai_link)
{.../* find one from the set of registered platforms */list_for_each_entry(platform, &platform_list, list) {platform_of_node = platform->dev->of_node;...if (dai_link->platform_of_node) { /* 按 DTS 节点匹配 *//** 已知:* @platform_of_node: CPU DAI 接口注册的【DMA 内存管理组件】,绑定到 i2s0 设备(的 DTS 节点)* @dai_link->platform_of_node: 注册声卡前,也设定为 i2s0* 所以这里 platform_of_node == dai_link->platform_of_node,也即 */if (platform_of_node != dai_link->platform_of_node)continue;} else { /* 按名称匹配 */if (strcmp(platform->component.name, platform_name)) /* 名称匹配失败 */continue;}rtd->platform = platform; /* i2s0 注册的 【DMA 内存管理组件】 绑定到声卡 */}...
}

到此,一个 ASoC 声卡终于完整了,它的最后一个部分【DMA 内存管理组件】 也已经添加完成。当然,这里只分析了公版【DMA 内存管理组件】 的绑定过程,对自定义【DMA 内存管理组件】感兴趣的读者,可自行分析。

随着ASoC 声卡【DMA 内存管理组件】 的绑定完成,声卡就具备了内存管理能力。接下来,我们看如何使用ASoC 声卡的【DMA 内存管理组件】 来分配 DMA 内存空间

2.3.3 通过【DMA 内存管理组件】分配 DMA 内存

声卡 DMA 内存空间的申请,可能在不同的时间点:

1. 注册声卡时预分配 DMA 内存
2. ioctl(SNDRV_PCM_IOCTL_HW_PARAMS) 设置硬件参数的分配 DMA 内存

声卡从主存分配的 DMA 内存相关信息用数据结构 struct snd_dma_buffer 管理的:

/* include/sound/memalloc.h *//** info for buffer allocation*/
struct snd_dma_buffer {struct snd_dma_device dev;	/* device type */unsigned char *area;	/* virtual pointer (分配的主存的虚拟地址) */dma_addr_t addr;	/* physical address (分配的主存的总线地址) */size_t bytes;		/* buffer size in bytes */void *private_data;	/* private for allocator; don't touch */
};

它们是基于每 PCM 流(录音、播放)管理的,记录在 struct snd_pcm_substreamdma_buffer 成员中:

/* include/sound/pcm.h */struct snd_pcm_substream {...struct snd_dma_buffer dma_buffer;...
};

分配声卡 DMA 内存时,会修改对应 PCM 流对象struct snd_dma_buffer 信息,接下来看两种不同场景下分配的细节。

2.3.3.1 注册声卡时预分配 DMA 内存

同样的,我们这里只分析公版【DMA 内存管理组件】DMA 内存预分配过程,它发生在注册声卡期间。注册声卡期间 DMA 内存预分配不是必定发生的,它需满足一定的条件,看代码细节:

/* sound/soc/pcm_memory.c */static int preallocate_dma = 1; /* 非 0 值表示 PCM 运行时 DMA 数据缓冲 预分配 */
module_param(preallocate_dma, int, 0444);
MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");static int maximum_substreams = 4;
module_param(maximum_substreams, int, 0444);
MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory.");static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,size_t size, size_t max)
{if (size > 0 && preallocate_dma && substream->number < maximum_substreams) // 需满足条件才发生预分配preallocate_pcm_pages(substream, size);...
}

我们可以看到,是否发生预分配,和变量 preallocate_dmamaximum_substreams 有关,还和传入的 size 参数有关。来看注册声卡时预分配 DMA 内存的完整流程(此时【DMA 内存管理组件】绑定已经完成):

devm_snd_soc_register_card()snd_soc_register_card()snd_soc_instantiate_card()
/* sound/soc/soc-core.c */static int snd_soc_instantiate_card(struct snd_soc_card *card)
{.../* bind DAIs */for (i = 0; i < card->num_links; i++) {ret = soc_bind_dai_link(card, &card->dai_link[i]); // 包括 绑定 【DMA 内存管理组件】 到声卡...}.../* probe all DAI links on this card */for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;order++) {list_for_each_entry(rtd, &card->rtd_list, list) {ret = soc_probe_link_dais(card, rtd, order);...}}...
}static int soc_probe_link_dais(struct snd_soc_card *card,struct snd_soc_pcm_runtime *rtd, int order)
{...if (cpu_dai->driver->compress_new) {...} else {if (!dai_link->params) { /* 如果 DAI 连接 没有设置 PCM 流参数(这是大多数情形) *//* create the pcm */ret = soc_new_pcm(rtd, rtd->num);...} else {...}}...
}
/* sound/soc/soc-generic-dmaengine-pcm.c */int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{...if (platform->driver->pcm_new) {/* 用【DMA 内存管理组件】分配 DMA 缓冲 */ret = platform->driver->pcm_new(rtd); /* dmaengine_pcm_new(), fsl_dma_new(), ... */...}...
}
/* sound/soc/soc-core.c */static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
{.../* 获取 预分配 DMA 缓冲大小 */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;}.../* 为 播放、录音 PCM 流分别分配 DMA 缓冲 */for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) {.../** 分配 DMA 缓冲:* 首先尝试 SNDRV_DMA_TYPE_DEV_IRAM 分配, * 如果失败继续尝试 SNDRV_DMA_TYPE_DEV 分配.*/ret = snd_pcm_lib_preallocate_pages(substream,SNDRV_DMA_TYPE_DEV_IRAM,dmaengine_dma_dev(pcm, substream),prealloc_buffer_size,max_buffer_size);...}
}
/* sound/soc/pcm_memory.c */int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,int type, struct device *data,size_t size, size_t max)
{substream->dma_buffer.dev.type = type;substream->dma_buffer.dev.dev = data;return snd_pcm_lib_preallocate_pages1(substream, size, max);
}static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size)
{struct snd_dma_buffer *dmab = &substream->dma_buffer;...do {if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev,size, dmab)) < 0) {if (err != -ENOMEM)return err; /* fatal error */} elsereturn 0;size >>= 1;} while (size >= snd_minimum_buffer);...
}
/* sound/core/memalloc.c */int snd_dma_alloc_pages(int type, struct device *device, size_t size,struct snd_dma_buffer *dmab)
{...dmab->dev.type = type;dmab->dev.dev = device;dmab->bytes = 0;switch (type) { /* 从指定类型的内存区间分配 */...
#ifdef CONFIG_GENERIC_ALLOCATORcase SNDRV_DMA_TYPE_DEV_IRAM:snd_malloc_dev_iram(dmab, size);if (dmab->area) /* 尝试从 IRAM 类型内存池 分配成功 */break; /* 结束分配工作 *//* Internal memory might have limited size and no enough space,* so if we fail to malloc, try to fetch memory traditionally.*//* 从 IRAM 类型内存池 分配失败, 继续尝试 SNDRV_DMA_TYPE_DEV 类型分配 */ dmab->dev.type = SNDRV_DMA_TYPE_DEV;
#endif /* CONFIG_GENERIC_ALLOCATOR */...}...dmab->bytes = size;return 0;
}

对声卡 DMA 内存预分配的情形,已经分析完成。可以看到,注册声卡时预分配 DMA 内存,是默认的情形。接下来,看通过 ioctl(SNDRV_PCM_IOCTL_HW_PARAMS) 触发 DMA 内存分配的情形。

2.3.3.2 ioctl(SNDRV_PCM_IOCTL_HW_PARAMS) 触发 DMA 内存分配
ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &hw_params)...snd_pcm_ioctl()snd_pcm_common_ioctl()snd_pcm_hw_params_user()snd_pcm_hw_params()static int snd_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params)
{...if (substream->ops->hw_params != NULL) {err = substream->ops->hw_params(substream, params); /* soc_pcm_hw_params() */...}...
}
/* sound/soc/soc-pcm.c */static int soc_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_soc_platform *platform = rtd->platform; // 指代 i2s0 注册的 【DMA 内存管理组件】...if (platform->driver->ops && platform->driver->ops->hw_params) {ret = platform->driver->ops->hw_params(substream, params); /* dmaengine_pcm_hw_params() */...}...
}
/* sound/soc/soc-generic-dmaengine-pcm.c */static int dmaengine_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params)
{...	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
}
/* sound/soc/pcm_memory.c */int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
{...if (substream->dma_buffer.area != NULL &&substream->dma_buffer.bytes >= size) {/* 已经在声卡注册时预分配了 DMA 内存且空间大小满足一起,不用重新分配,直接使用 */dmab = &substream->dma_buffer; /* use the pre-allocated buffer */}  else { /* 如果没有预分配 DMA 缓冲,此时分配 */dmab = kzalloc(sizeof(*dmab), GFP_KERNEL);...dmab->dev = substream->dma_buffer.dev;if (snd_dma_alloc_pages(substream->dma_buffer.dev.type,substream->dma_buffer.dev.dev,size, dmab) < 0) {kfree(dmab);return -ENOMEM;}}/* 复制分配的 DMA 内存信息到PCM 流运行时管理数据 */snd_pcm_set_runtime_buffer(substream, dmab);runtime->dma_bytes = size;return 1;			/* area was changed */
}

接下来的分配流程进入函数 snd_dma_alloc_pages(),这和前面 2.3.3.1 小节一样,不再赘叙。函数 snd_pcm_set_runtime_buffer() 还得看一下,它将分配的 DMA 内存信息(注册声卡时预分配的,或 当前场景分配的),复制到 PCM 流运行时管理数据中,因为播放、录音时使用的是运行时管理数据。来看细节:

/* include/sound/pcm.h */static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substream,struct snd_dma_buffer *bufp)
{struct snd_pcm_runtime *runtime = substream->runtime;if (bufp) {runtime->dma_buffer_p = bufp;runtime->dma_area = bufp->area;runtime->dma_addr = bufp->addr;runtime->dma_bytes = bufp->bytes;} else {runtime->dma_buffer_p = NULL;runtime->dma_area = NULL;runtime->dma_addr = 0;runtime->dma_bytes = 0;}
}

2.3.4 设定 DMA 传输源、目的地址

声卡通过【DMA 内存管理组件】分配了 DMA 缓冲,用于和 CPU DAI 的硬件缓冲 FIFO 之间传输数据。如:

播放:音频数据
DMA Buffer --------> I2S TX FIFODMA录音:音频数据
I2S TX FIFO --------> DMA BufferDMA

分配的 DMA 缓冲给出了传输一方的地址,要完成传输,还得知道另一方的地址,也即 CPU DAI 的硬件缓冲 FIFO 的地址。从前面的代码分析得知,分配的 DMA 内存地址信息记录在 PCM 流对象 struct snd_pcm_substreamdma_buffer 和 运行时数据 runtime 中,runtime 中是 dma_buffer 数据的副本;而 CPU DAI 硬件缓冲(如 I2S FIFO)的地址信息,则是记录在 CPU DAI 驱动自身管理的数据中,如前面的例子中:

/* sound/soc/sunxi/sun4i-i2s.c */struct sun4i_i2s {.../** I2S DMA 传输信息:* . FIFO 地址* . DMA 通道名、数据位宽* ...*/struct snd_dmaengine_dai_dma_data	capture_dma_data; /* 录音 */struct snd_dmaengine_dai_dma_data	playback_dma_data; /* 播放 */...
};static int sun4i_i2s_probe(struct platform_device *pdev)
{/* 播放时: DMA 数据的目的地址为通道的 FIFO */i2s->playback_dma_data.addr = res->start +i2s->variant->reg_offset_txdata;i2s->playback_dma_data.maxburst = 8;/* 录音时: DMA 数据的目的地址为通道的 FIFO */i2s->capture_dma_data.addr = res->start + SUN4I_I2S_FIFO_RX_REG;i2s->capture_dma_data.maxburst = 8;
}/* sound/soc/fsl/fsl-dma.c */struct dma_object {...dma_addr_t ssi_stx_phys; /* 播放 DMA 硬件缓冲物理地址 */dma_addr_t ssi_srx_phys; /* 录音 DMA 硬件缓冲物理地址 */...
};static int fsl_soc_dma_probe(struct platform_device *pdev)
{struct dma_object *dma;...dma = kzalloc(sizeof(*dma), GFP_KERNEL);.../* Store the SSI-specific information that we need *//* 录音、播放 DMA 硬件缓冲地址设定 */dma->ssi_stx_phys = res.start + CCSR_SSI_STX0;dma->ssi_srx_phys = res.start + CCSR_SSI_SRX0;...
}

ASoC 声卡驱动框架下,将这些 CPU DAI 的 FIFO 地址管理在 DAI 对象数据结构 struct snd_soc_daiplayback_dma_datacapture_dma_data 成员中:

/** Digital Audio Interface runtime data.** Holds runtime data for a DAI.*/
struct snd_soc_dai {.../* DAI DMA data */void *playback_dma_data; /* DAI DMA TX FIFO 地址 (播放时数据 DMA 目的地址) */void *capture_dma_data; /* DAI DMA RX FIFO 地址 (录音时数据 DMA 源地址) */...
};

注册声卡 CPU DAI 驱动 probe 时,拷贝到 ASoC 声卡对象

snd_soc_register_card()snd_soc_instantiate_card()soc_probe_link_dais()soc_probe_dai()dai->driver->probe(dai)sun4i_i2s_dai_probe()
static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai)
{struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);snd_soc_dai_init_dma_data(dai,&i2s->playback_dma_data,&i2s->capture_dma_data);...return 0;
}
/* include/sound/soc-dai.h */static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai,void *playback, void *capture)
{dai->playback_dma_data = playback;dai->capture_dma_data = capture;
}

也就是完成了:

snd_soc_dai::playback_dma_data = sun4i_i2s::playback_dma_data
snd_soc_dai::capture_dma_data = sun4i_i2s::capture_dma_data

到此,声卡 PCM 流数据对象就知晓了 DMA 数据传输双方的地址,于是就可以进行音频数据的 DMA 传输了。接下来就分析频数据的 DMA 传输的启动过程

2.3.5 DMA 数据传输

2.3.5.1 DMA 传输准备工作

ioctl(SNDRV_PCM_IOCTL_HW_PARAMS) 做了 DMA 启动传输前的准备工作

ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &hw_params)...snd_pcm_ioctl()snd_pcm_common_ioctl()snd_pcm_hw_params_user()snd_pcm_hw_params()soc_pcm_hw_params()dmaengine_pcm_hw_params()
/* sound/soc/soc-generic-dmaengine-pcm.c */static int dmaengine_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);int (*prepare_slave_config)(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params,struct dma_slave_config *slave_config);struct dma_slave_config slave_config;...if (!pcm->config)prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config;elseprepare_slave_config = pcm->config->prepare_slave_config;if (prepare_slave_config) {// DMA 传输配置初始化。//// 这里假定走 snd_dmaengine_pcm_prepare_slave_config() ,// 自定义的接口也会间接调用 snd_dmaengine_pcm_prepare_slave_config() 。ret = prepare_slave_config(substream, params, &slave_config);...// 保存 DMA 传输配置信息 @slave_configret = dmaengine_slave_config(chan, &slave_config);...}//return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
}int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_dmaengine_dai_dma_data *dma_data;.../** 获取的正是前面举例中 sun4i_i2s_dai_probe() -> snd_soc_dai_init_dma_data() * 设置的 CPU I2S FIFO 地址信息。*/dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);...snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data,slave_config);return 0;
}
/* sound/core/pcm_dmaengine.c */void snd_dmaengine_pcm_set_config_from_dai_data(const struct snd_pcm_substream *substream,const struct snd_dmaengine_dai_dma_data *dma_data,struct dma_slave_config *slave_config)
{if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* DMA 数据流向: DMA_MEM_TO_DEV */slave_config->dst_addr = dma_data->addr; /* 设置 DMA 数据目的地址为 I2S FIFO */slave_config->dst_maxburst = dma_data->maxburst;if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)slave_config->dst_addr_width =DMA_SLAVE_BUSWIDTH_UNDEFINED;if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)slave_config->dst_addr_width = dma_data->addr_width;} else { /* DMA 数据流向: DMA_DEV_TO_MEM */slave_config->src_addr = dma_data->addr; /* 设置 DMA 数据源地址为 I2S FIFO */slave_config->src_maxburst = dma_data->maxburst;if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)slave_config->src_addr_width =DMA_SLAVE_BUSWIDTH_UNDEFINED;if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)slave_config->src_addr_width = dma_data->addr_width;}slave_config->slave_id = dma_data->slave_id;
}
2.3.5.2 启动 DMA 传输

可以通过 ioctl(SNDRV_PCM_IOCTL_START) 显式的启动 DMA 传输,或者在 ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES) 隐式触发:

ioctl(SNDRV_PCM_IOCTL_START)...snd_pcm_start_lock_irq()snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING)...snd_pcm_do_start()
/* sound/soc/pcm_native.c */static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
{if (substream->runtime->trigger_master != substream)return 0;/* 触发 声卡硬件底层 各组件、DAI 接口的 trigger 回调 */return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); /* soc_pcm_trigger() */
}
/* sound/soc/soc-pcm.c */static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_soc_platform *platform = rtd->platform;...if (platform->driver->ops && platform->driver->ops->trigger) {ret = platform->driver->ops->trigger(substream, cmd); /* snd_dmaengine_pcm_trigger() */if (ret < 0)return ret;}...
}
/* sound/core/pcm_dmaengine.c */int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);struct snd_pcm_runtime *runtime = substream->runtime;int ret;switch (cmd) {case SNDRV_PCM_TRIGGER_START:ret = dmaengine_pcm_prepare_and_submit(substream); /* 周期性的 DMA */if (ret)return ret;/** 启动 DMA 传输: * 播放: DMA Buffer -> I2S TX FIFO* 录音:I2S RX FIFO -> DMA Buffer*/dma_async_issue_pending(prtd->dma_chan);break;...}return 0;
}static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
{struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);struct dma_chan *chan = prtd->dma_chan;struct dma_async_tx_descriptor *desc;enum dma_transfer_direction direction;unsigned long flags = DMA_CTRL_ACK;direction = snd_pcm_substream_to_dma_direction(substream);...desc = dmaengine_prep_dma_cyclic(chan,substream->runtime->dma_addr,snd_pcm_lib_buffer_bytes(substream),snd_pcm_lib_period_bytes(substream), direction, flags);desc->callback = dmaengine_pcm_dma_complete;desc->callback_param = substream;prtd->cookie = dmaengine_submit(desc);return 0;
}
2.3.5.3 循环 DMA 传输

声卡 DMA 数据传输是一个循环往复的过程,一次传输完成后,会重启下次传输。更多关于 DMA 的细节,可参考博文章节 4.5 DMA 使用范例 。

3. 其它

3.1 声卡其它组成部分

ASoC 声卡还包括 DAPM(Dynamic Audio Power Management),即动态电源管理路由组件(widget) 等其它部分,本文未作涉及,感兴趣的读者可查阅相关资料。

3.2 声卡用户空间接口

在目录 /proc/asound 下,声卡提供一系列用户空间接口,方便查看调试。

# cat /proc/asound/cards0 [Loopback       ]: Loopback - LoopbackLoopback 11 [SDINOUT        ]: SDINOUT - SDINOUTDEP EVK IMX8 I2S# cat /proc/asound/pcm
00-00: Loopback PCM : Loopback PCM : playback 8 : capture 8
00-01: Loopback PCM : Loopback PCM : playback 8 : capture 8
01-00: HiFi1 dep-i2s-0 :  : playback 1 : capture 1
01-01: HiFi2 dep-i2s-1 :  : playback 1 : capture 1# cat /proc/asound/devices 0: [ 0]   : control16: [ 0- 0]: digital audio playback17: [ 0- 1]: digital audio playback24: [ 0- 0]: digital audio capture25: [ 0- 1]: digital audio capture32: [ 1]   : control33:        : timer48: [ 1- 0]: digital audio playback49: [ 1- 1]: digital audio playback56: [ 1- 0]: digital audio capture57: [ 1- 1]: digital audio captur# ls -l /proc/asound/card1
total 0
-r--r--r--    1 root     root             0 Jan  1 00:04 id
dr-xr-xr-x    3 root     root             0 Jan  1 00:04 pcm0c
dr-xr-xr-x    3 root     root             0 Jan  1 00:04 pcm0p
dr-xr-xr-x    3 root     root             0 Jan  1 00:04 pcm1c
dr-xr-xr-x    3 root     root             0 Jan  1 00:04 pcm1p# cat /proc/asound/card1/pcm0c/info 
card: 1
device: 0
subdevice: 0
stream: CAPTURE
id: HiFi1 dep-i2s-0
name: 
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1# ls -l /proc/asound/card1/pcm0c/sub0/
total 0
-r--r--r--    1 root     root             0 Jan  1 00:05 hw_params
-r--r--r--    1 root     root             0 Jan  1 00:05 info
-r--r--r--    1 root     root             0 Jan  1 00:05 status
-r--r--r--    1 root     root             0 Jan  1 00:05 sw_params

3.2 ASoC 声卡框架目录组织

include/sound/*: 声卡数据结构定义
sound/core/*.c,*.h: 所有声卡的核心公共代码,ASoC 驱动框架是基于其上的
sound/soc/*.c: ASoC 核心公共代码
sound/soc/generic/*.h,*.c: 公版 Machine 驱动
sound/soc/厂商名/*.c,*.h:各厂商 CPU DAI 驱动,自定义 Machine 驱动 
sound/soc/codec/*.c,*.h: 各种适配到 ASoC 驱动框架的 CODEC 驱动
http://www.dtcms.com/wzjs/111404.html

相关文章:

  • 具备网站维护与建设能力五种常用的网站推广方法
  • 东莞市网络营销推广多少钱水平优化
  • 服务器网站日志文件深圳搜索seo优化排名
  • 辽宁城市建设网站neotv
  • 张云网站建设网络安全
  • 设计师培训心得优化网站排名费用
  • 专业建设的主要内容百度seo官网
  • 金融网站开发小说网站排名免费
  • 网站建设公司发展饥饿营销案例
  • emlog转换wordpressseo入门基础教程
  • 微信开放平台是干什么用的兰州网络优化seo
  • 企业网站推广方案网络营销作业百度app下载官方免费最新版
  • 佛山高端网页制作进一步优化
  • 网站运营培训班最好的网站优化公司
  • 网站建设哪家好江东seo做关键词优化
  • 成都网站建设 四川冠辰科技域名注册查询阿里云
  • 网站页面优化方法互联网怎么赚钱
  • wordpress视频上传湖南专业seo优化
  • 青岛黄岛区网站开发百度快照手机版网页版
  • 免费做公益网站seo是什么意思 为什么要做seo
  • 推广公司怎么做seo自然优化排名
  • 大学营销型网站建设实训课程chatgpt网页
  • 做网站的项目流程免费的推广引流软件
  • 做有趣的网站荆州网站seo
  • 响应式网站制作软件官网百度
  • 南京哪家做电商网站合肥做网站哪家好
  • 网站建设 教案成都seo顾问
  • 网站开发的前后台的步骤分别为市场策划方案
  • 建设网站需要了解什么深圳谷歌网络推广公司
  • 哪家企业的网站做的好seo超级外链发布