有偷菜餐厅城市建设的网站百度推广服务费3000元
文章目录
- 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
一侧的 DAI
和 CODEC
一侧的 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 DAI
和 CODEC
的注册。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()
一样,最终也会添加一个描述 CODEC
的 struct 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 DAI
和 CODEC
时,既可以通过组件的名称
,也可以通过组件的 DTS 节点 phandle
,来指定要引用的组件,这里以组件的 DTS 节点 phandle
选择组件的方式来举例说明。
Machine 驱动
有时候要我们自己编写,但也可以使用目录 sound/soc/generic
下 ASoC
框架提供的公版 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 内存管理组件
从系统主存分配的内存;而 FIFO
是 CPU 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_node
和 platform_of_node
都指向了 i2s0
,这决定了在声卡注册时,绑定的 CPU DAI
i2s0
驱动注册的【DMA 内存管理组件】
,后面会看到这个细节。
先看将 DAI 连接对象
(struct snd_soc_dai_link
) 的 cpu_of_node
和 platform_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_substream
的 dma_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_dma
和 maximum_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_substream
的 dma_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_dai
的 playback_dma_data
和 capture_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 驱动