DSP

ALSA 驱动框架和驱动开发 (二)

2019-07-13 19:38发布

本文主要接着讲,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)