Linux ALSA音频系统:platform,machine,codec

1.前言

  本篇结合自己的项目,参考CSDN博主:zyuanyun 来讲解。

2.项目平台介绍

  • Kernel - 4.9
  • Soc - Amlogic (型号保密)
  • CODEC - npcp215x
  • Machine
  • Userspace - alsa-lib-1.1.5

3.linux ALSA音频系统

官网:https://www.alsa-project.org/main/index.php/Main_Page (这个网站是ALSA的官网,相关上层lib api说明都可以在此查看和下载)

 Linux ALSA音频系统架构如下:

         Linux ALSA音频系统:platform,machine,codec_第1张图片     

  • Alsa application:aplay,arecord,amixer,是alsa alsa-tools中提供的上层调试工具,用户可以直接将其移植到自己所需要的平台,这些应用可以用来实现playback,capture,controls等。
  • alsa library API:alsa 用户库接口,常见有alsa-lib.(alsa-tools中的应用程序基于alsa-lib提供的api来实现)
  • alsa core:alsa 核心层,向上提供逻辑设备(pcm/ctl/midi/timer/..)系统调用,向下驱动硬件设备(Machine/i2s/dma/codec)
  • asoc core:asoc是建立在标准alsa core基础上,为了更好支持嵌入式系统和应用于移动设备的音频codec的一套软件体系。
  • hardware driver:音频硬件设备驱动,由三大部分组成,分别是machine,platform,codec.

4.hardware driver中三者的关系

Linux ALSA音频系统:platform,machine,codec_第2张图片

4.1 Platform

           指某款soc平台的音频模块,比如qcom,omap,amlogic,atml等等。platform又可细分为二个部分:

  • cpu dai:在嵌入式系统里面通常指soc的i2s,pcm总线控制器,负责把音频数据从I2S tx FIFO搬运到codec(playback,capture则相反)。cpu_dai通过 snd_soc_register_dai()来注册。注:DAI是Digital Audio Interface的简称,分为cpu_daicodec_dai,这两者通过i2s/pcm总线连接;AIF是Audio Interface母的简称,嵌入式系统中一般是I2S和PCM接口。
  • PCM dma:负责把dma buffer中的音频数据搬运到i2s tx fifo。值得留意的是:某些情形下是不需要dma操作的,比如modem和codec直连,因为modem本身已经把数据送到fifo了,这时只需要启动codec_dai接收数据即可;该情形下,machine驱动dai_link中需要设定.platform_name = "snd_soc_dummy",这是虚拟dma驱动,实现见sound/soc/soc-utils.c. 音频dma驱动通过 snd_soc_register_platform()来注册,故也常用platform来指代音频dma驱动(这里的platform需要与soc platfrom区分开)。

4.2Codec:对于回放来说,userspace送过来的音频数据是经过采样量化的数字信号,在codec经过DAC转换成模拟信号然后输出到外放或耳机,这样我么你就可以听到声音了。codec字面意思是编解码器,但芯片(codec)里面的功能部件很多,常见的有AIF,DAC,ADC,Mixer,PGA,line-in,line-out,有些高端的codec芯片还有EQ,DSP,SRC,DRC,AGC,Echo-Canceller,Noise-Suppression等部件。比如本文中的npcp215x,自带Maxx算法。

 4.3Machine:指某款机器,通过配置dai_link把cpu_dai,codec_dai,modem_dai各个音频接口给链结成一条条音频链路,然后注册snd_soc_card.和上面两个不一样,platform和codec驱动一般是可以重用的,而machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:Soc Platform与Codec的差异;DAIs之间的链结方式;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External-OSC作为i2s,CODEC的时钟源等等。

从上面的描述来看,对于回放的情形,PCM数据流向大致是:

Linux ALSA音频系统:platform,machine,codec_第3张图片

dai_link:machine驱动中定义的音频数据链路,它指定链路用到的codec,codec_dai,cpu_dai,platform.比如对于amlogci这款,通过dts来配置media链路:codec ="npcp215x",codec-dai="npcp215x_e6",cpu_dai = "aml_tdmc",platform="aml-audio-card".amlogic这款cpu通过dts来配置声卡的连接,其相关解析和注册声卡都在soc/amlogic相关文件下。.所以本文也会参考前言博主的的media链路:codec="wm8994-codec",codec-dai="wm8994-aif1",cpu_dai="samsung-i2s",platform="samsung-audio",这四者就构成了一条音频数据链路用于多媒体声音的回放和录制。一个系统可能有多个音频数据链路,比如media和voice,因此可以定义多个dai_link.如wm8994的典型设计,有三个dai_link,分别是API<>AIF1的"HIFI"(多媒体声音链路),BP<>AIF2的“voice”(通话语音链路),以及BT<>AIF3(蓝牙sco语音链路)。

npcp215x codec_dai 代码如下:

static struct snd_soc_dai_driver npcp215x_dai = {
       .name = "npcp215x",                                                                               
       .playback = {
       .stream_name = "Playback",
       .channels_min =1,
       .channels_max = 2,
       .rates = NPCP215X_RATES,
       .formats = NPCP215X_FORMATS,
  },
       .capture = {
       .stream_name = "Capture",
       .channels_min = 1,
       .channels_max = 2,
       .rates = NPCP215X_RATES,
       .formats = NPCP215X_FORMATS,
  },
       .ops = &npcp215x_dai_ops,
  };

Hardware constraints:指平台本身的硬件限制,如所能支持的通道数/采样率/数据格式,DMA支持的数据周期大小(period size),周期次数(period count)等,通过snd_pcm_hardware(include/sound/pcm.h)结构体描述 :

struct snd_pcm_hardware {
        unsigned int info;      /* SNDRV_PCM_INFO_* */
        u64 formats;            /* SNDRV_PCM_FMTBIT_* */
        unsigned int rates;     /* SNDRV_PCM_RATE_* */
        unsigned int rate_min;      /* min rate */
        unsigned int rate_max;      /* max rate */
        unsigned int channels_min;  /* min channels */
        unsigned int channels_max;  /* max channels */
        size_t buffer_bytes_max;    /* max buffer size */
        size_t period_bytes_min;    /* min period size */
        size_t period_bytes_max;    /* max period size */
        unsigned int periods_min;   /* min # of periods */
        unsigned int periods_max;   /* max # of periods */
        size_t fifo_size;       /* fifo size in bytes */                     
};

例如对于本平台:sound/soc/amlogic 描述本平台soc相关的特性:

static const struct snd_pcm_hardware aml_pcm_hardware = {                                                                         
       .info = SNDRV_PCM_INFO_INTERLEAVED |
             SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE,
      .formats =
            SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
            SNDRV_PCM_FMTBIT_S32_LE,
      .period_bytes_min = 32,
      .period_bytes_max = 32 * 1024 * 2,
      .periods_min = 2,
      .periods_max = 1024,
      .buffer_bytes_max = 512 * 1024,
      .rate_min = 8000,
      .rate_max = 192000,
      .channels_min = 1,
       .channels_max = 16,
    };
    
 static const struct snd_pcm_hardware aml_pcm_capture = {
        .info = SNDRV_PCM_INFO_INTERLEAVED |
           SNDRV_PCM_INFO_BLOCK_TRANSFER |
           SNDRV_PCM_INFO_MMAP |
           SNDRV_PCM_INFO_MMAP_VALID |
           SNDRV_PCM_INFO_PAUSE,
        .formats =
          SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
          SNDRV_PCM_FMTBIT_S32_LE,
       .period_bytes_min = 64,
       .period_bytes_max = 32 * 1024,
       .periods_min = 2,
       .periods_max = 1024,
       .buffer_bytes_max = 512 * 1024,
    
       .rate_min = 8000,
       .rate_max = 192000,
       .channels_min = 1,
       .channels_max = 16,
       .fifo_size = 0,
    };
static const struct snd_pcm_hardware aml_i2s_hardware = {
    99     .info =
   100 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP
   101     SNDRV_PCM_INFO_MMAP |
   102     SNDRV_PCM_INFO_MMAP_VALID |
   103 #endif
   104     SNDRV_PCM_INFO_INTERLEAVED |
   105         SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE,
   106     .formats =
   107         SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
   108         SNDRV_PCM_FMTBIT_S32_LE,
   109 
   110     .period_bytes_min = 64,
   111     .period_bytes_max = 32 * 1024 * 2,
   112     .periods_min = 2,
   113     .periods_max = 1024,
   114     .buffer_bytes_max = 128 * 1024 * 2 * 2,
   115 
   116     .rate_min = 8000,
   117     .rate_max = 48000,
   118     .channels_min = 2,
   119     .channels_max = 8,
   120 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP
   121     .fifo_size = 4,
   122 #else
   123     .fifo_size = 0,
   124 #endif
   125 };

static const struct snd_pcm_hardware aml_i2s_capture = {
   128     .info = SNDRV_PCM_INFO_INTERLEAVED |
   129         SNDRV_PCM_INFO_BLOCK_TRANSFER |
   130         SNDRV_PCM_INFO_MMAP |
   131         SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE,
   132 
   133     .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
   134         SNDRV_PCM_FMTBIT_S32_LE,
   135     .period_bytes_min = 64,
   136     .period_bytes_max = 32 * 1024 * 2,
   137     .periods_min = 2,
   138     .periods_max = 1024,
   139     .buffer_bytes_max = 64 * 1024 * 4,
   140 
   141     .rate_min = 8000,
   142     .rate_max = 48000,
   143     .channels_min = 2,
   144     .channels_max = 8,
   145     .fifo_size = 0,
   146 };

