1. Platform驱动在ASoC中的作用
2. snd_soc_platform_driver的注册
-
定义一个snd_soc_platform_driver结构的实例;
-
在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
-
实现snd_soc_platform_driver中的各个回调函数;
-
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_platform实例申请内存;
-
从platform_device中获得它的名字,用于Machine驱动的匹配工作;
-
初始化snd_soc_platform的字段;
-
把snd_soc_platform实例连接到全局链表platform_list中;
-
调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;
3. cpu的snd_soc_dai driver驱动的注册
-
定义一个snd_soc_dai_driver结构的实例;
-
在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;
-
实现snd_soc_dai_driver结构中的probe、suspend等回调;
-
实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;
-
driver 指向关联的snd_soc_dai_driver结构,由注册时通过参数传入;
-
playback_dma_data 用于保存该dai播放stream的dma信息,例如dma的目标地址,dma传送单元大小和通道号等;
-
capture_dma_data 同上,用于录音stream;
-
platform 指向关联的snd_soc_platform结构;
-
probe、remove 回调函数,分别在声卡加载和卸载时被调用;
-
suspend、resume 电源管理回调函数;
-
ops 指向snd_soc_dai_ops结构,用于配置和控制该dai;
-
playback snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;
-
capture snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;
4. snd_soc_dai_driver中的ops字段
-
set_sysclk 设置dai的主时钟;
-
set_pll 设置PLL参数;
-
set_clkdiv 设置分频系数;
-
dai的格式配置函数 通常由machine驱动调用:
-
set_fmt 设置dai的格式;
-
set_tdm_slot 如果dai支持时分复用,用于设置时分复用的slot;
-
set_channel_map 声道的时分复用映射设置;
-
set_tristate 设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;
-
startup
-
shutdown
-
hw_params
-
hw_free
-
prepare
-
trigger
-
digital_mute
-
snd_soc_dai_set_fmt() 实际上会调用snd_soc_dai_ops或者codec driver中的set_fmt回调;
-
snd_soc_dai_set_pll() 实际上会调用snd_soc_dai_ops或者codec driver中的set_pll回调;
-
snd_soc_dai_set_sysclk() 实际上会调用snd_soc_dai_ops或者codec driver中的set_sysclk回调;
-
snd_soc_dai_set_clkdiv() 实际上会调用snd_soc_dai_ops或者codec driver中的set_clkdiv回调;
-
#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 */
-
#define SND_SOC_DAIFMT_CONT (1 << 4) /* continuous clock */
-
#define SND_SOC_DAIFMT_GATED (2 << 4) /* clock is gated */
-
#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 */
-
#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 */
5. snd_soc_platform_driver中的ops字段
6. 音频数据的dma操作
6.1. 申请dma buffer
-
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 */
-
};
-
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 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;
-
}
6.2 dma buffer管理
-
snd_pcm_runtime.hw_ptr_base 环形缓冲区每一圈的基地址,当读写指针越过一圈后,它按buffer size进行移动;
-
snd_pcm_runtime.status->hw_ptr 硬件逻辑位置,播放时相当于读指针,录音时相当于写指针;
-
snd_pcm_runtime.control->appl_ptr 应用逻辑位置,播放时相当于写指针,录音时相当于读指针;
-
snd_pcm_runtime.boundary 扩展后的逻辑缓冲区大小,通常是(2^n)*size;
-
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)
-
应用程序调用alsa-lib的snd_pcm_writei、snd_pcm_writen函数;
-
应用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
-
应用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;
-
更新dma的硬件的当前位置,该数值通常保存在runtime->private_data中;
-
调用snd_pcm_period_elapsed函数,该函数会进一步调用snd_pcm_update_hw_ptr0函数更新上述所说的4个缓冲区管理字段,然后唤醒相应的等待进程;
-
<span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;"></span></span><pre name="code" class="cpp">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);
-
}
-
</pre>如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。