http://blog.csdn.net/azloong/article/details/17317829
概述中提到音频Platform驱动主要作用是音频数据的传输,这里又细分为两步:
· 把音频数据从dma buffer通过dma操作搬运到cpu_dai FIFO,这部分驱动用snd_soc_platform_driver描述,后面分析用pcm_dma指代它。
· 把音频数据从cpu_dai FIFO通过数字音频接口(I2S/PCM/AC97)传送到codec_dai,这部分驱动用snd_soc_dai_driver描述,后面分析用cpu_dai指代它。
那么dma buffer中的音频数据从哪而来?保留这个问题,在后面章节pcm native分析。
cpu dai
从上图我们可知,实现一个cpu_dai驱动主要工作有:
· 实现dai操作函数,见snd_soc_dai_ops字段定义,用于配置控制dai,如系统时钟设置set_sysclk()、格式设置set_fmt()、硬件参数设置hw_params()、触发dai启动或停止传输trigger()等。
· 实现dai_drv的probe函数(dai初始化工作)、remove函数(dai卸载工作)、suspend/resume函数(dai电源管理配置)
· 初始化dai_drv实例snd_soc_dai_driver,包括playback和capture的能力描述信息、dai操作函数集指针、probe/remove回调、电源管理相关的suspend/resume回调。
· 通过snd_soc_register_dai()把初始化完成的snd_soc_dai_driver注册到soc-core:首先创建并初始化一个snd_soc_dai实例,然后把该snd_soc_dai实例插入到dai_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的cpu_dai并绑定。
- /**
- * snd_soc_register_dai - Register a DAI with the ASoC core
- *
- * @dai: DAI to register
- */
- int snd_soc_register_dai(struct device *dev,
- struct snd_soc_dai_driver *dai_drv)
- {
- struct snd_soc_dai *dai;
- dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
- if (dai == NULL)
- return -ENOMEM;
- /* create DAI component name */
- dai->name = fmt_single_name(dev, &dai->id);
- if (dai->name == NULL) {
- kfree(dai);
- return -ENOMEM;
- }
- dai->dev = dev;
- dai->driver = dai_drv;
- if (!dai->driver->ops)
- dai->driver->ops = &null_dai_ops;
- mutex_lock(&client_mutex);
- list_add(&dai->list, &dai_list);
- mutex_unlock(&client_mutex);
- return 0;
- }
注意dai操作函数的实现是cpu_dai驱动的主体,主要工作是配置好相关寄存器让硬件正常运转,因此这依赖于具体平台的cpu dai specification。snd_soc_dai_ops字段的详细说明见Codec audio operations章节。
cpu_dai驱动应该算是这个系列中最简单的一环,因此不多花费笔墨在这里了。倒是某些平台上,dma设备信息(设备的总线地址、通道号、传输单元大小)是在这里初始化的,这点要留意,这些dma设备信息在pcm_dma驱动中用到。
以Exynos平台为例,代码位置sound/soc/samsung/i2s.c。
Samsung Exynos平台的音频dma设备信息用s3c_dma_params结构体描述:
- struct s3c_dma_params {
- struct s3c2410_dma_client *client; /* stream identifier */
- int channel; /* Channel ID */
- dma_addr_t dma_addr;
- int dma_size; /* Size of the DMA transfer */
- unsigned ch;
- struct samsung_dma_ops *ops;
- };
· client:流标识符
· channel:通道号
· dma_addr:设备的总线地址,这里通常指向I2S tx FIFO或I2S rx FIFO地址
· dma_size:dma传输单元大小
· ops:平台dma操作函数
sound/soc/samsung/i2s.c中设置dma设备信息的相关代码片段:
- struct i2s_dai {
- // ...
- /* Driver for this DAI */
- struct snd_soc_dai_driver i2s_dai_drv;
- /* DMA parameters */
- struct s3c_dma_params dma_playback; // playback dma描述信息
- struct s3c_dma_params dma_capture; // capture dma描述信息
- struct s3c_dma_params idma_playback;// playback idma描述信息,idma仅用于回放,用于三星平台的LPA(低功耗音频)模式
- // ...
- };
- static __devinit int samsung_i2s_probe(struct platform_device *pdev)
- {
- // ...
- // 从platform_device中取得resource,得到playback dma通道号
- res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!res) {
- dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");
- return -ENXIO;
- }
- dma_pl_chan = res->start; // dma_pl_chan中的pl是playback简写
- // 从platform_device中取得resource,得到capture dma通道号
- res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!res) {
- dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");
- return -ENXIO;
- }
- dma_cp_chan = res->start; // dma_cp_chan中的cp是capture的简写
- // 从platform_device中取得resource,得到playback idma通道号
- res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
- if (res)
- dma_pl_sec_chan = res->start;
- else
- dma_pl_sec_chan = 0;
- // 从platform_device中取得resource,得到I2S的基地址
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
- return -ENXIO;
- }
- if (!request_mem_region(res->start, resource_size(res),
- "samsung-i2s")) {
- dev_err(&pdev->dev, "Unable to request SFR region\n");
- return -EBUSY;
- }
- regs_base = res->start;
- // ...
- pri_dai->dma_playback.dma_addr = regs_base + I2STXD; // 设置playback dma设备地址为I2S tx FIFO地址
- pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; // 设置capture dma设备地址为I2S rx FIFO地址
- pri_dai->dma_playback.client =
- (struct s3c2410_dma_client *)&pri_dai->dma_playback;
- pri_dai->dma_capture.client =
- (struct s3c2410_dma_client *)&pri_dai->dma_capture;
- pri_dai->dma_playback.channel = dma_pl_chan; // 设置playback dma通道号
- pri_dai->dma_capture.channel = dma_cp_chan; // 设置capture dma通道号
- pri_dai->src_clk = i2s_cfg->src_clk;
- pri_dai->dma_playback.dma_size = 4; // 设置playback dma传输单元大小为4个字节
- pri_dai->dma_capture.dma_size = 4; // 设置capture dma传输单元大小为4个字节
- static struct resource exynos4_i2s0_resource[] = {
- [0] = {
- .start = EXYNOS4_PA_I2S0, // start字段保存的是I2S基地址
- .end = EXYNOS4_PA_I2S0 + 0x100 - 1,
- .flags = IORESOURCE_MEM, // 置该resource标识为MEM资源
- },
- [1] = {
- .start = DMACH_I2S0_TX, // start字段保存的是用于回放的dma通道号
- .end = DMACH_I2S0_TX,
- .flags = IORESOURCE_DMA, // 置该resource标识为DMA资源
- },
- [2] = {
- .start = DMACH_I2S0_RX, // start字段保存的是用于录制的dma通道号
- .end = DMACH_I2S0_RX,
- .flags = IORESOURCE_DMA, // 置该resource标识为DMA资源
- },
- [3] = {
- .start = DMACH_I2S0S_TX, // start字段保存的是用于回放的idma通道号
- .end = DMACH_I2S0S_TX,
- .flags = IORESOURCE_DMA, // 置该resource标识为DMA资源
- },
- };
- struct platform_device exynos4_device_i2s0 = {
- .name = "samsung-i2s", // platform_device名称标识为"samsung-i2s",将与i2s.c中的samsung_i2s_driver匹配
- .id = 0,
- .num_resources = ARRAY_SIZE(exynos4_i2s0_resource),
- .resource = exynos4_i2s0_resource,
- .dev = {
- .platform_data = &i2sv5_pdata,
- },
- };
当samsung_i2s_driver初始化时,通过platform_get_resource()函数来获取platform_device声明的resource。
struct resource结构中我们通常关心start、end和flags这3个字段,分别标明资源的开始值、结束值和类型。flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,譬如说某设备占据了2个内存区域,则可以定义2个IORESOURCE_MEM资源。
以上内容摘自:http://21cnbao.blog.51cto.com/109393/337609
pcm dma
pcm数据管理可以说是ALSA系统中最核心的部分,这部分的工作有两个:
· copy_from_user把用户态的音频数据拷贝到dma buffer;
· 启动dma把音频数据从dma buffer传送到I2S tx FIFO,这是回放的情形。
当数据送到I2S tx FIFO后,剩下的是启动I2S把数据传送到codec,然后DAC把数字PCM信号转换成模拟信号,送给SPK/HS输出,关于I2S(cpu_dai)和codec,在以上两章已经描述清楚了。
为什么要使用dma传输?两个原因:首先在数据传输过程中,不需要cpu的参与,节省了cpu的开销;其次传输速度快,提高硬件设备的吞吐量。对于Arm,它不能直接把数据从A地址搬运到B地址,只能把数据从A地址搬运到一个寄存器,然后再从这个寄存器搬运到B地址;而dma有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,尤其适合大数据的高速传输。一个dma传输块里面,可以划分若干个帧,每传输完一帧都可以产生一个中断。
写这个文档的初衷是为了描述清楚pcm数据流向,这里先剖析pcm dma驱动,以便后面pcm native的分析。
以Exynos平台为例,代码位置sound/soc/samsung/dma.c。
· 浅绿色:pcm_dma驱动共有的实现
· 浅灰色:samsung exynos平台的pcm_dma驱动特有的实现
· 浅紫色:pcm native关键结构
· 浅橙色:snd_soc_platform是pcm_dma注册时所创建的platform实例
我们先看看dma相关的结构,对于回放来说,dma把内存缓冲区的音频数据传送到I2S tx FIFO;对于录制来说,dma把I2S rx FIFO的音频数据传送到内存缓存区。因此在dma传输之前,必须确定data buffer和I2S FIFO信息。
snd_dma_buffer:数据缓存区,用于保存从用户态拷贝过来的音频数据,包括dma buffer的物理首地址,虚拟首地址、大小等信息;其中物理地址用于设定dma传输的源地址(回放情形)或目的地址(录制情形),虚拟地址用于与用户态的音频数据拷贝。
s3c_dma_params:dma设备描述,包括设备总线地址(回放的情形下为I2S tx FIFO首地址,设置为dma传输的目的地址)、dma通道号、dma传输数据单元大小,这些信息在i2s.c中获取。
runtime_data:dma运行期信息
· state:记录dma状态,启动或停止;
· dma_loaded:dma装载计数,每当启动一次dma传输,该计数加一;每当完成一次dma传输,该计数减一;
· dma_period:dma周期数据大小;
· dma_start:指向dma buffer的物理首地址;
· dma_pos:记录dma buffer当前指针位置,当dma每传输一次数据,都会更新该指针;
· dma_end:dma buffer结束位置;
· params:dma设备描述信息,包括设备总线地址、dma通道号、传输单元大小。
pcm operations
pcm_dma操作函数的实现是本模块实现的主体,它用snd_pcm_ops结构体描述:
- struct snd_pcm_ops {
- int (*open)(struct snd_pcm_substream *substream);
- int (*close)(struct snd_pcm_substream *substream);
- int (*ioctl)(struct snd_pcm_substream * substream,
- unsigned int cmd, void *arg);
- int (*hw_params)(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params);
- int (*hw_free)(struct snd_pcm_substream *substream);
- int (*prepare)(struct snd_pcm_substream *substream);
- int (*trigger)(struct snd_pcm_substream *substream, int cmd);
- snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
- int (*copy)(struct snd_pcm_substream *substream, int channel,
- snd_pcm_uframes_t pos,
- void __user *buf, snd_pcm_uframes_t count);
- int (*silence)(struct snd_pcm_substream *substream, int channel,
- snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
- struct page *(*page)(struct snd_pcm_substream *substream,
- unsigned long offset);
- int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
- int (*ack)(struct snd_pcm_substream *substream);
- };
下面介绍几个重要的回调函数:
· open:打开pcm逻辑设备时,会回调该函数,用于为runtime设定硬件约束,为runtime的private_data申请一个私有结构,保存dma资源如通道号、传输单元、缓冲区信息、IO设备信息等。代码如下:
- static const struct snd_pcm_hardware dma_hardware = {
- .info = SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_BLOCK_TRANSFER |
- SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE |
- SNDRV_PCM_FMTBIT_U16_LE |
- SNDRV_PCM_FMTBIT_U8 |
- SNDRV_PCM_FMTBIT_S8,
- .channels_min = 2,
- .channels_max = 2,
- .buffer_bytes_max = 128*1024,
- .period_bytes_min = PAGE_SIZE,
- .period_bytes_max = PAGE_SIZE*2,
- .periods_min = 2,
- .periods_max = 128,
- .fifo_size = 32,
- };
- static int dma_open(struct snd_pcm_substream *substream)
- {
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct runtime_data *prtd;
- pr_debug("Entered %s\n", __func__);
- // 设置substream运行期信息中的hw字段,可以把dma_hardware看成该platform的硬件约束
- snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
- snd_soc_set_runtime_hwparams(substream, &dma_hardware);
- // 为runtime_data分配内存,用于保存dma资源,包括缓冲区信息、IO设备信息、通道号、传输单元大小
- prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
- if (prtd == NULL)
- return -ENOMEM;
- spin_lock_init(&prtd->lock);
- // runtime中的private_data字段指向runtime_data
- runtime->private_data = prtd;
- return 0;
- }
· hw_params:设置pcm硬件参数时(cmd:SNDRV_PCM_IOCTL_HW_PARAMS),会回调该函数,一般用于初始化dma资源,包括通道号、传输单元、缓冲区信息、IO设备信息等。代码如下:
- static int dma_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
- {
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct runtime_data *prtd = runtime->private_data;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- unsigned long totbytes = params_buffer_bytes(params);
- struct s3c_dma_params *dma =
- snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从cpu dai驱动i2s.c取得dma设备资源
- struct samsung_dma_info dma_info;
- /* return if this is a bufferless transfer e.g.
- * codec <--> BT codec or GSM modem -- lg FIXME */
- if (!dma)
- return 0;
- /* this may get called several times by oss emulation
- * with different params -HW */
- if (prtd->params == NULL) {
- /* prepare DMA */
- prtd->params = dma; // 该字段保存的是dma设备资源,如I2S tx FIFO地址、dma通道号、dma传输单元等
- prtd->params->ops = samsung_dma_get_ops(); // dma操作函数集,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c
- //...
- prtd->params->ch = prtd->params->ops->request(
- prtd->params->channel, &dma_info);
- }
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里把dma buffer相关信息赋给substream runtime,dma_buffer在创建pcm逻辑设备时分配
- runtime->dma_bytes = totbytes;
- spin_lock_irq(&prtd->lock);
- prtd->dma_loaded = 0;
- prtd->dma_period = params_period_bytes(params);
- prtd->dma_start = runtime->dma_addr; // dma buffer的物理首地址
- prtd->dma_pos = prtd->dma_start;
- prtd->dma_end = prtd->dma_start + totbytes;
- spin_unlock_irq(&prtd->lock);
- return 0;
- }
· prepare:当数据已准备好时(cmd:SNDRV_PCM_IOCTL_PREPARE),会回调该函数告知dma数据已就绪。代码如下:
- static int dma_prepare(struct snd_pcm_substream *substream)
- {
- struct runtime_data *prtd = substream->runtime->private_data;
- int ret = 0;
- pr_debug("Entered %s\n", __func__);
- /* return if this is a bufferless transfer e.g.
- * codec <--> BT codec or GSM modem -- lg FIXME */
- if (!prtd->params)
- return 0;
- /* flush the DMA channel */
- prtd->params->ops->flush(prtd->params->ch);
- prtd->dma_loaded = 0; // 初始化dma装载计数
- prtd->dma_pos = prtd->dma_start; // 设置dma buffer当前指针为dma buffer首地址
- /* enqueue dma buffers */
- dma_enqueue(substream); // 插入到dma传输队列中
- return ret;
- }
dma_enqueue()函数,把当前dma buffer插入到dma传输队列中,当触发trigger()启动dma传输后,dma系统将会把dma buffer数据传送到FIFO(回放的情形)。
注意:每次dma传输完成后,都要调用snd_pcm_period_elapsed()告知pcm native一个周期的数据已经传送到FIFO上了,然后再次调用dma_enqueue(),dma传输...如此循环,直到触发trigger()停止dma传输。
· trigger:pcm数据传送开始、停止、暂停、恢复时,会回调该函数启动或停止dma传输(补充:当上层第一次调用pcm_write()时,触发trigger启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger停止dma传输)。trigger函数里面的操作必须是原子的,不能有可能引起睡眠的操作,并且应尽量简单。代码如下:
- static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
- {
- struct runtime_data *prtd = substream->runtime->private_data;
- int ret = 0;
- pr_debug("Entered %s\n", __func__);
- spin_lock(&prtd->lock);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- prtd->state |= ST_RUNNING;
- prtd->params->ops->trigger(prtd->params->ch); // 启动dma传输
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- prtd->state &= ~ST_RUNNING;
- prtd->params->ops->stop(prtd->params->ch); // 停止dma传输
- break;
- default:
- ret = -EINVAL;
- break;
- }
- spin_unlock(&prtd->lock);
- return ret;
- }
· pointer:该回调函数返回传输数据的当前位置。当dma每完成一次传输后,都会调用该函数获得传输数据的当前位置,这样pcm native可根据它来计算dma buffer指针位置及可用空间。该函数也是原子的。代码如下:
- static snd_pcm_uframes_t
- dma_pointer(struct snd_pcm_substream *substream)
- {
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct runtime_data *prtd = runtime->private_data;
- unsigned long res;
- pr_debug("Entered %s\n", __func__);
- res = prtd->dma_pos - prtd->dma_start; // 当前位置减去首地址,其实就是已传输数据的size
- pr_debug("Pointer offset: %lu\n", res);
- /* we seem to be getting the odd error from the pcm library due
- * to out-of-bounds pointers. this is maybe due to the dma engine
- * not having loaded the new values for the channel before being
- * called... (todo - fix )
- */
- if (res >= snd_pcm_lib_buffer_bytes(substream)) {
- if (res == snd_pcm_lib_buffer_bytes(substream))
- res = 0;
- }
- return bytes_to_frames(substream->runtime, res); // 单位转化为frames
- }
dma buffer allocation
在pcm operations小节,数次提及dma buffer,即dma数据缓冲区,用于保存上层拷贝过来的音频数据。dma buffer的分配,一般发生在pcm_dma驱动初始化阶段(probe)或pcm逻辑设备创建阶段(pcm_new)。代码如下:
- static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
- {
- struct snd_pcm_substream *substream = pcm->streams[stream].substream;
- struct snd_dma_buffer *buf = &substream->dma_buffer;
- size_t size = dma_hardware.buffer_bytes_max; // dma buffer大小不得超过hardware的buffer_bytes_max
- buf->dev.type = SNDRV_DMA_TYPE_DEV;
- buf->dev.dev = pcm->card->dev;
- buf->private_data = NULL;
- buf->area = dma_alloc_writecombine(pcm->card->dev, size,
- &buf->addr, GFP_KERNEL); // area字段是dma buffer的虚拟首地址,addr字段是dma buffer的物理首地址
- if (!buf->area)
- return -ENOMEM;
- buf->bytes = size;
- return 0;
- }
- static int dma_new(struct snd_soc_pcm_runtime *rtd)
- {
- struct snd_card *card = rtd->card->snd_card;
- struct snd_pcm *pcm = rtd->pcm;
- int ret = 0;
- if (!card->dev->dma_mask)
- card->dev->dma_mask = &dma_mask;
- if (!card->dev->coherent_dma_mask)
- card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
- if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
- ret = preallocate_dma_buffer(pcm, // 为playback stream分配的dma buffer
- SNDRV_PCM_STREAM_PLAYBACK);
- if (ret)
- goto out;
- }
- if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
- ret = preallocate_dma_buffer(pcm, // 为capture stream分配的dma buffer
- SNDRV_PCM_STREAM_CAPTURE);
- if (ret)
- goto out;
- }
- out:
- return ret;
- }
- static struct snd_soc_platform_driver samsung_asoc_platform = {
- .ops = &dma_ops,
- .pcm_new = dma_new,
- .pcm_free = dma_free_dma_buffers,
- };
当soc-core调用soc_new_pcm()创建pcm逻辑设备时,会回调pcm_new()完成dma buffer的分配,注意回放子流和录制子流都有着各自的dma buffer。
pcm dma register
上两个小节,我们介绍了pcm_dma操作函数的作用及实现和dma buffer的分配,本小节分析pcm_dma注册过程。
当platform_driver:
- static struct platform_driver asoc_dma_driver = {
- .driver = {
- .name = "samsung-audio",
- .owner = THIS_MODULE,
- },
- .probe = samsung_asoc_platform_probe,
- .remove = __devexit_p(samsung_asoc_platform_remove),
- };
与name = "samsung-audio"的platform_device(该platform_device在arch/arm/plat-samsung/devs.c中创建)匹配后,立即回调samsung_asoc_platform_probe()注册platform:
- static struct snd_soc_platform_driver samsung_asoc_platform = {
- .ops = &dma_ops,
- .pcm_new = dma_new,
- .pcm_free = dma_free_dma_buffers,
- };
- static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
- {
- return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
- }
snd_soc_register_platform:将platform_drv注册到asoc-core。
· 创建一个snd_soc_platform实例,包含platform_drv(snd_soc_platform_driver)的相关信息,封装给asoc-core使用。
· 把以上创建并初始化好的platform插入到platform_list链表上,Machine驱动初始化时会遍历该链表,以找到dai_link声明的platform并绑定。
代码实现:
- /**
- * snd_soc_register_platform - Register a platform with the ASoC core
- *
- * @platform: platform to register
- */
- int snd_soc_register_platform(struct device *dev,
- struct snd_soc_platform_driver *platform_drv)
- {
- struct snd_soc_platform *platform;
- platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
- if (platform == NULL)
- return -ENOMEM;
- /* create platform component name */
- platform->name = fmt_single_name(dev, &platform->id);
- if (platform->name == NULL) {
- kfree(platform);
- return -ENOMEM;
- }
- platform->dev = dev;
- platform->driver = platform_drv;
- platform->dapm.dev = dev;
- platform->dapm.platform = platform;
- platform->dapm.stream_event = platform_drv->stream_event;
- mutex_init(&platform->mutex);
- mutex_lock(&client_mutex);
- list_add(&platform->list, &platform_list);
- mutex_unlock(&client_mutex);
- return 0;
- }
至此,完成了platform驱动的实现,回放的情形下,pcm_dma负责把dma buffer中的数据搬运到I2S tx FIFO,I2S负责把FIFO中的数据传送到codec。
至于alsa如何把音频数据从userspace拷贝到dma buffer,如何管理dma buffer,如何启动I2S和DMA传输,见后续pcm native分析。