以上结构体描述了pdm ,i2s数据格式时的相关硬件参数限制。

hw params:用户层设置的硬件参数,如channels,sample rate,pcm format,period size ,period count;这些参数受hw constraints约束。

sw params:用户设置的软件参数,如start threshold,stop threshold,silence threshold。

5.ASOC

ASoc: ALSA System on Chip,是建立在标准的ALSA驱动之上,为了更好支持嵌入式系统和应用于移动设备的音频codec的一套软件体系,它依赖与标准ALSA驱动框架。内核文档Documentation/alsa/soc/overview.txt中详细介绍了ASoC的设计初衷,这里不一一引用,简单陈述如下:

  • 独立的codec驱动,标准的ALSA驱动框架里面codec驱动往往与SoC/CPU耦合过于紧密,不利于在多样化的平台/机器上移植复用。
  • 方便codec与Soc通过PCM/I2S总线建立连接
  • 动态音频电源管理DAPM,使得codec任何时候都工作在最低功耗状态,同时负责音频路由的创建
  • POPs和click音频抑制弱化处理,在ASoC中通过正确的音频部件上下电次序来实现
  • Machine驱动的特定控制,比如耳机,麦克风的插拔检测,外放功放的开关

在上面的hardware driver中已经介绍了ASoC硬件设备驱动的三大构成:Codec,Platform和Machine,下面列举各驱动的功能构成:

ASoC Codec Driver:

  • Codec DAI和PCM的配置信息
  • Codec的控制接口,如I2C/SPI
  • Mixer和其他音频控件
  • Codec的音频接口函数,见snd_soc_dai_ops(include/sound/soc-dai.h)结构定义
  • DAPM描述信息
  • DAPM事件处理句柄
  • DAC数字静音控制

ASoC Platform Driver: 包括dma和cpu_dai两部分:

  • dma驱动实现音频dma操作,具体见snd_pcm_ops结构体定义
        66 struct snd_pcm_ops {
        67     int (*open)(struct snd_pcm_substream *substream);
        68     int (*close)(struct snd_pcm_substream *substream);
        69     int (*ioctl)(struct snd_pcm_substream * substream,
        70              unsigned int cmd, void *arg);
        71     int (*hw_params)(struct snd_pcm_substream *substream,
        72              struct snd_pcm_hw_params *params);
        73     int (*hw_free)(struct snd_pcm_substream *substream);
        74     int (*prepare)(struct snd_pcm_substream *substream);
        75     int (*trigger)(struct snd_pcm_substream *substream, int cmd);
        76     snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
        77     int (*get_time_info)(struct snd_pcm_substream *substream,
        78             struct timespec *system_ts, struct timespec *audio_ts,
        79             struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
        80             struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
    >>  81     int (*copy)(struct snd_pcm_substream *substream, int channel,
        82             snd_pcm_uframes_t pos,
    >>  83             void __user *buf, snd_pcm_uframes_t count);
        84     int (*silence)(struct snd_pcm_substream *substream, int channel, 
        85                snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
        86     struct page *(*page)(struct snd_pcm_substream *substream,
        87                  unsigned long offset);
        88     int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
        89     int (*ack)(struct snd_pcm_substream *substream);
        90 };
  • cpu_dai驱动实现音频数字接口控制器的描述和配置

ASoC Machine Driver:

  • 作为链接Platform和Codec的载体,它必须配置dai_link为音频数据链路指定Platform和Codec
  • 处理机器持有的音频控件和音频事件,例如回放时打开外放功放

硬件设备驱动相关结构体:

  • snd_soc_codec_driver:音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息,时钟配置,IO控制等
  • struct snd_soc_codec_driver {
       893 
       894     /* driver ops */
       895     int (*probe)(struct snd_soc_codec *);
       896     int (*remove)(struct snd_soc_codec *);
       897     int (*suspend)(struct snd_soc_codec *);
       898     int (*resume)(struct snd_soc_codec *);
       899     struct snd_soc_component_driver component_driver;
       900 
       901     /* codec wide operations */
       902     int (*set_sysclk)(struct snd_soc_codec *codec,
       903               int clk_id, int source, unsigned int freq, int dir);
       904     int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
       905         unsigned int freq_in, unsigned int freq_out);
       906 
       907     /* codec IO */
       908     struct regmap *(*get_regmap)(struct device *);
       909     unsigned int (*read)(struct snd_soc_codec *, unsigned int);
       910     int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
       911     unsigned int reg_cache_size;
       912     short reg_cache_step;
       913     short reg_word_size;
       914     const void *reg_cache_default;
       915 
       916     /* codec bias level */
       917     int (*set_bias_level)(struct snd_soc_codec *,
       918                   enum snd_soc_bias_level level);
       919     bool idle_bias_off;
       920     bool suspend_bias_off;
       921 
       922     void (*seq_notifier)(struct snd_soc_dapm_context *,
       923                  enum snd_soc_dapm_type, int);
       924 
       925     bool ignore_pmdown_time;  /* Doesn't benefit from pmdown delay */
       926 };
  • snd_soc_dai_driver:音频数据接口描述及操作函数,根据codec端和soc端,分为codec_dai和cpu_dai
  • snd_soc_platform_driver:音频dma设备描述及操作函数
  •  929 struct snd_soc_platform_driver {
       930 
       931     int (*probe)(struct snd_soc_platform *);
       932     int (*remove)(struct snd_soc_platform *);
       933     struct snd_soc_component_driver component_driver;
       934 
       935     /* pcm creation and destruction */
       936     int (*pcm_new)(struct snd_soc_pcm_runtime *);
       937     void (*pcm_free)(struct snd_pcm *);
       938 
       939     /*
       940      * For platform caused delay reporting.
       941      * Optional.
       942      */
       943     snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
       944         struct snd_soc_dai *);
       945 
       946     /* platform stream pcm ops */
       947     const struct snd_pcm_ops *ops;
       948 
       949     /* platform stream compress ops */
       950     const struct snd_compr_ops *compr_ops;
       951 
       952     int (*bespoke_trigger)(struct snd_pcm_substream *, int);
       953 };
  • snd_soc_dai_link:音频链路描述及板级操作函数
  •  970 struct snd_soc_dai_link {
       971     /* config - must be set by machine driver */
       972     const char *name;           /* Codec name */
       973     const char *stream_name;        /* Stream name */
       974    
       981     const char *cpu_name;
       982     struct device_node *cpu_of_node;
       988     const char *cpu_dai_name;
       993     const char *codec_name;
       994     struct device_node *codec_of_node;
       996     const char *codec_dai_name;
       997 
       998     struct snd_soc_dai_link_component *codecs;
       999     unsigned int num_codecs;
      1006     const char *platform_name;
      1007     struct device_node *platform_of_node;
      1008     int id; /* optional ID for machine driver link identification */
      1009 
      1010     const struct snd_soc_pcm_stream *params;
      1011     unsigned int num_params;
      1012 
      1013     unsigned int dai_fmt;           /* format to set on init */
      1014 
      1015     enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
      1018     int (*init)(struct snd_soc_pcm_runtime *rtd);
      1021     int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
      1022             struct snd_pcm_hw_params *params);
      1025     const struct snd_soc_ops *ops;
      1026     const struct snd_soc_compr_ops *compr_ops;
      1029     bool playback_only;
      1030     bool capture_only;
      1033     bool nonatomic;
      1036     unsigned int ignore_suspend:1;
      1039     unsigned int symmetric_rates:1;
      1040     unsigned int symmetric_channels:1;
      1041     unsigned int symmetric_samplebits:1;                                                                     
      1044     unsigned int no_pcm:1;
      1047     unsigned int dynamic:1;
      1050     unsigned int dpcm_capture:1;
      1051     unsigned int dpcm_playback:1;
      1052 
      1053     /* DPCM used FE & BE merged format */
      1054     unsigned int dpcm_merged_format:1;
      1055 
      1056     /* pmdown_time is ignored at stop */
      1057     unsigned int ignore_pmdown_time:1;
      1058 
      1059     struct list_head list; /* DAI link list of the soc card */
      1060     struct snd_soc_dobj dobj; /* For topology */
      1061 };

6.Codec

  上一章提到codec_drv的几个组成部分,下面逐一介绍,基本是以内核文档Documentation/sound/alsa/soc/codec.txt中的内容为脉络来分析的。Codec的作用,之前已有描述,本章主要罗列下codec driver中重要的数据结构及注册流程。

  我们先看看codec的硬件框图,以npcp215x为例:

Linux ALSA音频系统:platform,machine,codec_第4张图片

以上是npcp215x的外围电路图,如上面所讲,codec有着各种功能部件,包括但不限于:

Linux ALSA音频系统:platform,machine,codec_第5张图片

这款codec自身自带dsp算法,其音频处理路劲:

Linux ALSA音频系统:platform,machine,codec_第6张图片

6.1 Codec DAI and PCM configuration

  codec_dai和pcm配置信息通过结构体snd_soc_dai_driver描述,包括dai的能力描述和操作接口,snd_soc_dai_driver最终会被注册到soc-core中。

