DSP

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

2019-07-13 19:42发布

    本文主要接着讲,ALSA驱动框架中,内核调用到驱动的全过程 十.从内核调用到驱动的全过程       1Open /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->startupat91_ssc_startup该函数主要是设置传输方向的mask(串口只允许单向传输) Platform->pcm_ops->openat91_pcm_open该函数主要设置at91_pcm_hardware,包括pcm_infoperiod_bytes_min,period_bytes_max, periods_min,periods_max,buffer_bytes_max等, Codec_dai->ops.startupNULL Machine->ops->startupepayment_snd_startup,该函数数主要设置cpu_daiclk为系统时钟AT91_SYSCLK_MCK,设置codec_daiclkPCMXXX_SYSCLK,,然后使能codec_daiclk 2Open /dev/mixer Open /dev/mixer的大致过程与上述open /dev/dsp类似,只是后面的操作指针为mixer ops 具体的就是调用mixer_oss.c中的snd_mixer_oss_open,该函数大致就是完成了struct snd_mixer_oss_file *fmixerfmixer的填充,然后将其赋值为文件的私有数据。   3.配置参数的过程(dsp ioctl) 配置参数的过程具体是通过ioctl来实现的 例如在库文件中,对参数的设置都是通过ioctl来实现的,一般包括,syncchannelfmt,rate等参数的设置。 举设置采样速率为例,其流程图如下,       同样的,在函数soc_pcm_hw_params中,仍然遵循CPUàplatformàcodec 的大致顺序。       具体的, a)                machine->ops->hw_params(machinedai_link结构指针),为函数epayment_snd_hw_params,主要完成设置cpu_daicodec_daifmt,设置cpu_daicmr_divperiod(通过采样计算所得) b)               codec_dai->ops.hw_paramspcmXXX_hw_params null c)                cpu_dai->ops.hw_paramsat91_ssc_hw_params,主要完成初始化一个struct at91_pcm_dma_params *dma_params,并对dma_params进行填充,包括pdc registerssc_basepdc_xfer_size(以字节计)和一些SSC寄存器的设置,如rcmr, rfmr, tcmr, tfmr. 最后,reset ssc它的PDC寄存器,请求中断,中断服务例程为at91_ssc_interrupt,在传输过程中,当接收到数据和发送完所有数据时均为产生中断,从而调用中断服务例程。 d)               platform->pcm_ops->hw_paramsat91_pcm_hw_params,主要完成at91_runtime_data的数据填充,这里还填充了一个dma_intr_handlerat91_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->xnprpdc->xncr 若中断类型为dma_buffer已满,则先关闭PDC,然后重新定位prtd->period_ptr指针,(设置为dma_buffer的头指针),设置pdc->xpr,pdc->xcr,然后重新开启PDC         4Mixer_ioctl 同上,mixer的相关操作是通过ioctl来实现的,它本身甚至不需要readWrite 由于PCMXXX不支持音量调节,略。   5Pcm 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                            具体电源管理的见电源部分   6Pcm 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->statecheck是否还有数据保留在playback buffer              -----àdo_action 调用substream->ops->triggersoc_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_time5000       即延时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)