本文主要接着讲,ALSA驱动框架中,内核调用到驱动的全过程
十.从内核调用到驱动的全过程
1.Open /dev/dsp
Open操作,通过前面所说的结构图,我们知道,当内核调用open函数时, 首先调用的是soundcore_open,通过__look_for_unit找到chain[3],即dsp这一sound_unit指针,然后重新赋值文件的操作指针为dsp设备文件的操作指针。
这一方法也对其他的文件适用,例如,对于/dev/mixer,调用open操作时,一样调用soundcore_open,然后接下来的操作就是重新定义文件的操作指针为mixer文件的操作指针,并且调用mixer_ops中的open 函数,
以后对文件的操作就是调用更新后的ops了。
这里调用的是pcm_oss.c中的open,具体流程图如下,
具体的,
Cpu_dai.ops->startup 为at91_ssc_startup 该函数主要是设置传输方向的mask(串口只允许单向传输)
Platform->pcm_ops->open 为at91_pcm_open 该函数主要设置at91_pcm_hardware,包括pcm_info,period_bytes_min,period_bytes_max,
periods_min,periods_max,buffer_bytes_max等,
Codec_dai->ops.startup 为NULL
Machine->ops->startup 为epayment_snd_startup,该函数数主要设置cpu_dai的clk为系统时钟AT91_SYSCLK_MCK,设置codec_dai的clk为PCMXXX_SYSCLK,,然后使能codec_dai的clk
2.Open /dev/mixer
Open /dev/mixer的大致过程与上述open /dev/dsp类似,只是后面的操作指针为mixer ops
具体的就是调用mixer_oss.c中的snd_mixer_oss_open,该函数大致就是完成了struct snd_mixer_oss_file *fmixer,fmixer的填充,然后将其赋值为文件的私有数据。
3.配置参数的过程(dsp ioctl)
配置参数的过程具体是通过ioctl来实现的
例如在库文件中,对参数的设置都是通过ioctl来实现的,一般包括,sync,channel,fmt,rate等参数的设置。
举设置采样速率为例,其流程图如下,
同样的,在函数soc_pcm_hw_params中,仍然遵循CPUàplatformàcodec
的大致顺序。
具体的,
a) machine->ops->hw_params,(该machine为dai_link结构指针),为函数epayment_snd_hw_params,主要完成 设置cpu_dai,codec_dai的fmt,设置cpu_dai的cmr_div和period(通过采样计算所得)
b) codec_dai->ops.hw_params 为pcmXXX_hw_params null
c) cpu_dai->ops.hw_params 为at91_ssc_hw_params,主要完成初始化一个struct at91_pcm_dma_params *dma_params,并对dma_params进行填充,包括pdc register,ssc_base,pdc_xfer_size(以字节计)和一些SSC 寄存器的设置,如rcmr, rfmr, tcmr, tfmr.
最后,reset ssc 和 它的PDC寄存器,请求中断,中断服务例程为at91_ssc_interrupt,在传输过程中,当接收到数据和发送完所有数据时均为产生中断,从而调用中断服务例程。
d) platform->pcm_ops->hw_params 为at91_pcm_hw_params,主要完成at91_runtime_data的数据填充,这里还填充了一个dma_intr_handler为at91_pcm_dma_irq(实际上非硬件产生的中断)
此中断服务例程与上面提到的at91_ssc_interrupt中断的关系是,
在at91_ssc_interrupt中断服务例程中先判断中断类型,然后再调用at91_pcm_dma_irq这一中断服务例程完成中断服务。
而at91_pcm_dma_irq中断服务程序主要完成的工作是,
若中断类型为传输数据结束后的中断,则先重新设置prtd->period_ptr指针(如果刚好是dma_buffer_end,则设置为dma_buffer的头指针,否则指针下移period_size个字节),设置pdc->xnpr和pdc->xncr。
若中断类型为dma_buffer已满,则先关闭PDC,然后重新定位prtd->period_ptr指针,(设置为dma_buffer的头指针),设置pdc->xpr,pdc->xcr,然后重新开启PDC。
4.Mixer_ioctl
同上,mixer的相关操作是通过ioctl来实现的,它本身甚至不需要read和Write。
由于PCMXXX不支持音量调节,略。
5.Pcm prepare
在设置同步时钟和Write/read之前都会调用snd_pcm_oss_make_ready进行prepare,
同样,是通过ioctl进行实现的。
流程图类似,
-----àsnd_pcm_prepare 先等待上电,后
-----à snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
substream, f_flags);
static struct action_ops snd_pcm_action_prepare = {
.pre_action = snd_pcm_pre_prepare,
.do_action = snd_pcm_do_prepare,
.post_action = snd_pcm_post_prepare
};
-----àpre_action 检查runtime->status->state
-----àdo_action 调用substream->ops->prepare(substream)
即为soc_pcm_prepare
-----àpost_action 设置runtime->status->state
同样soc_pcm_prepare也遵循soc_core调用的基本流程
------àmachine->ops->prepare
null
------àplatform->pcm_ops->prepare
ssc prepare,ssc interrrupt disable,pdc disable
------àcodec_dai->ops.prepare
null
------àcpu_dai->ops.prepare
ssc enable
------àsnd_soc_dapm_stream_event
给电源管理发送事件SND_SOC_DAPM_STREAM_START,
具体电源管理的见电源部分
6.Pcm Write 方法(read略)
Write方法的调用过程也遵循ALSA的体系结构,
具体的流程图如下,
在snd_pcm_lib_write1中,先调用snd_pcm_lib_write_transfer函数,完成数据从用户空间到内核空间的拷贝过程,这个拷贝过程是以帧的方式进行数据复制的。
static struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
.undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};
-----àpre_action 先检测runtime->status->state和check是否还有数据保留在playback buffer
-----àdo_action 调用substream->ops->trigger (soc_pcm_trigger)
-----àpost_action 检测是否设置休眠(且最小休眠大于0),如果设置了则进入休眠事件,并且通知timer.
而soc_pcm_trigger调用如下,
----àCodec_dai->ops.trigger null
----àPlatform->pcm_ops->trigger at91_pcm_trigger
主要就是设置PDC参数,最后使能PDC进行数据传输
----àCpu_dai->ops.trigger null
7 close方法
Close方法的调用过程也遵循ALSA的体系结构,
具体的流程图如下,
在soc_pcm_hw_free中,调用情况如下,
-----àcodec_dai->dai_ops.digital_mute
拉低对应的codec digital mute
-----àmachine->ops->hw_free
释放所有dai_link相关的参量
-----àplatform->pcm_ops->hw_free
释放DMA资源
-----àcodec_dai->ops.hw_free
释放codec_dai相关的参量
-----àcpu_dai->ops.hw_free
释放cpu_dai相关的参量
在soc_codec_close中, 调用情况如下,
-----àcpu_dai->ops.shutdown
为at91_ssc_shutdown,设置dma_param各参量为空,禁止SSC的控制寄存器,关闭SSC时钟等.
-----àcodec_dai->ops.shutdown
null
-----àmachine->ops->shutdown
禁止PCLK1
-----àplatform->pcm_ops->close
为at91_pcm_close,释放at91_runtime_data
-----àschedule_delayed_work(&socdev->delayed_work,
msecs_to_jiffies(pmdown_time));
其中参数pmdown_time为5000
即延时5秒后完成工作(保证需要完成的工作都完成,此work为最后一步),在soc_probe中有提到注册的这个工作,为close_delayed_work。
close_delayed_work,将关闭电源的事件发送给dapm,关闭电源,如下 snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name,
SND_SOC_DAPM_STREAM_STOP)