220 struct snd_soc_dai_driver {
  221     /* DAI description */
  222     const char *name;
  223     unsigned int id;
  224     unsigned int base;
>>225     struct snd_soc_dobj dobj;
  226 
  227     /* DAI driver callbacks */
  228     int (*probe)(struct snd_soc_dai *dai);
  229     int (*remove)(struct snd_soc_dai *dai);
  230     int (*suspend)(struct snd_soc_dai *dai);
  231     int (*resume)(struct snd_soc_dai *dai);
  232     /* compress dai */
  233     int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
  234     /* DAI is also used for the control bus */
  235     bool bus_control;
  236 
  237     /* ops */
  238     const struct snd_soc_dai_ops *ops;
  239 
  240     /* DAI capabilities */
>>241     struct snd_soc_pcm_stream capture;
>>242     struct snd_soc_pcm_stream playback;
  243     unsigned int symmetric_rates:1;
  244     unsigned int symmetric_channels:1;
  245     unsigned int symmetric_samplebits:1;
  246 
  247     /* probe ordering - for components with runtime dependencies */
  248     int probe_order;
  249     int remove_order;
  250 }
  • name: codec_dai的名称标识,dai_link通过配置codec_dai_name来找到对应的codec_dai;
  • probe:codec_dai的初始化函数,注册声卡时回调;
  • playback:回放能力描述,如回放设备所支持的声道数,采样率,音频格式;
  • capture:录制能力描述,如录制设备所支持声道数,采样率,音频格式;
  • ops:codec_dai的操作函数集,这些函数集非常重要,用于dai的时钟配置,格式配置,硬件参数配置。
  • static struct snd_soc_dai_driver npcp215x_dai = {
      420     .name = "npcp215x",
      421     .playback = {                                                                                                           
      422         .stream_name = "Playback",
      423         .channels_min =1,
      424         .channels_max = 2,
      425         .rates = NPCP215X_RATES,
      426         .formats = NPCP215X_FORMATS,
      427     },
      428     .capture = {
      429         .stream_name = "Capture",
      430         .channels_min = 1,
      431         .channels_max = 2,
      432         .rates = NPCP215X_RATES,
      433         .formats = NPCP215X_FORMATS,
      434     },
      435     .ops = &npcp215x_dai_ops,
      436 };
  • static const struct snd_soc_dai_ops npcp215x_dai_ops = {
      412     .hw_params = npcp215x_hw_params,
      413     .digital_mute = npcp215x_mute,
      414     .set_sysclk = npcp215x_set_dai_sysclk,
      415     .set_fmt = npcp215x_set_dai_fmt,
      416     .set_clkdiv = npcp215x_set_dai_clkdiv,
      417 }; 
    

6.2 Codec control IO

移动设备的音频code,其控制接口一般是i2c或spi,控制接口用于读写codec的寄存器。在snd_soc_codec_driver结构中,有如下字段描述codec的控制接口:

907     /* codec IO */
   908     struct regmap *(*get_regmap)(struct device *);
   909     unsigned int (*read)(struct snd_soc_codec *, unsigned int);
   910     int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
   911     unsigned int reg_cache_size;
   912     short reg_cache_step;
   913     short reg_word_size;
   914     const void *reg_cache_default;
  • read:度寄存器;
  • write:写寄存器;
  • reg_cache_default:寄存器的缺省值;
  • reg_cache_size:缺省的寄存器值数组大小;
  • reg_word_size:寄存器宽度。

在linux4.9中,很多codec的控制接口都改用regmap了。soc-core中判断是否用的是regmap,如果是,则调用regmap接口,见如下函数:

int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned int reg,
  228                 unsigned int mask, unsigned int value)
  229 {
  230     return snd_soc_component_update_bits(&codec->component, reg, mask,    
  231         value);
  232 }
101 int snd_soc_component_update_bits(struct snd_soc_component *component,
  102     unsigned int reg, unsigned int mask, unsigned int val)
  103 {
  104     bool change;                                                          
  105     int ret;
  106 
  107     if (component->regmap)
            //当前使用regmap,调用regmap接口.
  108         ret = regmap_update_bits_check(component->regmap, reg, mask,
  109             val, &change);
  110     else
            //非regmap
  111         ret = snd_soc_component_update_bits_legacy(component, reg,
  112             mask, val, &change);
  113 
  114     if (ret < 0)
  115         return ret;
  116     return change;
  117 }

使用regmap,使得控制接口抽象化,codec_drv不用关心当前控制方式是什么;regmap 在线调试目录是/sys/kernel/debug/regmap. 注意:npcp215x没有使用regmap,由于npcp215x采用的命令格式特殊,使用i2c_transfer来传送命令。

6.3Mixers and audio controls

音频控件多用于部件开关和音量的设定,音频控件空通过soc.h中的宏来定义,例如单一型控件:

    62 #define SOC_SINGLE(xname, reg, shift, max, invert) \                     
    63 {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
    64     .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
    65     .put = snd_soc_put_volsw, \
    66     .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }

这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:

  • xname:控件的名称标识;
  • reg:控件对应的寄存器地址;
  • shift:控件控制位在寄存器中的偏移;
  • max:控件设置值范围;
  • invert:设定值是否取反。

其他类型控件类似,不一一介绍。

上述只是宏定义,音频控件真正的结构是snd_kcontrol_new:

44 struct snd_kcontrol_new {
   45     snd_ctl_elem_iface_t iface; /* interface identifier */
   46     unsigned int device;        /* device/client number */
   47     unsigned int subdevice;     /* subdevice (substream) number */
   48     const unsigned char *name;  /* ASCII name of item */
   49     unsigned int index;     /* index of item */
   50     unsigned int access;        /* access rights */
   51     unsigned int count;     /* count of same elements */
   52     snd_kcontrol_info_t *info;
   53     snd_kcontrol_get_t *get;
   54     snd_kcontrol_put_t *put;
   55     union {
   56         snd_kcontrol_tlv_rw_t *c;
   57         const unsigned int *p;                                            
   58     } tlv;
   59     unsigned long private_value;
   60 };

codec初始化时,通过snd_soc_add_codec_controls()把所有定义好的音频控件注册到alsa-core,上层可以通过amixer等工具查看修改这些控件的设定。

6.4 Codec audio operations

codec音频操作接口通过结构体snd_soc_dai_ops描述:

143 struct snd_soc_dai_ops {                                                                                                                          
  144     /*
  145      * DAI clocking configuration, all optional.
  146      * Called by soc_card drivers, normally in their hw_params.
  147      */
  148     int (*set_sysclk)(struct snd_soc_dai *dai,
  149         int clk_id, unsigned int freq, int dir);
  150     int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
  151         unsigned int freq_in, unsigned int freq_out);
  152     int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
  153     int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
  154 
  155     /*
  156      * DAI format configuration
  157      * Called by soc_card drivers, normally in their hw_params.
  158      */
  159     int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
  160     int (*xlate_tdm_slot_mask)(unsigned int slots,
  161         unsigned int *tx_mask, unsigned int *rx_mask);
  162     int (*set_tdm_slot)(struct snd_soc_dai *dai,
  163         unsigned int tx_mask, unsigned int rx_mask,
  164         int slots, int slot_width);
  165     int (*set_channel_map)(struct snd_soc_dai *dai,
  166         unsigned int tx_num, unsigned int *tx_slot,
  167         unsigned int rx_num, unsigned int *rx_slot);
  168     int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
  169 
  170     /*
  171      * DAI digital mute - optional.
  172      * Called by soc-core to minimise any pops.
  173      */
  174     int (*digital_mute)(struct snd_soc_dai *dai, int mute);
  175     int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
  176 
  177     /*
  178      * ALSA PCM audio operations - all optional.
  179      * Called by soc-core during audio PCM operations.
  180      */
  181     int (*startup)(struct snd_pcm_substream *,
182         struct snd_soc_dai *);
  183     void (*shutdown)(struct snd_pcm_substream *,
  184         struct snd_soc_dai *);
  185     int (*hw_params)(struct snd_pcm_substream *,
  186         struct snd_pcm_hw_params *, struct snd_soc_dai *);
  187     int (*hw_free)(struct snd_pcm_substream *,
  188         struct snd_soc_dai *);
  189     int (*prepare)(struct snd_pcm_substream *,
  190         struct snd_soc_dai *);
  191     /*
  192      * NOTE: Commands passed to the trigger function are not necessarily
  193      * compatible with the current state of the dai. For example this
  194      * sequence of commands is possible: START STOP STOP.
  195      * So do not unconditionally use refcounting functions in the trigger
  196      * function, e.g. clk_enable/disable.
  197      */
  198     int (*trigger)(struct snd_pcm_substream *, int,
  199         struct snd_soc_dai *);
  200     int (*bespoke_trigger)(struct snd_pcm_substream *, int,
  201         struct snd_soc_dai *);
  202     /*
  203      * For hardware based FIFO caused delay reporting.
  204      * Optional.
  205      */
>>206     snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
  207         struct snd_soc_dai *);                                                                                                                    
  208 };

