/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!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);
}
static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
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),
};
module_platform_driver(asoc_dma_driver);
snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:
snd_soc_dai_driver 该结构需要自己根据不同的soc芯片进行定义,关键字段介绍如下:
ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:
工作时钟配置函数 通常由machine驱动调用:
标准的snd_soc_ops回调 通常由soc-core在进行PCM操作时调用:
抗pop,pop声 由soc-core调用:
以下这些api通常被machine驱动使用,machine驱动在他的snd_pcm_ops字段中的hw_params回调中使用这些api:
snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)的第二个参数fmt在这里特别说一下,ASoC目前只是用了它的低16位,并且为它专门定义了一些宏来方便我们使用:
bit 0-3 用于设置接口的格式:
#define SND_SOC_DAIFMT_I2S 1 /* I2S mode */
#define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */
#define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */
#define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */
#define SND_SOC_DAIFMT_DSP_B 5 /* L data MSB during FRM LRC */
#define SND_SOC_DAIFMT_AC97 6 /* AC97 */
#define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */
bit 4-7 用于设置接口时钟的开关特性:
#define SND_SOC_DAIFMT_CONT (1 << 4) /* continuous clock */
#define SND_SOC_DAIFMT_GATED (2 << 4) /* clock is gated */
bit 8-11 用于设置接口时钟的相位:
#define SND_SOC_DAIFMT_NB_NF (1 << 8) /* normal bit clock + frame */
#define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */
#define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */
#define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */
bit 12-15 用于设置接口主从格式:
#define SND_SOC_DAIFMT_CBM_CFM (1 << 12) /* codec clk & FRM master */
#define SND_SOC_DAIFMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */
#define SND_SOC_DAIFMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */
#define SND_SOC_DAIFMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */
该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:
ops.open
当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。
ops.hw_params
驱动的hw_params阶段,该函数会被调用。通常,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数,获得的参数一般都会保存在snd_pcm_runtime结构的private_data字段。然后通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。
ops.prepare
正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。
ops.trigger
数据传送的开始,暂停,恢复和停止时,该函数会被调用。
ops.pointer
该函数返回传送数据的当前位置。
soc-platform驱动的最主要功能就是要完成音频数据的传送,大多数情况下,音频数据都是通过dma来完成的。
因为dma的特殊性,dma buffer是一块特殊的内存,比如有的平台规定只有某段地址范围的内存才可以进行dma操作,而多数嵌入式平台还要求dma内存的物理地址是连续的,以方便dma控制器对内存的访问。在ASoC架构中,dma buffer的信息保存在snd_pcm_substream结构的snd_dma_buffer *buf字段中,它的定义如下
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 */
};
那么,在哪里完成了snd_dam_buffer结构的初始化赋值操作呢?答案就在snd_soc_platform_driver的pcm_new回调函数中,还是以/sound/soc/samsung/dma.c为例:
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);
}
pcm_new字段指向了dma_new函数,dma_new函数进一步为playback和capture分别调用preallocate_dma_buffer函数,我们看看preallocate_dma_buffer函数的实现:
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;
pr_debug("Entered %s\n", __func__);
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);
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
该函数先是获得事先定义好的buffer大小,然后通过dma_alloc_weitecombine函数分配dma内存,然后完成substream->dma_buffer的初始化赋值工作。上述的pcm_new回调会在声卡的建立阶段被调用,调用的详细的过程请参考Linux ALSAs声卡驱动之六:ASoC架构中的Machine中的图3.1。
在声卡的hw_params阶段,snd_soc_platform_driver结构的ops->hw_params会被调用,在该回调用,通常会使用api:snd_pcm_set_runtime_buffer()把substream->dma_buffer的数值拷贝到substream->runtime的相关字段中(.dma_area, .dma_addr, .dma_bytes),这样以后就可以通过substream->runtime获得这些地址和大小信息了。
dma buffer获得后,即是获得了dma操作的源地址,那么目的地址在哪里?其实目的地址当然是在dai中,也就是前面介绍的snd_soc_dai结构的playback_dma_data和capture_dma_data字段中,而这两个字段的值也是在hw_params阶段,由snd_soc_dai_driver结构的ops->hw_params回调,利用api:snd_soc_dai_set_dma_data进行设置的。紧随其后,snd_soc_platform_driver结构的ops->hw_params回调利用api:snd_soc_dai_get_dma_data获得这些dai的dma信息,其中就包括了dma的目的地址信息。这些dma信息通常还会被保存在substream->runtime->private_data中,以便在substream的整个生命周期中可以随时获得这些信息,从而完成对dma的配置和操作。
static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)
{
snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;
if (avail < 0)
avail += runtime->boundary;
else if ((snd_pcm_uframes_t) avail >= runtime->boundary)
avail -= runtime->boundary;
return avail;
}
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
unsigned long flags;
if (PCM_RUNTIME_CHECK(substream))
return;
runtime = substream->runtime;
if (runtime->transfer_ack_begin)
runtime->transfer_ack_begin(substream);
snd_pcm_stream_lock_irqsave(substream, flags);
if (!snd_pcm_running(substream) ||
snd_pcm_update_hw_ptr0(substream, 1) < 0)
goto _end;
if (substream->timer_running)
snd_timer_interrupt(substream->timer, 1);
_end:
snd_pcm_stream_unlock_irqrestore(substream, flags);
if (runtime->transfer_ack_end)
runtime->transfer_ack_end(substream);
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
}
如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。
7. 图说代码
最后,反正图也画了,好与不好都传上来供参考一下,以下这张图表达了 ASoC中Platform驱动的几个重要数据结构之间的关系:
图7.1 ASoC Platform驱动
一堆的private_data,很重要但也很容易搞混,下面的图不知对大家有没有帮助:
图7.2 private_data