注释比较详细了,codec音频操作接口分为5大部分:时钟配置,格式配置,数字静音,pcm音频接口,FIFO延迟。着重说下时钟配置及格式配置接口:

  • set_sysclk:codec_dai系统时钟设置,当上层打开pcm设备时,需要回调该接口设置codec的系统时钟,codec才能正常工作;
  • set_pll:codec FLL设置,codec一般接了一个MCKL输入时钟,回调该接口基于mclk来产生Codec FLL时钟,接着codec_dai的sysclk,bclk,lrclk均可从FLL分频出来(假设codec作为master);
  • set_fmt:codec_dai格式设置,具体见soc-dai.h;
  1. SND_SOC_DAIFMT_I2S:音频数据是I2S格式,常用于多媒体音频;
  2. SND_SOC_DAIFMT_DSP_A:音频数据是PCM格式,常用于通话语音;
  3. SND_SOC_DAIFMT_CBM_CFM:codec作为master,BCLK和LRCLK由codec提供;
  4. SND_SOC_DAIFMT_CBS_CFS:codec作为slave,BCLK和LRCLK由Soc/CPU提供;
  • hw_params:codec_dai硬件参数设置,根据上层设定的声道数,采样率,数据格式,来配置codec_dai相关寄存器。

以上接口一般在machine驱动中回调,由于amlogic这个平台,将相应的machine和platform综合在自己的soc/amlogic下相关的代码中,并通过dts来配置cpu-dai与codec-dai的相关联。所以为了更好的说明,我这里借用CSDN:https://blog.csdn.net/zyuanyun/article/details/59170418来说明问题。

 machine驱动goni_wm8994.c的goni_hifi_hw_params()函数:

static int goni_hifi_hw_params(struct snd_pcm_substream *substream,
        struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    struct snd_soc_dai *codec_dai = rtd->codec_dai;
    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    unsigned int pll_out = 24000000; // 这是 MCLK 的时钟频率,Codec 的源时钟
    int ret = 0;

    /* set the cpu DAI configuration */
    ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
            SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
    if (ret < 0)
        return ret;

    /* set codec DAI configuration */
    ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
            SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
    if (ret < 0)
        return ret;

    /* set the codec FLL */
    ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
            params_rate(params) * 256);
    if (ret < 0)
        return ret;

    /* set the codec system clock */
    ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
            params_rate(params) * 256, SND_SOC_CLOCK_IN);
    if (ret < 0)
        return ret;

    return 0;
}

其中snd_soc_dai_set_fmt()实际上会调用cpu_dai或codec_dai的set_fmt()回调,snd_soc_dai_set_pll()和

snd_soc_dai_set_sysclk()也类似。

  • MCLK作为Codec的源时钟,频率为24Mhz;
  • 设置cpu_dai和codec_dai格式:数据格式是i2s;codec作为master,BCLK和LRCLK由Codec提供;
  • 设置codec_dai的FLL1:时钟源是MCLK,时钟源频率是24Mhz,目的时钟频率是256fs(fs是采样频率);
  • 设置codec_dai的系统时钟:时钟源是FLL1,系统时钟频率是256fs.
对于dai(codec_dai和cpu_dai),都要非常留意时钟设置,它很关键又复杂,设置错误将会导致很多问题,典型如下:

  • 系统无声:检查codec系统时钟,codec_dai位时钟和帧时钟是否使能;
  • 声音失真:检查音频数据的采样率是否和codec_dai帧时钟一致;
  • 断续破音:检查codec系统时钟和位时钟,帧时钟是否同步,出现这种情况,可能是因为sysclk和BCKL/LRCLK不是由同一个时钟源分频出来的。
  • i2c通信不成功:若codec做从,MCLK有soc这边提供,在i2c通信之前,一定要确保mclk已经提供给codec.

以下是2种音频系统时钟设置:

  1. soc 为master,codec为slave    

Linux ALSA音频系统:platform,machine,codec_第7张图片

  •  Mclk为soc提供codec的时钟,一般为12.288Mhz,24Mhz
  •   Bclk为位时钟,一般为64FS(采样频率),比如FS=48Khz,BLCK=3.072Mhz
  •  LRclk为同步时钟,一般Fs.

2.soc为slave,codec为master.

Linux ALSA音频系统:platform,machine,codec_第8张图片

  • 此为codec给soc提供blck,lrclk.codec的时钟由晶振提供。

6.5 DAPM description

概念:Dynamic Audio Power Management,动态音频电源管理,为移动linux设备设计,使得音频系统任何时候都工作在最低功耗状态。

目的:使能最少的必要的部件,令音频系统正常工作。

原理:当音频路劲发生改变(比如上层使用amxier工具设置音频通路)时,或发生数据流事件(比如启动或停止播放)时,都会触发dapm去遍历所有邻近的音频部件,检查是否存在完整的音频路劲(complete path:满足条件的音频路劲,该路劲上任意一个部件往前遍历能到达输入端点如DAC/MiC/Linein,往后遍历能到达输出端点如ADC/HP/SPK),如果存在完整的音频路劲,则该路劲上面的所有部件都是需要上电的,其他部件则下电。


部件上下电都是dapm根据策略自主控制的,外部无法干预,可以说dapm是一个专门为音频系统设计的自成体系的电源管理模块,独立于linux电源管理之外。即使soc休眠了,codec仍可以在正常工作,试想下这个情景:语音通话,modem_dai连接到codec_dai,语音数据不经过soc,因此这种情形下soc可以进入睡眠以降低功耗,只保持codec正常工作就行了。

如下是多媒体外放回放通路:


Linux ALSA音频系统:platform,machine,codec_第9张图片

在这个例子中,codec中的音频通路是:i2s->MaxxDsp->DAC->SPKOUT.i2s是输入端点,spkout是输出端点,因此这条通路是一个complete path,这通路上的所有部件都是需要上电的,与此同时,其他部件需要上电。

而音频部件由于上下点瞬间的瞬态冲击会产生爆破音,我们称之为POPs.POPs是电气特性,我们无法彻底消除,只能硬件软件上优化消弱到人耳辨识不出的程度。DAPM中,部件的上下电有严格的顺序以抑制爆破音,总的来说:上电次序是从输入端点到输出端点,下电次序是从输出端点到输入端点。

驱动中如何创建dapm widget和dapm route?

static const struct snd_soc_dapm_widget npcp215x_dapm_widgets[] = {
  599     /* npca110p dapm route */
  600     SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),                                                                                      
  601     SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
  602     SND_SOC_DAPM_OUTPUT("DAC OUT"),
  603     SND_SOC_DAPM_INPUT("ADC IN"),
  604 };
  605 
  606 static const struct snd_soc_dapm_route npcp215x_audio_map[] = {
  607     /* ADC */
  608     {"ADC", NULL, "ADC IN"},
  609     {"DAC OUT", NULL, "DAC"},
  610 }; 

6.6 codec register

 由于npcp215x不是remap式的。这是用wm8994来说明问题:

platform_driver:

static struct platform_driver wm8994_codec_driver = {
    .driver = {
        .name = "wm8994-codec",
        .owner = THIS_MODULE,
        .pm = &wm8994_pm_ops,
    },
    .probe = wm8994_probe,
    .remove = __devexit_p(wm8994_remove),
};

与.name = "wm8994-codec"的platform_device(该platform_device在driver/mfd/wm8994-core.c中注册)匹配后,立即回调wm8994_probe()注册codec:

static int __devinit wm8994_probe(struct platform_device *pdev)
{
    struct wm8994_priv *wm8994;

    wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv),
                  GFP_KERNEL);
    if (wm8994 == NULL)
        return -ENOMEM;
    platform_set_drvdata(pdev, wm8994);

    wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent);
    wm8994->pdata = dev_get_platdata(pdev->dev.parent);

    return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
            wm8994_dai, ARRAY_SIZE(wm8994_dai));
}

snd_soc_register_codec:将codec_driver和codec_dai_driver注册到soc-core.

**
 * snd_soc_register_codec - Register a codec with the ASoC core
 *
 * @codec: codec to register
 */
int snd_soc_register_codec(struct device *dev,
               const struct snd_soc_codec_driver *codec_drv,
               struct snd_soc_dai_driver *dai_drv,
               int num_dai)
  • 创建一个snd_soc_codec实例,包含codec_drv(snd_soc_dai_driver)相关信息,封装给soc-core使用,相关代码段如下:
 struct snd_soc_codec *codec;

    dev_dbg(dev, "codec register %s\n", dev_name(dev));

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
        return -ENOMEM;

    /* create CODEC component name */
    codec->name = fmt_single_name(dev, &codec->id);
    if (codec->name == NULL) {
        kfree(codec);
        return -ENOMEM;
    }

    // 初始化 Codec 的寄存器缓存配置及读写接口
    codec->write = codec_drv->write;
    codec->read = codec_drv->read;
    codec->volatile_register = codec_drv->volatile_register;
    codec->readable_register = codec_drv->readable_register;
    codec->writable_register = codec_drv->writable_register;
    codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
    codec->dapm.bias_level = SND_SOC_BIAS_OFF;
    codec->dapm.dev = dev;
    codec->dapm.codec = codec;
    codec->dapm.seq_notifier = codec_drv->seq_notifier;
    codec->dapm.stream_event = codec_drv->stream_event;
    codec->dev = dev;
    codec->driver = codec_drv;
    codec->num_dai = num_dai;
    mutex_init(&codec->mutex);
  • 把以上codec实例插入到codec_list链表中(声卡注册时会遍历该链表,找到dai_link声明的codec并绑定):
    list_add(&codec->list, &codec_list);
  • 把codec_drv中的snd_soc_dai_driver(wm8994有3个dai,分别是aif1,aif2,aif3)注册到soc-core:
   /* register any DAIs */
    if (num_dai) {
        ret = snd_soc_register_dais(dev, dai_drv, num_dai);
        if (ret < 0)
            goto fail;
    }

snd_soc_register_dais()会把dai插入到dai_list链表中(声卡注册时会遍历该链表,找到dai_link声明的codec_dai并绑定):

list_add(&dai->list, &dai_list);

最后顺便提下codec和codec_dai的区别:codec指音频芯片共有的部分,包括codec初始化函数,控制接口,寄存器缓存,控件,dapm部件,音频路由,偏置电压设置函数等描述信息;而codec_dai指codec上的音频接口驱动描述,包括时钟配置,格式配置,能力描述等等,各个接口的描述信息不一定都是一致的,所以每个音频接口都有着各自的驱动描述。关于npcp215x的codec驱动代码,请主动联系获取。


7.platform

上面的hardware driver中提到音频platform驱动主要用于音频数据传输,这里又细分为两步:

  • 启动dma设备,把音频数据从dma buffer搬运到cpu_daiFIFO,这部分驱动用snd_soc_platform_driver描述,后面分析用pcm_dma指代它。
  • 启动数字音频接口控制器(I2S/PCM/AC97),把音频数据从cpu_dai FIFO传送到codec_dai,这部分驱动使用snd_soc_dai_driver描述,后面分析用cpu_dai指代它。

我们浏览下platform_drv中的几个重要结构体,其中浅蓝色部分是cpu_dai相关的,浅绿色部分是pcm_dma相关的。snd_soc_dai是cpu_dai注册时所创建的dai实例,snd_soc_platform是pcm_dma注册时所创建的platform实例,这些实例方便soc-core管理。

Linux ALSA音频系统:platform,machine,codec_第10张图片

7.1 cpu dai

一个典型的i2s总线控制器框图:

Linux ALSA音频系统:platform,machine,codec_第11张图片

在回顾下i2s总线协议,

Linux ALSA音频系统:platform,machine,codec_第12张图片

  • BCLK:位时钟,对应数字音频的每一位数据;BCKL = 声道数*采样频率*采样位数;
  • LRCLK:帧时钟,构成一个完整的声音单元;双声道的情况下,LRCKL=0时表示是左声道的数据,=1时表示是右声道的数据;LRCLK=采样频率;
  • DACDAT:下行数据;
  • ADCDAT:上行数据;
  • 数据的最高位总是出现在LRCKL跳变后的第2个BCLK脉冲处。

对于cpu_dai驱动,从上面的类图我们可知,主要工作有:

  • 实现dai操作函数,见snd_soc_dai_ops定义,用于配置和操作音频数字接口控制器,如时钟配置set_sysclk(),,格式配置set_fmt(),硬件参数配置hw_params(),启动/停止数据传输trigger()等;
  • 实现probe函数(初始化),remove函数(卸载),suspend/resume函数(电源管理);
  • 初始化snd_soc_dai_driver实例,包括回放和录制的能力描述,dai操作函数集,probe/remove回调,电源管理相关的suspend/resume回调;
  • 通过snd_soc_register_dai()把初始化完成的snd_soc_dai_driver注册到soc-core:首先创建一个snd_soc_dai实例,然后把该snd_soc_dai实例插入到dai_list链表(声卡注册时会遍历该链表,找到dai_link声明的cpu_dai并绑定)
/**
 * snd_soc_register_dai - Register a DAI with the ASoC core
 *
 * @dai: DAI to register
 */
int snd_soc_register_dai(struct device *dev,
        struct snd_soc_dai_driver *dai_drv)
{
    struct snd_soc_dai *dai;

    dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
    if (dai == NULL)
        return -ENOMEM;

    /* create DAI component name */
    dai->name = fmt_single_name(dev, &dai->id);
    if (dai->name == NULL) {
        kfree(dai);
        return -ENOMEM;
    }

    dai->dev = dev;
    dai->driver = dai_drv;
    if (!dai->driver->ops)
        dai->driver->ops = &null_dai_ops;

    mutex_lock(&client_mutex);
    list_add(&dai->list, &dai_list);
    mutex_unlock(&client_mutex);

    return 0;
}

dai操作函数的实现是cpu_dai驱动的主体,需要配置好相关寄存器i2s/pcm总线控制器正常运转。另外不同的平台,dma设备信息(总线地址,通道号,传输单元大小)在这里初始化,不同的平台此处有差别。以amlogic平台为例,代码位置sound/soc/amlogic/i2s.h

struct aml_i2s_dma_params {
   63     char *name;         /* stream identifier */
   64     struct snd_pcm_substream *substream;
>> 65     void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
   66 };
   74 /*--------------------------------------------------------------------------
   75  * Data types
   76  *--------------------------------------------------------------------------
   77  */
   78 struct aml_runtime_data {
   79     struct aml_i2s_dma_params *params;                                                                                                            
>> 80     dma_addr_t dma_buffer;      /* physical address of dma buffer */
>> 81     dma_addr_t dma_buffer_end;  /* first address beyond DMA buffer */
   82 
   83     struct snd_pcm *pcm;
   84     struct snd_pcm_substream *substream;
   85     struct audio_stream s;
>> 86     struct timer_list timer;    /* timeer for playback and capture */
>> 87     spinlock_t timer_lock;
   88     void *buf; /* tmp buffer for playback or capture */
   89     int active;
   90     unsigned int xrun_num;
   91 
   92     /* hrtimer */
>> 93     struct hrtimer hrtimer;
>> 94     ktime_t wakeups_per_second;
   95 };
 

7.2 pcm dma

pcm数据管理可以说是alsa系统中最核心的部分,这部分的工作有两个(回放情形):

  • copy_from_user把用户态的音频数据拷贝到dma buffer中
  • 启动dma设备把音频数据从dma buffer传送到i2s tx FIFO.

当数据送到i2s tx FIFO后,剩下的是启动i2s控制器把数据传送到codec,然后DAC把音频数字信号转换成模拟信号,在输出到SPK/HP.


为什么要使用dma传输?

1.首先在数据传输过程中,不需要cpu的参与,节省cpu的开销

2.传输速度快,提高硬件设备的吞吐量。

对于ARM,它不能直接把数据从A地址搬运到B地址,只能把数据从A地址搬运到一个寄存器,然后在从这个寄存器搬运到B地址;而dma有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,尤其适合大数据的高速传输。一个dma传输块里面,可以划分为若干个周期,毎传输完一个周期产生一个中断。


下面来看整个pcm_dma驱动:

snd_pcm_substream是pcm native关键结构体,上图可以看出这个结构体包含了音频数据传输所需的重要信息:pcm ops操作函数集和dma buffer.

我们先看看dma设备相关的结构,对于回放来说,dma设备把内存缓冲区的音频数据传送到i2s tx FIFO;对于录制来说,dma设备把i2s rx FIFO的音频数据传送到内存缓存区。因此在dma设备传输之前,必须确定data buffer和i2s FIFO的信息。

snd_dma_buffer:数据缓存区,用于保存从用户态拷贝过来的音频数据;包含dma buffer的物理首地址,虚拟首地址,大小等信息;其中物理地址用于设定dma传输的源地址(回放情形)或目的地址(录制情形),虚拟地址用于与用户态之间的音频数据拷贝。

dma_params:dma设备描述,包括设备总线地址(回放情况下为i2s tx FIFO首地址,设置为dma传输的目的地址),dma通道号,dma传输单元大小,这些信息在i2s.c中初始化。

runtime_data:dma运行期信息

  • state:记录dma设备状态,启动或停止;
  • dma_loaded:dma装载计数,毎当启动一次dma传输,该计数加一;每当完成一次dma传输,该计数减一;
  • dma_period:dma周期数据大小;
  • dma_start:指向dma buffer物理首地址;
  • dma_pos:记录dma buffer当前指针位置,当dma毎传输一次,都会更新该指针;
  • dma_end:dma_buffer结束位置;
  • params:dma设备描述信息,包括设备总线地址,dma通道号,传输单元大小。

7.3 pcm operations

操作函数的实现是本模块的主体,见snd_pcm_ops结构体描述:

    66 struct snd_pcm_ops {
    67     int (*open)(struct snd_pcm_substream *substream);
    68     int (*close)(struct snd_pcm_substream *substream);
    69     int (*ioctl)(struct snd_pcm_substream * substream,
    70              unsigned int cmd, void *arg);
    71     int (*hw_params)(struct snd_pcm_substream *substream,
    72              struct snd_pcm_hw_params *params);
    73     int (*hw_free)(struct snd_pcm_substream *substream);
    74     int (*prepare)(struct snd_pcm_substream *substream);
    75     int (*trigger)(struct snd_pcm_substream *substream, int cmd);
    76     snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
    77     int (*get_time_info)(struct snd_pcm_substream *substream,
    78             struct timespec *system_ts, struct timespec *audio_ts,
    79             struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
    80             struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
>>  81     int (*copy)(struct snd_pcm_substream *substream, int channel,
    82             snd_pcm_uframes_t pos,
>>  83             void __user *buf, snd_pcm_uframes_t count);
    84     int (*silence)(struct snd_pcm_substream *substream, int channel, 
    85                snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
    86     struct page *(*page)(struct snd_pcm_substream *substream,
    87                  unsigned long offset);
    88     int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
    89     int (*ack)(struct snd_pcm_substream *substream);                                                                                             
    90 };

下面介绍几个重要的接口:

  • open:打开pcm逻辑设备时,回调该函数设定dma设备的硬件约束;并申请一个私有结构,保存dma设备资源如通道号,传输单元,缓存区信息,io信息等,保存在runtime->private_data.代码如下:
static const struct snd_pcm_hardware dma_hardware = {
    .info           = SNDRV_PCM_INFO_INTERLEAVED |
                    SNDRV_PCM_INFO_BLOCK_TRANSFER |
                    SNDRV_PCM_INFO_MMAP |
                    SNDRV_PCM_INFO_MMAP_VALID |
                    SNDRV_PCM_INFO_PAUSE |
                    SNDRV_PCM_INFO_RESUME,
    .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                    SNDRV_PCM_FMTBIT_U16_LE |
                    SNDRV_PCM_FMTBIT_U8 |
                    SNDRV_PCM_FMTBIT_S8,
    .channels_min       = 2,
    .channels_max       = 2,
    .buffer_bytes_max   = 128*1024,
    .period_bytes_min   = PAGE_SIZE,
    .period_bytes_max   = PAGE_SIZE*2,
    .periods_min        = 2,
    .periods_max        = 128,
    .fifo_size      = 32,
};

static int dma_open(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct runtime_data *prtd;

    pr_debug("Entered %s\n", __func__);

    // 设置 dma 设备的硬件约束
    snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
    snd_soc_set_runtime_hwparams(substream, &dma_hardware);

    // 为 runtime_data 分配内存,用于保存 dma 资源,包括缓冲区信息、IO 设备信息、通道号、传输单元大小 
    prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
    if (prtd == NULL)
        return -ENOMEM;

    spin_lock_init(&prtd->lock);

    // runtime 的私有数据指向 runtime_data 
    runtime->private_data = prtd;
    return 0;
}
  • hw_params:设置硬件参数时(cmd=SNDRV_PCM_IOCTL_HW_PARAMS),回调该函数初始化dma资源,包括通道号,传输单元,缓冲区信息,io设备信息等。代码如下:
static int dma_hw_params(struct snd_pcm_substream *substream,
    struct snd_pcm_hw_params *params)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct runtime_data *prtd = runtime->private_data;
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    unsigned long totbytes = params_buffer_bytes(params); 
    struct s3c_dma_params *dma =
        snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从 cpu_dai 驱动 i2s.c 取得 dma 设备资源
    struct samsung_dma_info dma_info;

    /* return if this is a bufferless transfer e.g.
     * codec <--> BT codec or GSM modem -- lg FIXME */
    if (!dma)
        return 0;

    /* this may get called several times by oss emulation
     * with different params -HW */
    if (prtd->params == NULL) {
        /* prepare DMA */
        prtd->params = dma; // 该字段保存的是 dma 设备资源,如 I2S tx FIFO 地址、dma 通道号、dma 传输单元等

        prtd->params->ops = samsung_dma_get_ops(); // 平台的 dma 操作函数,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c

        //...
        prtd->params->ch = prtd->params->ops->request(
                prtd->params->channel, &dma_info);
    }

    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里把 dma buffer 相关信息赋给 substream runtime,注意 dma_buffer 在创建 pcm 逻辑设备时分配

    runtime->dma_bytes = totbytes;

    spin_lock_irq(&prtd->lock);
    prtd->dma_loaded = 0;
    prtd->dma_period = params_period_bytes(params);
    prtd->dma_start = runtime->dma_addr; // dma buffer 物理首地址
    prtd->dma_pos = prtd->dma_start;
    prtd->dma_end = prtd->dma_start + totbytes;
    spin_unlock_irq(&prtd->lock);

    return 0;
}
  • prepare:当数据已准备好(cmd=SNDRV_PCM_IOCTL_PREPARE),回调该函数告知dma设备数据已就绪。代码如下:
static int dma_prepare(struct snd_pcm_substream *substream)
{
    struct runtime_data *prtd = substream->runtime->private_data;
    int ret = 0;

    pr_debug("Entered %s\n", __func__);

    /* return if this is a bufferless transfer e.g.
     * codec <--> BT codec or GSM modem -- lg FIXME */
    if (!prtd->params)
        return 0;

    /* flush the DMA channel */
    prtd->params->ops->flush(prtd->params->ch);

    prtd->dma_loaded = 0; // 初始化 dma 装载计数
    prtd->dma_pos = prtd->dma_start; // 设置 dma buffer 当前指针为 dma buffer 首地址

    /* enqueue dma buffers */
    dma_enqueue(substream); // 插入到 dma 传输队列中

    return ret;
}

dma_enqueue()函数,把当前dma buffer插入到dma传输队列中。当触发trigger()启动dma设备传输后,将会把dma buffer数据传送到FIFO(回放情形)。

注意:每次dma传输完一个周期的数据后,都要调用snd_pcm_period_elapsed()告知pcm native一个周期的数据已经传送到FIFO上了,然后再次调用dma_enqueue(),dma传输..如此循环,直到触发trigger()停止dma传输。

  • trigger:数据传送 开始/停止/暂停/恢复 时,回调该函数启动或停止dma传输(当上层第一次调用pcm_write()时,触发trigger())启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger()停止dma传输).trigger()函数里面的操作必须是原子的,不能调用可能睡眠的操作,并且尽量简单。代码如下:
static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
    struct runtime_data *prtd = substream->runtime->private_data;
    int ret = 0;

    pr_debug("Entered %s\n", __func__);

    spin_lock(&prtd->lock);

    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        prtd->state |= ST_RUNNING;
        prtd->params->ops->trigger(prtd->params->ch); // 启动 dma 传输
        break;

    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        prtd->state &= ~ST_RUNNING;
        prtd->params->ops->stop(prtd->params->ch); // 停止 dma 传输
        break;

    default:
        ret = -EINVAL;
        break;
    }

    spin_unlock(&prtd->lock);

    return ret;
}
  • pointer:dma毎完成一次传输,都会调用该函数获得传输数据的当前位置,这样pcm native可计算dma buffer指针位置及可用空间。该函数也是原子的。代码如下:
static snd_pcm_uframes_t
dma_pointer(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct runtime_data *prtd = runtime->private_data;
    unsigned long res;

    res = prtd->dma_pos - prtd->dma_start; // 当前位置减去首地址,其实就是已传输数据的大小

    /* we seem to be getting the odd error from the pcm library due
     * to out-of-bounds pointers. this is maybe due to the dma engine
     * not having loaded the new values for the channel before being
     * called... (todo - fix )
     */
    if (res >= snd_pcm_lib_buffer_bytes(substream)) {
        if (res == snd_pcm_lib_buffer_bytes(substream))
            res = 0;
    }

    return bytes_to_frames(substream->runtime, res); // 单位转化为 frames
}

7.3 dma buffer allocation

在pcm operations小节,数次提及dma buffer,即dma数据缓冲区。dma buffer的分配,一般发生在pcm_dma驱动初始化阶段probe()或pcm逻辑设备创建阶段pcm_new().代码如下:

       static int aml_i2s_probe(struct snd_soc_platform *platform)
  1348 {
  1349     return snd_soc_add_platform_controls(platform,
  1350             aml_i2s_controls, ARRAY_SIZE(aml_i2s_controls));
  1351 }
       static struct snd_pcm_ops aml_i2s_ops = {                                                                                                        
  1211     .open = aml_i2s_open,
  1212     .close = aml_i2s_close,
  1213     .ioctl = snd_pcm_lib_ioctl,
  1214     .hw_params = aml_i2s_hw_params,
  1215     .hw_free = aml_i2s_hw_free,
  1216     .prepare = aml_i2s_prepare,
  1217     .trigger = aml_i2s_trigger,
  1218     .pointer = aml_i2s_pointer,
  1219 #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP
  1220     .mmap = aml_pcm_mmap,
  1221 #else
  1222     .copy = aml_i2s_copy,
  1223 #endif 
  1224     .silence = aml_i2s_silence,
  1225 };
  1233 static int aml_i2s_new(struct snd_soc_pcm_runtime *rtd)
  1234 {
  1235     int ret = 0;
  1236     struct snd_soc_card *card = rtd->card;
  1237     struct snd_pcm *pcm = rtd->pcm;
  1238 
  1239     if (!card->dev->dma_mask)
  1240         card->dev->dma_mask = &aml_i2s_dmamask;
  1241     if (!card->dev->coherent_dma_mask)
  1242         card->dev->coherent_dma_mask = 0xffffffff;
  1243 
  1244     if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
  1245         ret = aml_i2s_preallocate_dma_buffer(pcm,
  1246                              SNDRV_PCM_STREAM_PLAYBACK);
  1247         if (ret)
  1248             goto out;
  1249     }
  1250 
  1251     if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
  1252         ret = aml_i2s_preallocate_dma_buffer(pcm,
  1253                              SNDRV_PCM_STREAM_CAPTURE);
  1254         if (ret)
  1255             goto out;
  1256     }
  1257 
  1258  out:
  1259     return ret;
  1260 }
  1262 static void aml_i2s_free_dma_buffers(struct snd_pcm *pcm)
  1263 {
  1264     struct snd_pcm_substream *substream;
  1265     struct snd_dma_buffer *buf;
  1266     struct aml_audio_buffer *tmp_buf;
  1267     int stream;
  1268 
  1269     for (stream = 0; stream < 2; stream++) {
  1270         substream = pcm->streams[stream].substream;
  1271         if (!substream)
  1272             continue;
  1273 
  1274         buf = &substream->dma_buffer;
  1275         if (!buf->area)
  1276             continue;
  1277         dma_free_coherent(pcm->card->dev, buf->bytes,
  1278                   buf->area, buf->addr);
  1279         buf->area = NULL;
  1280 
  1281         tmp_buf = buf->private_data;
  1282         if (tmp_buf->buffer_start != NULL && tmp_buf != NULL)
  1283             kfree(tmp_buf->buffer_start);
  1284         if (tmp_buf != NULL)
  1285             kfree(tmp_buf);
  1286         buf->private_data = NULL;
  1287     }
  1288 }
  1353 struct snd_soc_platform_driver aml_soc_platform = {
  1354     .probe = aml_i2s_probe,
  1355     .ops = &aml_i2s_ops,
  1356     .pcm_new = aml_i2s_new,
  1357     .pcm_free = aml_i2s_free_dma_buffers,
  1358 };

当soc-core调用soc_new_pcm()创建pcm逻辑设备时,会调用pcm_new()完成dma buffer内存分配,注意回放子流和录制子流有着各自的dma buffer.

7.4 pcm dma register

上两个小节,我们介绍了pcm_dma接口函数的作用及实现和dma buffer的分配,本小节分析pcm_dma注册过程。

1413 static struct platform_driver aml_i2s_driver = {                         
  1414     .driver = {
  1415         .name           = "aml-i2s",
  1416         .owner          = THIS_MODULE,
  1417         .of_match_table = amlogic_audio_dt_match,
  1418 #ifdef CONFIG_HIBERNATION
  1419         .pm             = &aml_i2s_pm,
  1420 #endif
  1421         },
  1422 
  1423     .probe = aml_soc_platform_probe,
  1424     .remove = aml_soc_platform_remove,
  1425 };

与.name="aml-i2s"的platform_device匹配后,系统会调用aml_soc_platform_probe()注册platform:

 1360 static int aml_soc_platform_probe(struct platform_device *pdev)          
  1361 {
  1362     return snd_soc_register_platform(&pdev->dev, &aml_soc_platform);
  1363 }

snd_soc_register_platform:将platform_drv注册到soc-core.

  • 创建一个snd_soc_platform实例,包含platform_drv(snd_soc_platform_driver)的相关信息,封装给soc-core使用;
  • 把以上创建的platform实例插入到platform_list链表上(声卡注册时会遍历该链表,找到dai_link声明的platform并绑定)。

代码实现:

3152 /**
  3153  * snd_soc_register_platform - Register a platform with the ASoC core
  3154  *
  3155  * @dev: The device for the platform
  3156  * @platform_drv: The driver for the platform
  3157  */
  3158 int snd_soc_register_platform(struct device *dev,                                                                                                
  3159         const struct snd_soc_platform_driver *platform_drv)
  3160 {
  3161     struct snd_soc_platform *platform;
  3162     int ret;
  3163 
  3164     dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev));
  3165 
  3166     platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
  3167     if (platform == NULL)
  3168         return -ENOMEM;
  3169 
  3170     ret = snd_soc_add_platform(dev, platform, platform_drv);
  3171     if (ret)
  3172         kfree(platform);
  3173 
  3174     return ret;
  3175 }
 

至此,完成了platform驱动的实现。回放情形下,pcm_dma设备负责把dma buffer中的数据搬运到i2s tx FIFO,i2s总线控制器负责把i2s tx FIFO中的数据传送到codec.

8.Machine

      开篇讲过,amlogic这个平台没有专门的machine驱动,通过dts来配置声卡,并在soc/amlogic目录里面去解析dts,所以本章同.

     上面讲解了codec,platform驱动,但仅有codec,platform驱动是不能工作的,需要一个角色把codec,codec_dai,cpu_dai,platform给连接起来才能构成一个完整的音频链路,这个角色就由machine_drv承担了。如下是一个典型的智能手机音频框图:

+------------+        +---------------------+        +------------+
|            |        |                     |        |            |
|            |        +        CODEC        +        |            |
|     AP     +------>AIF1                 AIF3+------>     PA     +->SPK
|            |        +   +-----+ +-----+   +        |            |
|            |        |   | DSP | | DAC |   |        |            |
+------------+        |   +-----+ +-----+   |        +------------+
                      |   +-----+ +-----+   |
                      |   | DSP | | DAC |   |
                      |   +-----+ +-----+   |
+------------+        |   +-----+ +-----+   |        +------------+
|            |        |   | DSP | | ADC |   |        |            |
|            |        +   +-----+ +-----+   +        |            |
|     BB     +------>AIF2 +-----+ +-----+ AIF4+------>    BTSCO   |
|            |        +   | DSP | | ADC |   +        |            |
|            |        |   +-----+ +-----+   |        |            |
+------------+        +----------+----------+        +------------+
                          |      |     |
                          +MIC   +HP   +EARP
 

组成了4个音频链路(dai_link):

  • AP<>AIF1:AP(应用处理器)与codec之间的链路,多媒体声音
  • BB<>AIF2:BB(基带处理器)与codec之间的链路,通话语音
  • PA<>AIF3:PA(智能功率放大器)与codec之间的链路,外放输出
  • BTSCO<>AIF4:BTSCO(蓝牙)与codec之间的链路,蓝牙耳机输出

snd_soc_dai_link结构体:

970 struct snd_soc_dai_link {                                                                                                                        
   971     /* config - must be set by machine driver */
   972     const char *name;           /* Codec name */
   973     const char *stream_name;        /* Stream name */
   974     /*
   975      * You MAY specify the link's CPU-side device, either by device name,
   976      * or by DT/OF node, but not both. If this information is omitted,
   977      * the CPU-side DAI is matched using .cpu_dai_name only, which hence
   978      * must be globally unique. These fields are currently typically used
   979      * only for codec to codec links, or systems using device tree.
   980      */
   981     const char *cpu_name;
   982     struct device_node *cpu_of_node;
   983     /*
   984      * You MAY specify the DAI name of the CPU DAI. If this information is
   985      * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
   986      * only, which only works well when that device exposes a single DAI.
   987      */
   988     const char *cpu_dai_name;
   989     /*
   990      * You MUST specify the link's codec, either by device name, or by
   991      * DT/OF node, but not both.
   992      */
   993     const char *codec_name;
   994     struct device_node *codec_of_node;
   995     /* You MUST specify the DAI name within the codec */
   996     const char *codec_dai_name;
   997 
   998     struct snd_soc_dai_link_component *codecs;
   999     unsigned int num_codecs;
  1000 
  1001     /*
  1002      * You MAY specify the link's platform/PCM/DMA driver, either by
  1003      * device name, or by DT/OF node, but not both. Some forms of link
  1004      * do not need a platform.
  1005      */
  1006     const char *platform_name;
  1007     struct device_node *platform_of_node;
  1008     int id; /* optional ID for machine driver link identification */
1010     const struct snd_soc_pcm_stream *params;
  1011     unsigned int num_params;
  1012 
  1013     unsigned int dai_fmt;           /* format to set on init */
  1014 
  1015     enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
  1016 
  1017     /* codec/machine specific init - e.g. add machine controls */
  1018     int (*init)(struct snd_soc_pcm_runtime *rtd);
  1019 
  1020     /* optional hw_params re-writing for BE and FE sync */
  1021     int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
  1022             struct snd_pcm_hw_params *params);
  1023 
  1024     /* machine stream operations */
  1025     const struct snd_soc_ops *ops;
  1026     const struct snd_soc_compr_ops *compr_ops;
  1027 
  1028     /* For unidirectional dai links */
  1029     bool playback_only;
  1030     bool capture_only;
  1031 
  1032     /* Mark this pcm with non atomic ops */
  1033     bool nonatomic;
  1034 
  1035     /* Keep DAI active over suspend */
  1036     unsigned int ignore_suspend:1;
  1037 
  1038     /* Symmetry requirements */
  1039     unsigned int symmetric_rates:1;
  1040     unsigned int symmetric_channels:1;
  1041     unsigned int symmetric_samplebits:1;
  1042 
  1043     /* Do not create a PCM for this DAI link (Backend link) */
  1044     unsigned int no_pcm:1;                                                                                                                       
  1045 
  1046     /* This DAI link can route to other DAI links at runtime (Frontend)*/
  1047     unsigned int dynamic:1;
1049     /* DPCM capture and Playback support */
  1050     unsigned int dpcm_capture:1;
  1051     unsigned int dpcm_playback:1;
  1052 
  1053     /* DPCM used FE & BE merged format */
  1054     unsigned int dpcm_merged_format:1;
  1055 
  1056     /* pmdown_time is ignored at stop */
  1057     unsigned int ignore_pmdown_time:1;
  1058 
  1059     struct list_head list; /* DAI link list of the soc card */
  1060     struct snd_soc_dobj dobj; /* For topology */
  1061 };

注释比较详细,重点介绍如下几个字段:

  • codec_name:音频链路需要绑定的codec名称,声卡注册时会遍历codec_list,找到同名的codec并绑定;
  • platform_name:音频链路需要绑定的platform名称,声卡注册时会遍历platform_list,找到同名的platform并绑定;
  • cpu_dai_name:音频链路需要绑定的cpu_dai名称,声卡注册时会遍历dai_list,找到同名的dai并绑定;
  • ops:重点留意hw_params()回调,一般来说这个回调是要实现的,用于配置codec,codec_dai,cpu_dai的数据格式和系统时钟。

goni_wm8994.c 中的 dai_link 定义,两个音频链路分别用于 Media 和 Voice:

static struct snd_soc_dai_link goni_dai[] = {
{
    .name = "WM8994",
    .stream_name = "WM8994 HiFi",
    .cpu_dai_name = "samsung-i2s.0",
    .codec_dai_name = "wm8994-aif1",
    .platform_name = "samsung-audio",
    .codec_name = "wm8994-codec.0-001a",
    .init = goni_wm8994_init,
    .ops = &goni_hifi_ops,
}, {
    .name = "WM8994 Voice",
    .stream_name = "Voice",
    .cpu_dai_name = "goni-voice-dai",
    .codec_dai_name = "wm8994-aif2",
    .codec_name = "wm8994-codec.0-001a",
    .ops = &goni_voice_ops,
},
};

除了 dai_link,机器中一些特定的音频控件和音频事件也可以在 machine_drv 定义,如耳机插拔检测、外部功放打开关闭等。

我们再分析 machine_drv 初始化过程:

static struct snd_soc_card goni = {
    .name = "goni",
    .owner = THIS_MODULE,
    .dai_link = goni_dai,
    .num_links = ARRAY_SIZE(goni_dai),

    .dapm_widgets = goni_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),
    .dapm_routes = goni_dapm_routes,
    .num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),
};

static int __init goni_init(void)
{
    int ret;

    goni_snd_device = platform_device_alloc("soc-audio", -1);
    if (!goni_snd_device)
        return -ENOMEM;

    /* register voice DAI here */
    ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);
    if (ret) {
        platform_device_put(goni_snd_device);
        return ret;
    }

    platform_set_drvdata(goni_snd_device, &goni);
    ret = platform_device_add(goni_snd_device);

    if (ret) {
        snd_soc_unregister_dai(&goni_snd_device->dev);
        platform_device_put(goni_snd_device);
    }

    return ret;
}
  • 创建一个.name="soc-audio"的platform_device实例;
  • 设置platform_device的私有数据snd_soc_card;
  • 然后注册platform_device到系统中;
  • platform_device与Platform_driver配对。
/* ASoC platform driver */
static struct platform_driver soc_driver = {
    .driver     = {
        .name       = "soc-audio",
        .owner      = THIS_MODULE,
        .pm     = &snd_soc_pm_ops,
    },
    .probe      = soc_probe,
    .remove     = soc_remove,
};

static int __init snd_soc_init(void)
{
    // ...
    snd_soc_util_init();
    return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);

二者匹配后,soc_probe()会被调用,继而调用snd_soc_register_card()注册声卡。流程图如下:

Linux ALSA音频系统:platform,machine,codec_第13张图片

  • 取出platform_device的私有数据,该私有数据就是snd_soc_card;
  • snd_soc_register_card()为每个dai_link分配一个snd_soc_pcm_runtime实例,snd_soc_pcm_runtime是Asoc的桥梁,保存着codec,codec_dai,cpu_dai,platform等硬件设备实例。
  • 随后的工作都在snd_soc_instantiate_card()进行;
  • 遍历dai_list,codec_list,platform_list链表,为每个音频链路找到对应的cpu_dai,codec_dai,codec,platfrom;找到的cpu_dai,codec_dai,codec,platform保存到snd_soc_pcm_runtime,完成音频链路的设备绑定;
  • 调用snd_card_create()创建声卡;
  • soc_probe_dai_link()依次回调cpu_dai,codec,platform,codec_dai的probe()函数,完成各音频设备的初始化,随后调用soc_new_pcm()创建pcm逻辑设备
  • 最后调用snd_card_register()注册声卡。

soc_new_pcm源码分析:

 2649 /* create a new pcm */
  2650 int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
  2651 {
  2652     struct snd_soc_platform *platform = rtd->platform;
  2653     struct snd_soc_dai *codec_dai;
  2654     struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
  2655     struct snd_pcm *pcm;
  2656     char new_name[64];
  2657     int ret = 0, playback = 0, capture = 0;
  2658     int i;
  2659 
  2660     if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
  2661         playback = rtd->dai_link->dpcm_playback;
  2662         capture = rtd->dai_link->dpcm_capture;
  2663     } else {
  2664         for (i = 0; i < rtd->num_codecs; i++) {
  2665             codec_dai = rtd->codec_dais[i];
  2666             if (codec_dai->driver->playback.channels_min)
  2667                 playback = 1;
  2668             if (codec_dai->driver->capture.channels_min)
  2669                 capture = 1;
  2670         }
  2671 
  2672         capture = capture && cpu_dai->driver->capture.channels_min;
  2673         playback = playback && cpu_dai->driver->playback.channels_min;
  2674     }
  2675 
  2676     if (rtd->dai_link->playback_only) {
  2677         playback = 1;
  2678         capture = 0;
  2679     }
  2680 
  2681     if (rtd->dai_link->capture_only) {                                                                                                           
  2682         playback = 0;
  2683         capture = 1;
  2684     }
2681     if (rtd->dai_link->capture_only) {
  2682         playback = 0;
  2683         capture = 1;
  2684     }
  2685 
  2686     /* create the PCM */
  2687     if (rtd->dai_link->no_pcm) {
  2688         snprintf(new_name, sizeof(new_name), "(%s)",
  2689             rtd->dai_link->stream_name);
  2690 
  2691         ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
  2692                 playback, capture, &pcm);
  2693     } else {
  2694         if (rtd->dai_link->dynamic)
  2695             snprintf(new_name, sizeof(new_name), "%s (*)",
  2696                 rtd->dai_link->stream_name);
  2697         else
  2698             snprintf(new_name, sizeof(new_name), "%s %s-%d",
  2699                 rtd->dai_link->stream_name,
  2700                 (rtd->num_codecs > 1) ?
  2701                 "multicodec" : rtd->codec_dai->name, num);
  2702 
  2703         ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
  2704             capture, &pcm);
  2705     }
  2706     if (ret < 0) {
  2707         dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
  2708             rtd->dai_link->name);
  2709         return ret;
  2710     }
  2711     dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);
  2712 
  2713     /* DAPM dai link stream work */
  2714     INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
  2715 
  2716     pcm->nonatomic = rtd->dai_link->nonatomic;                                                                                                   
  2717     rtd->pcm = pcm;
  2718     pcm->private_data = rtd;
2720     if (rtd->dai_link->no_pcm) {
  2721         if (playback)
  2722             pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
  2723         if (capture)
  2724             pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
  2725         goto out;
  2726     }
  2727 
  2728     /* ASoC PCM operations */
  2729     if (rtd->dai_link->dynamic) {
  2730         rtd->ops.open       = dpcm_fe_dai_open;
  2731         rtd->ops.hw_params  = dpcm_fe_dai_hw_params;
  2732         rtd->ops.prepare    = dpcm_fe_dai_prepare;
  2733         rtd->ops.trigger    = dpcm_fe_dai_trigger;
  2734         rtd->ops.hw_free    = dpcm_fe_dai_hw_free;
  2735         rtd->ops.close      = dpcm_fe_dai_close;
  2736         rtd->ops.pointer    = soc_pcm_pointer;
  2737         rtd->ops.ioctl      = soc_pcm_ioctl;
  2738     } else {
  2739         rtd->ops.open       = soc_pcm_open;
  2740         rtd->ops.hw_params  = soc_pcm_hw_params;
  2741         rtd->ops.prepare    = soc_pcm_prepare;
  2742         rtd->ops.trigger    = soc_pcm_trigger;
  2743         rtd->ops.hw_free    = soc_pcm_hw_free;
  2744         rtd->ops.close      = soc_pcm_close;
  2745         rtd->ops.pointer    = soc_pcm_pointer;
  2746         rtd->ops.ioctl      = soc_pcm_ioctl;
  2747     }
  2748 
  2749     if (platform->driver->ops) {
  2750         rtd->ops.ack        = platform->driver->ops->ack;
  2751         rtd->ops.copy       = platform->driver->ops->copy;
  2752         rtd->ops.silence    = platform->driver->ops->silence;
  2753         rtd->ops.page       = platform->driver->ops->page;
  2754         rtd->ops.mmap       = platform->driver->ops->mmap;
  2755     }
  2756                                                                                                                                                  
  2757     if (playback)
  2758         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
2757     if (playback)
  2758         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
  2759 
  2760     if (capture)
  2761         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
  2762 
  2763     if (platform->driver->pcm_new) {
  2764         ret = platform->driver->pcm_new(rtd);
  2765         if (ret < 0) {
  2766             dev_err(platform->dev,
  2767                 "ASoC: pcm constructor failed: %d\n",
  2768                 ret);
  2769             return ret;
  2770         }
  2771     }
  2772 
  2773     pcm->private_free = platform->driver->pcm_free;
  2774 out:
  2775     dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
  2776          (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
  2777          cpu_dai->name);
  2778     return ret;
  2779 }
可见soc_new_pcm()最主要的工作是创建pcm逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的pcm操作函数(数据搬运时,需要调用这些函数来驱动codec,codec_dai,cpu_dai,dma设备工作)


你可能感兴趣的:(Linux,Alsa,driver,linux,alsa,linux,device,driver)