Rockchip RK3399 - Codec驱动基础

发布时间 2023-07-08 11:52:26作者: 大奥特曼打小怪兽

在上一篇博客我们介绍了ALSA子系统的软件架构,同时介绍了ALSA CORE核心数据结构和相关API。本节我们将会介绍ASoC软件体系中音频三大驱动模块:Codec、Platform 和Machine。

一、ASoC核心数据结构

我们首先来了解Codec、Platform 和Machine驱动中涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。本节介绍的数据结构大部分位于include/sound/soc.h头文件中。

1.1 Machine

Machine driver描述了如何控制platform、codec、cpu dai(Digital Audio Interface,数字音频接口)和codec dai,使得互相配合在一起工作。单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,描述Machine 的最主要的几个数据结构分别是:snd_soc_card,snd_soc_dai,snd_soc_dai_driver、snd_soc_dai_link;当然了此外还有一些操作集相关的数据结构,比如snd_soc_ops、snd_soc_dai_ops;

  • snd_soc_card:ASoC中的核心数据结构,和ASLA CORE中的snd_card地位一样;用于对ASocC中的声卡设备进行统一抽象;
  • snd_soc_dai和snd_soc_dai_driver:用于描述dai以及dai驱动,根据codec端和soc端,分为codec_dai 和cpu_dai,在ASoC的Platform驱动和Codec驱动中也会使用到;所以这个我们单独拎出来说;
  • snd_soc_dai_link:用来描述音频数据链路以及板级操作函数,在snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字;
1.1.1 struct snd_soc_card

ASoC中使用struct snd_soc_card数据结构来描述SoC声卡的所有信息,需要将该数据结构与我们上一节介绍的ALSA CORE中的struct snd_card区分开来;struct snd_soc_card定义在include/sound/soc.h;

/* SoC card */
struct snd_soc_card {
        const char *name;
        const char *long_name;
        const char *driver_name;
        const char *components;
#ifdef CONFIG_DMI
        char dmi_longname[80];
#endif /* CONFIG_DMI */
        char topology_shortname[32];

        struct device *dev;
        struct snd_card *snd_card;
        struct module *owner;

        struct mutex mutex;
        struct mutex dapm_mutex;

        /* Mutex for PCM operations */
        struct mutex pcm_mutex;
        enum snd_soc_pcm_subclass pcm_subclass;

        int (*probe)(struct snd_soc_card *card);
        int (*late_probe)(struct snd_soc_card *card);
        void (*fixup_controls)(struct snd_soc_card *card);
        int (*remove)(struct snd_soc_card *card);

        /* the pre and post PM functions are used to do any PM work before and
         * after the codec and DAI's do any PM work. */
        int (*suspend_pre)(struct snd_soc_card *card);
        int (*suspend_post)(struct snd_soc_card *card);
        int (*resume_pre)(struct snd_soc_card *card);
        int (*resume_post)(struct snd_soc_card *card);

        /* callbacks */
        int (*set_bias_level)(struct snd_soc_card *,
                              struct snd_soc_dapm_context *dapm,
                              enum snd_soc_bias_level level);
        int (*set_bias_level_post)(struct snd_soc_card *,
                                   struct snd_soc_dapm_context *dapm,
                                   enum snd_soc_bias_level level);

        int (*add_dai_link)(struct snd_soc_card *,
                            struct snd_soc_dai_link *link);
        void (*remove_dai_link)(struct snd_soc_card *,
                            struct snd_soc_dai_link *link);

        long pmdown_time;
        /* CPU <--> Codec DAI links  */
        struct snd_soc_dai_link *dai_link;  /* predefined links only */
        int num_links;  /* predefined links only */

        struct list_head rtd_list;
        int num_rtd;

        /* optional codec specific configuration */
        struct snd_soc_codec_conf *codec_conf;
        int num_configs;

        /*
         * optional auxiliary devices such as amplifiers or codecs with DAI
         * link unused
         */
        struct snd_soc_aux_dev *aux_dev;
        int num_aux_devs;
        struct list_head aux_comp_list;

        const struct snd_kcontrol_new *controls;
        int num_controls;

        /*
         * Card-specific routes and widgets.
         * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in.
         */
        const struct snd_soc_dapm_widget *dapm_widgets;
        int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        int num_dapm_routes;
        const struct snd_soc_dapm_widget *of_dapm_widgets;
        int num_of_dapm_widgets;
        const struct snd_soc_dapm_route *of_dapm_routes;
        int num_of_dapm_routes;

        /* lists of probed devices belonging to this card */
        struct list_head component_dev_list;
        struct list_head list;

        struct list_head widgets;
        struct list_head paths;
        struct list_head dapm_list;
        struct list_head dapm_dirty;

        /* attached dynamic objects */
        struct list_head dobj_list;

        /* Generic DAPM context for the card */
        struct snd_soc_dapm_context dapm;
        struct snd_soc_dapm_stats dapm_stats;
        struct snd_soc_dapm_update *update;
#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_card_root;
#endif
#ifdef CONFIG_PM_SLEEP
        struct work_struct deferred_resume_work;
#endif
        u32 pop_time;

        /* bit field */
        unsigned int instantiated:1;
        unsigned int topology_shortname_created:1;
        unsigned int fully_routed:1;
        unsigned int disable_route_checks:1;
        unsigned int probed:1;
        unsigned int component_chaining:1;

        void *drvdata;
};

这个数据结构的内容比较多,我们只挑一些重点说一下:

  • name:声卡的名称,保存解析设备节点label或者simple-audio-card,name得到的信息;
  • long_name:更详细的名称;
  • driver_name:驱动程序的名称;
  • components:组件名称;
  • dev:分配给此声卡的设备;一般设置为平台设备的device;
  • snd_card:ALSA CORE中的声卡设备;
  • owner:指向驱动程序拥有者模块的指针;
  • pcm_subclass:PCM子类的枚举类型;
  • probe:probe是可选的函数,用于在设备被探测时执行特定的操作;
  • late_probe:
  • remove:remove是可选的函数,用于在设备被移除时执行特定的操作;
  • add_dai_link:
  • remove_dai_link:
  • dai_link:指向动态分配得到的数组,每个元素都一个struct snd_soc_dai_link,即每一元素描述了一条音频数据链路(解析设备节点simple-audio-card,cpu、simple-audio-card,codec得到的);
  • num_links:dai_link指向数组的长度;
  • codec_conf:指向动态分配得到的数组,每个元素都是一个struct snd_soc_codec_conf,即每个元素描述一个codec_conf;
  • num_configs:codec_conf指向数组的长度;
  • aux_dev:指向动态分配得到的数组,每个元素都是一个struct snd_soc_aux_dev,保存解析设备节点simple-audio-card,aux-devs得到的aux_dev;
  • num_aux_devs:aux_dev指向的数组的长度;
  • controls:指向动态分配得到的数组,每个元素都是一个struct snd_kcontrol_new,保存解析设备节点simple-audio-card,pin-switches得到的control信息;
  • num_controls:controls指向的数组的长度;
  • dapm_widgets:
  • num_dapm_widgets:
  • dapm_routes:
  • num_dapm_routes:
  • of_dapm_widgets:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_widget ,保存解析设备节点simple-audio-card,widgets得到的音频控件信息;
  • num_of_dapm_widgets:of_dapm_widgets指向的数组的长度;
  • of_dapm_routes:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_route ,保存解析设备节点simple-audio-card,routing得到的音频路由信息;
  • num_of_dapm_routes:of_dapm_routes指向的数组的长度;
  • component_dev_list:链表;
  • widgets:链表;
  • paths:链表;
  • dapm_list:链表;
  • dapm_dirty:链表;
  • drvdata:驱动程序的私有数据结构;
1.1.2 struct snd_soc_dai_link

ASoC使用struct snd_soc_dai_link数据结构来描述音频数据链路以及板级操作函数,在snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字,Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的。

struct snd_soc_dai_link定义在include/sound/soc.h:

struct snd_soc_dai_link {
        /* config - must be set by machine driver */
        const char *name;                       /* Codec name */
        const char *stream_name;                /* Stream name */

        /*
         * You MAY specify the link's CPU-side device, either by device name,
         * or by DT/OF node, but not both. If this information is omitted,
         * the CPU-side DAI is matched using .cpu_dai_name only, which hence
         * must be globally unique. These fields are currently typically used
         * only for codec to codec links, or systems using device tree.
         */
        /*
         * You MAY specify the DAI name of the CPU DAI. If this information is
         * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
         * only, which only works well when that device exposes a single DAI.
         */
        struct snd_soc_dai_link_component *cpus;
        unsigned int num_cpus;

        /*
         * You MUST specify the link's codec, either by device name, or by
         * DT/OF node, but not both.
         */
        /* You MUST specify the DAI name within the codec */
        struct snd_soc_dai_link_component *codecs;
        unsigned int num_codecs;

        /*
         * You MAY specify the link's platform/PCM/DMA driver, either by
         * device name, or by DT/OF node, but not both. Some forms of link
         * do not need a platform. In such case, platforms are not mandatory.
         */
        struct snd_soc_dai_link_component *platforms;
        unsigned int num_platforms;

        int id; /* optional ID for machine driver link identification */

        const struct snd_soc_pcm_stream *params;
        unsigned int num_params;

        unsigned int dai_fmt;           /* format to set on init */

        enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */

        /* codec/machine specific init - e.g. add machine controls */
        int (*init)(struct snd_soc_pcm_runtime *rtd);

        /* codec/machine specific exit - dual of init() */
        void (*exit)(struct snd_soc_pcm_runtime *rtd);

        /* optional hw_params re-writing for BE and FE sync */
        int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
                        struct snd_pcm_hw_params *params);

        /* machine stream operations */
        const struct snd_soc_ops *ops;
        const struct snd_soc_compr_ops *compr_ops;

        /* Mark this pcm with non atomic ops */
        unsigned int nonatomic:1;

        /* For unidirectional dai links */
        unsigned int playback_only:1;
        unsigned int capture_only:1;

        /* Keep DAI active over suspend */
        unsigned int ignore_suspend:1;

        /* Symmetry requirements */
        unsigned int symmetric_rate:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_sample_bits:1;

        /* Do not create a PCM for this DAI link (Backend link) */
        unsigned int no_pcm:1;

        /* This DAI link can route to other DAI links at runtime (Frontend)*/
        unsigned int dynamic:1;

        /* DPCM capture and Playback support */
        unsigned int dpcm_capture:1;
        unsigned int dpcm_playback:1;

        /* DPCM used FE & BE merged format */
        unsigned int dpcm_merged_format:1;
        /* DPCM used FE & BE merged channel */
        unsigned int dpcm_merged_chan:1;
        /* DPCM used FE & BE merged rate */
        unsigned int dpcm_merged_rate:1;

        /* pmdown_time is ignored at stop */
        unsigned int ignore_pmdown_time:1;

        /* Do not create a PCM for this DAI link (Backend link) */
        unsigned int ignore:1;

        /* This flag will reorder stop sequence. By enabling this flag
         * DMA controller stop sequence will be invoked first followed by
         * CPU DAI driver stop sequence
         */
        unsigned int stop_dma_first:1;

#ifdef CONFIG_SND_SOC_TOPOLOGY
        struct snd_soc_dobj dobj; /* For topology */
#endif
};

这个数据结构的内容比较多,我们只挑一些重点说一下:

  • name:指定Codec名称,必须配置;
  • stream_name:指定Stream名称,必须配置;
  • cpus:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(包括cpu端设备名称/设备树节点(二选一)以及cpu测的数字音频接口的名称),即保存当前音频数据链路上的所有cpu设备
  • num_cpus:cpus指向的数组长度;
  • codecs:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(包括codec端设备名称/设备树节点(二选一)以及codec测的数字音频接口名称),即保存当前音频数据链路上的所有codec设备;
  • num_codecs:codec指向的数组长度;
  • platforms:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(通过设备名称/设备树节点字段指定cpu测的platform驱动名称,通常都是DMA驱动,用于音频数据传输);
  • num_platforms:platforms指向的数组长度;
  • id:可选的链接 ID,用于识别Machine driver link;
  • params:指定PCM流参数;
  • num_params:PCM流参数数量;
  • dai_fmt:数字音频接口格式;
  • trigger:DPCM(Direct Pulse Code Modulation)触发类型;
  • init:初始化函数,例如添加Machine controls;
  • exit:退出函数;
  • be_hw_params_fixup:可选的硬件参数重写函数;
  • ops:音频相关的操作集;
  • compr_ops:数据压缩操作函数;
  • nonatomic:标记 PCM 是否使用非原子操作;
  • playback_only:标记 PCM 流是否只支持播放;
  • capture_only:标记 PCM 流是否只支持捕获;
  • ignore_suspend:标记 PCM 是否在挂起时保持 DAI 活动状态;
  • symmetric_rate:标记 PCM 采样率是否对称;
  • symmetric_channels:标记 PCM 通道数是否对称;
  • symmetric_sample_bits:标记 PCM 采样位数是否对称;
  • no_pcm:标记 PCM 流是否需要创建;
  • dynamic:标记该 DAI 链接是否可以在运行时路由到其他 DAI 链接;
  • dpcm_capture:标记是否支持 DPCM 捕获;
  • dpcm_playback:标记是否支持 DPCM 播放;
  • dpcm_merged_format:标记是否使用合并格式的 DPCM;
  • dpcm_merged_chan:标记是否使用合并通道的 DPCM;
  • dpcm_merged_rate:标记是否使用合并采样率的 DPCM;
  • ignore_pmdown_time:标记是否忽略 pmdown_time 停止时间;pmdown_time 是一种 PCM 的停止时间戳,用于控制 PCM 流在空闲一段时间后自动停止以降低功耗;
  • ignore:标记该 DAI 链接是否需要创建 PCM;
  • stop_dma_first:标记是否对停止序列进行排序;
1.1.3 snd_soc_dai_link_component 

ASoC使用struct snd_soc_dai_link_component来描述音频数据链路中的设备,定义在include/sound/soc.h;

struct snd_soc_dai_link_component {
        const char *name;
        struct device_node *of_node;
        const char *dai_name;
};

其中:

  • name:链路设备的名称;
  • of_node:链路设备所使用的设备树节点;
  • dai_name:链路设备使用的dai名称;
1.1.4 struct snd_soc_ops

ASoC使用struct snd_soc_ops来描述ALSA PCM操作集,定义在include/sound/soc.h;

/* SoC audio ops */
struct snd_soc_ops {
        int (*startup)(struct snd_pcm_substream *);
        void (*shutdown)(struct snd_pcm_substream *);
        int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
        int (*hw_free)(struct snd_pcm_substream *);
        int (*prepare)(struct snd_pcm_substream *);
        int (*trigger)(struct snd_pcm_substream *, int);
}

其中:

  • startup:在 PCM子流上启动音频传输期间调用;
  • shutdown:在PCM子流上关闭音频传输期间调用;
  • hw_params:更改PCM子流硬件参数期间调用;
  • hw_free:释放PCM子流资源期间调用;
  • prepare:准备PCM子流传输期间调用;
  • trigger:在PCM子流上触发执行操作期间调用;

1.2 Codec

Codec driver它不应包含任何特定于目标平台或设备的代码。所有特定于平台和设备的代码应分别添加到平台和机器驱动程序中,Codec deiver提供了配置编解码器、FM、MODEM、BT或外部DSP,以提供音频捕获和播放功能

每个Codec driver都必须有一个struct snd_soc_dai_driver 结构体,用于定义其 DAI 和 PCM 的能力和操作。该结构体被导出,以便Machine driver可以将其注册到ASoC CORE中。

描述Codec的最主要的几个数据结构分别是:snd_soc_dai,snd_soc_dai_driver,其中:

  • snd_soc_dai:描述codec端的dai;
  • snd_soc_dai_driver:描述codec端的dai驱动;

1.3 Platform

Platform driver主要是平台相关的DMA操作以及音频管理。大概流程:

  • DMA driver :先通过DMA驱动将dma buffer中的音频数据搬运到 I2S tx FIFO;
  • cpu dai driver:然后再通过cpu_da驱动将音频数据从I2S tx FIFO搬运Codesc中,数据会在Codec侧进行解码的操作,最终输出到耳机/音箱中。

而上述的两大类功能在ASoC中主要使用4个数据结构表示:snd_soc_dai、snd_soc_dai_driver、snd_soc_component、snd_soc_component_driver;

  • snd_soc_dai:描述cpu端的dai;
  • snd_soc_dai_driver:描述cpu端的dai驱动;其中包括dai的配置(音频格式、clock、音量等);
  • snd_soc_component:和Machine一样,使用snd_soc_component结构对ASoC中的platform设备进行统一抽象;
  • snd_soc_component_driver:代表Platform使用的dma驱动。主要是数据的传输等;
1.3.1 struct snd_soc_component

ASoC使用snd_soc_component来描述platform设备,定义在include/sound/soc-component.h:

struct snd_soc_component {
        const char *name;
        int id;
        const char *name_prefix;
        struct device *dev;
        struct snd_soc_card *card;

        unsigned int active;

        unsigned int suspended:1; /* is in suspend PM state */

        struct list_head list;
        struct list_head card_aux_list; /* for auxiliary bound components */
        struct list_head card_list;

        const struct snd_soc_component_driver *driver;

        struct list_head dai_list;
        int num_dai;

        struct regmap *regmap;
        int val_bytes;

        struct mutex io_mutex;

        /* attached dynamic objects */
        struct list_head dobj_list;

        /*
         * DO NOT use any of the fields below in drivers, they are temporary and
         * are going to be removed again soon. If you use them in driver code
         * the driver will be marked as BROKEN when these fields are removed.
         */

        /* Don't use these, use snd_soc_component_get_dapm() */
        struct snd_soc_dapm_context dapm;

        /* machine specific init */
        int (*init)(struct snd_soc_component *component);

        /* function mark */
        void *mark_module;
        struct snd_pcm_substream *mark_open;
        struct snd_pcm_substream *mark_hw_params;
        struct snd_pcm_substream *mark_trigger;
        struct snd_compr_stream  *mark_compr_open;
        void *mark_pm;

        struct dentry *debugfs_root;
        const char *debugfs_prefix;
};
1.3.2 struct snd_soc_component_driver

ASoC使用snd_soc_component_driver来描述platform dma driver,定义在include/sound/soc-component.h:

struct snd_soc_component_driver {
        const char *name;

        /* Default control and setup, added after probe() is run */
        const struct snd_kcontrol_new *controls;
        unsigned int num_controls;
        const struct snd_soc_dapm_widget *dapm_widgets;
        unsigned int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        unsigned int num_dapm_routes;

        int (*probe)(struct snd_soc_component *component);
        void (*remove)(struct snd_soc_component *component);
        int (*suspend)(struct snd_soc_component *component);
        int (*resume)(struct snd_soc_component *component);

        unsigned int (*read)(struct snd_soc_component *component,
                             unsigned int reg);
        int (*write)(struct snd_soc_component *component,
                     unsigned int reg, unsigned int val);

        /* pcm creation and destruction */
        int (*pcm_construct)(struct snd_soc_component *component,
                             struct snd_soc_pcm_runtime *rtd);
        void (*pcm_destruct)(struct snd_soc_component *component,
                             struct snd_pcm *pcm);
        /* component wide operations */
        int (*set_sysclk)(struct snd_soc_component *component,
                          int clk_id, int source, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_component *component, int pll_id,
                       int source, unsigned int freq_in, unsigned int freq_out);
        int (*set_jack)(struct snd_soc_component *component,
                        struct snd_soc_jack *jack,  void *data);
        int (*get_jack_type)(struct snd_soc_component *component);

        /* DT */
        int (*of_xlate_dai_name)(struct snd_soc_component *component,
                                 const struct of_phandle_args *args,
                                 const char **dai_name);
        int (*of_xlate_dai_id)(struct snd_soc_component *comment,
                               struct device_node *endpoint);
        void (*seq_notifier)(struct snd_soc_component *component,
                             enum snd_soc_dapm_type type, int subseq);
        int (*stream_event)(struct snd_soc_component *component, int event);
        int (*set_bias_level)(struct snd_soc_component *component,
                              enum snd_soc_bias_level level);

        int (*open)(struct snd_soc_component *component,
                    struct snd_pcm_substream *substream);
        int (*close)(struct snd_soc_component *component,
                     struct snd_pcm_substream *substream);
        int (*ioctl)(struct snd_soc_component *component,
                     struct snd_pcm_substream *substream,
                     unsigned int cmd, void *arg);
        int (*hw_params)(struct snd_soc_component *component,
                         struct snd_pcm_substream *substream,
                         struct snd_pcm_hw_params *params);
        int (*hw_free)(struct snd_soc_component *component,
                       struct snd_pcm_substream *substream);
        int (*prepare)(struct snd_soc_component *component,
                       struct snd_pcm_substream *substream);
        int (*trigger)(struct snd_soc_component *component,
                       struct snd_pcm_substream *substream, int cmd);
        int (*sync_stop)(struct snd_soc_component *component,
                         struct snd_pcm_substream *substream);
        snd_pcm_uframes_t (*pointer)(struct snd_soc_component *component,
                                     struct snd_pcm_substream *substream);
        int (*get_time_info)(struct snd_soc_component *component,
                struct snd_pcm_substream *substream, struct timespec64 *system_ts,
                struct timespec64 *audio_ts,
                struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
                struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
        int (*copy_user)(struct snd_soc_component *component,
                         struct snd_pcm_substream *substream, int channel,
                         unsigned long pos, void __user *buf,
                         unsigned long bytes);
        struct page *(*page)(struct snd_soc_component *component,
                             struct snd_pcm_substream *substream,
                             unsigned long offset);
        int (*mmap)(struct snd_soc_component *component,
                    struct snd_pcm_substream *substream,
                    struct vm_area_struct *vma);
        int (*ack)(struct snd_soc_component *component,
                   struct snd_pcm_substream *substream);
        snd_pcm_sframes_t (*delay)(struct snd_soc_component *component,
                                   struct snd_pcm_substream *substream);

        const struct snd_compress_ops *compress_ops;

        /* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;

        /*
         * signal if the module handling the component should not be removed
         * if a pcm is open. Setting this would prevent the module
         * refcount being incremented in probe() but allow it be incremented
         * when a pcm is opened and decremented when it is closed.
         */
        unsigned int module_get_upon_open:1;

        /* bits */
        unsigned int idle_bias_on:1;
        unsigned int suspend_bias_off:1;
        unsigned int use_pmdown_time:1; /* care pmdown_time at stop */
        /*
         * Indicates that the component does not care about the endianness of
         * PCM audio data and the core will ensure that both LE and BE variants
         * of each used format are present. Typically this is because the
         * component sits behind a bus that abstracts away the endian of the
         * original data, ie. one for which the transmission endian is defined
         * (I2S/SLIMbus/SoundWire), or the concept of endian doesn't exist (PDM,
         * analogue).
         */
        unsigned int endianness:1;
        unsigned int legacy_dai_naming:1;

        /* this component uses topology and ignore machine driver FEs */
        const char *ignore_machine;
        const char *topology_name_prefix;
        int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
                                  struct snd_pcm_hw_params *params);
        bool use_dai_pcm_id;    /* use DAI link PCM ID as PCM device number */
        int be_pcm_base;        /* base device ID for all BE PCMs */

        unsigned int start_dma_last;

#ifdef CONFIG_DEBUG_FS
        const char *debugfs_prefix;
#endif
};

1.4 DAI

DAI全称数字音频接口,根据codec端和soc端,分为codec_dai、cpu_dai,描述dai的最主要的几个数据结构分别是:snd_soc_dai、snd_soc_dai_driver、snd_soc_dai_ops;

以cpu dai driver为例,每个cpu dai driver必须提供以下功能:

  • DAI描述信息;
  • DAI配置信息;
  • PCM描述信息;
  • 系统时钟(SYSCLK)配置;
  • 挂起和恢复(可选);

以codec dai driver为例,每个codec dai driver必须提供以下功能:

  • Codec DAI和PCM的配置信息;
  • Codec的控制接口,如I2C/SPI;
  • Mixer和其它音频控件;
  • Codec的音频操作;
1.4.1 struct snd_soc_dai

ALSA中使用struct snd_soc_dai数据结构来描述dai运行时数据,定义在include/sound/soc-dai.h:

/*
 * Digital Audio Interface runtime data.
 *
 * Holds runtime data for a DAI.
 */
struct snd_soc_dai {
        const char *name;
        int id;
        struct device *dev;

        /* driver ops */
        struct snd_soc_dai_driver *driver;

        /* DAI runtime info */
        struct snd_soc_dai_stream stream[SNDRV_PCM_STREAM_LAST + 1];

        /* Symmetry data - only valid if symmetry is being enforced */
        unsigned int rate;
        unsigned int channels;
        unsigned int sample_bits;

        /* parent platform/codec */
        struct snd_soc_component *component;

        struct list_head list;

        /* function mark */
        struct snd_pcm_substream *mark_startup;
        struct snd_pcm_substream *mark_hw_params;
        struct snd_pcm_substream *mark_trigger;
        struct snd_compr_stream  *mark_compr_startup;

        /* bit field */
        unsigned int probed:1;
};
 

该数据结构包含以下字段:

  • name:DAI的名称;
  • id:DAI的标识符;
  • dev:指向包含DAI的设备的指针;
  • driver:指向dai驱动结构的指针;
  • stream:采集和播放流的数组,其中包含有关流的信息;
  • rate:如果强制对称,则为采样率;
  • channels:如果强制对称,则为通道数;
  • sample_bits:如果强制对称,则为采样位数;
  • component:指向父组件(通常是 platform或codec)的指针;
  • list:用于将DAI添加到其父组件的DAI列表中;
  • mark_startup:用于标记PCM启动事件的指针;
  • mark_hw_params:用于标记PCM硬件参数变化事件的指针;
  • mark_trigger:用于标记PCM触发事件的指针;
  • mark_compr_startup:用于标记压缩流启动事件的指针;
  • probed:标记DAI是否已经探测完成;
1.4.2 struct snd_soc_dai_driver

ALSA中使用struct snd_soc_dai_driver数据结构来描述DAI驱动,包括DAI和PCM的能力和操作,定义在include/sound/soc-dai.h:

/*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 *
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
        /* DAI description */
        const char *name;
        unsigned int id;
        unsigned int base;
        struct snd_soc_dobj dobj;

        /* DAI driver callbacks */
        int (*probe)(struct snd_soc_dai *dai);
        int (*remove)(struct snd_soc_dai *dai);
        /* compress dai */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* Optional Callback used at pcm creation*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);

        /* ops */
        const struct snd_soc_dai_ops *ops;
        const struct snd_soc_cdai_ops *cops;

        /* DAI capabilities */
        struct snd_soc_pcm_stream capture;
        struct snd_soc_pcm_stream playback;
        unsigned int symmetric_rate:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_sample_bits:1;

        /* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;
};

该数据结构包含了以下字段:

  • name:指定DAI的名称;
  • id:可选的 DAI 标识符,用于在注册期间区分多个DAI;
  • base:可选的 DAI 寄存器基地址;
  • dobj:DAI 对象,包含 DAI 及其父组件的句柄;
  • probe:可选的DAI探测回调函数;注册声卡时回调;
  • remove:可选的DAI卸载回调函数;
  • compress_new:可选的压缩 DAI 创建回调函数;
  • pcm_new:可选的 PCM 创建回调函数;
  • ops:指向本DAI的snd_soc_dai_ops结构,即DAI的操作集,这个操作集非常重要,用于DAI的时钟配置、格式配置、硬件参数配置;
  • cops:DAI 压缩操作函数指针表;
  • capture:描述capture的能力;如回放设备所支持的声道数、采样率、音频格式;非常重要的字段;
  • playbackk:描述playback的能力;如录制设备所支持声道数、采样率、音频格式;非常重要的字段;
  • symmetric_rate:标记 DAI 采样率是否对称;
  • symmetric_channels:标记 DAI 通道数是否对称;
  • symmetric_sample_bits:标记 DAI 采样位数是否对称;
  • probe_order:DAI 探测顺序;
  • remove_order:DAI 卸载顺序;
1.4.3 struct snd_soc_dai_ops
ASoC使用struct snd_soc_dai_ops数据结构描述DAI的控制和参数配置,这些函数包括对 DAI 时钟配置、对 DAI 格式配置、对 TDM(时分复用)通道配置以及对 ALSA PCM音频操作的配置等。定义在include/sound/soc-dai.h:
struct snd_soc_dai_ops {
        /*
         * DAI clocking configuration, all optional.
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_sysclk)(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);
        int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
        int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

        /*
         * DAI format configuration
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
        int (*xlate_tdm_slot_mask)(unsigned int slots,
                unsigned int *tx_mask, unsigned int *rx_mask);
        int (*set_tdm_slot)(struct snd_soc_dai *dai,
                unsigned int tx_mask, unsigned int rx_mask,
                int slots, int slot_width);
        int (*set_channel_map)(struct snd_soc_dai *dai,
                unsigned int tx_num, unsigned int *tx_slot,
                unsigned int rx_num, unsigned int *rx_slot);
        int (*get_channel_map)(struct snd_soc_dai *dai,
                        unsigned int *tx_num, unsigned int *tx_slot,
                        unsigned int *rx_num, unsigned int *rx_slot);
        int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

        int (*set_stream)(struct snd_soc_dai *dai,
                          void *stream, int direction);
        void *(*get_stream)(struct snd_soc_dai *dai, int direction);

        /*
         * DAI digital mute - optional.
         * Called by soc-core to minimise any pops.
         */
        int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

        /*
         * ALSA PCM audio operations - all optional.
         * Called by soc-core during audio PCM operations.
         */
        int (*startup)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        void (*shutdown)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*hw_params)(struct snd_pcm_substream *,
                struct snd_pcm_hw_params *, struct snd_soc_dai *);
        int (*hw_free)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*prepare)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        /*
         * NOTE: Commands passed to the trigger function are not necessarily
         * compatible with the current state of the dai. For example this
         * sequence of commands is possible: START STOP STOP.
         * So do not unconditionally use refcounting functions in the trigger
         * function, e.g. clk_enable/disable.
         */
        int (*trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        int (*bespoke_trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        /*
         * For hardware based FIFO caused delay reporting.
         * Optional.
         */
        snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
                struct snd_soc_dai *);

        /*
         * Format list for auto selection.
         * Format will be increased if priority format was
         * not selected.
         * see
         *      snd_soc_dai_get_fmt()
         */
        u64 *auto_selectable_formats;
        int num_auto_selectable_formats;

        /* bit field */
        unsigned int no_capture_mute:1;
};

具体函数成员包括:

  • set_sysclk: 配置DAI的时钟源;
  • set_pll: 配置DAI的PLL(锁相环);
  • set_clkdiv: 配置DAI的时钟分频;
  • set_bclk_ratio: 配置DAI的BCLK(Bit Clock)比率;
  • set_fmt: 配置DAI的数据格式;
  • xlate_tdm_slot_mask: 将TDM槽位映射为掩码;
  • set_tdm_slot: 配置DAI的TDM槽位;
  • set_channel_map: 配置DAI的通道映射;
  • get_channel_map: 获取DAI的通道映射;
  • set_tristate: 配置DAI的三态(tri-state)设置;
  • set_stream / get_stream: 设置/获取DAI的数据流;
  • mute_stream: 静音DAI的数据流;
  • startup / shutdown: 在PCM音频操作期间启动/关闭DAI;
  • hw_params: 配置PCM音频的硬件参数;
  • hw_free: 释放PCM音频的硬件资源;
  • prepare: 准备PCM音频操作;
  • trigger: 触发PCM音频操作;
  • bespoke_trigger: 自定义触发PCM音频操作;
  • delay: 返回基于硬件FIFO导致的延迟;
  • auto_selectable_formats: 自动选择的格式列表;
  • num_auto_selectable_formats: 自动选择的格式数量;
  • no_capture_mute: 捕获静音标志位;

这些函数指针在dai driver中实现,并通过structsnd_soc_dai_driver的ops成员导出,供上层音频驱动程序使用。

二、PCM介绍

我们在Rockchip RK3399 - ALC5651 & I2S基础中实际上已经介绍过PCM,它是一种音频编码格式,更确切的说是一种将声音从模拟信号转换成数字信号的技术。

音频驱动的两大核心任务就是:

  • playback :如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频;
  • capture :把mic拾取到得模拟信号,经过采样、量化,转换为PCM数据送回给用户空间的应用程序;

 参考文章:Linux ALSA声卡驱动之三:PCM设备的创建

三、kcontrol介绍

一个kcontrol代表着一个Mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。

Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。对于Mixer来说,Control接口显得尤为重要。

参考文章:Linux ALSA声卡驱动之四:Control设备的创建

二、ASoC核心API

2.1 注册声卡设备

snd_soc_register_card函数用于向ASoC CORE注册一个声卡设备,函数定义在sound/soc/soc-core.c:

/**
 * snd_soc_register_card - Register a card with the ASoC core
 *
 * @card: Card to register
 *
 */
int snd_soc_register_card(struct snd_soc_card *card)
{
        if (!card->name || !card->dev)
                return -EINVAL;

        dev_set_drvdata(card->dev, card);

        INIT_LIST_HEAD(&card->widgets);
        INIT_LIST_HEAD(&card->paths);
        INIT_LIST_HEAD(&card->dapm_list);
        INIT_LIST_HEAD(&card->aux_comp_list);
        INIT_LIST_HEAD(&card->component_dev_list);
        INIT_LIST_HEAD(&card->list);
        INIT_LIST_HEAD(&card->rtd_list);
        INIT_LIST_HEAD(&card->dapm_dirty);
        INIT_LIST_HEAD(&card->dobj_list);

        card->instantiated = 0;
        mutex_init(&card->mutex);
        mutex_init(&card->dapm_mutex);
        mutex_init(&card->pcm_mutex);

        return snd_soc_bind_card(card);
}

该函数接受一个 struct snd_soc_card 结构体作为参数,函数指向流程如下:

  • 首先检查传入的 card 结构体是否具有合法的 name 和 dev 成员,如果缺少其中任何一个成员,则返回 -EINVAL 错误代码;
  • 接着,函数使用dev_set_drvdata 函数设置card->dev 的driver_data为card。这是方便在后续的操作中可以通过设备句柄来获取到对应的card 结构体;
  • 然后,函数使用 INIT_LIST_HEAD 宏初始化card结构体中的各个链表成员,包括 widgets、paths、dapm_list、aux_comp_list、component_dev_list、list、rtd_list、dapm_dirty 和 dobj_list;
  • 接下来,函数将 card->instantiated 初始化为 0,表示该音频卡尚未实例化。然后,初始化card中的互斥锁:mutex、dapm_mutex 和 pcm_mutex;
  • 最后,函数调用 snd_soc_bind_card 函数来绑定声卡,并返回其结果;

snd_soc_bind_card函数定义在sound/soc/soc-core.c:

static int snd_soc_bind_card(struct snd_soc_card *card)
{
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_component *component;
        struct snd_soc_dai_link *dai_link;
        int ret, i;

        mutex_lock(&client_mutex);       // 获取互斥锁
        mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);  // 获取互斥锁

        snd_soc_dapm_init(&card->dapm, card, NULL);

        /* check whether any platform is ignore machine FE and using topology */
        soc_check_tplg_fes(card);

        /* bind aux_devs too */
        ret = soc_bind_aux_dev(card);
        if (ret < 0)
                goto probe_end;

        /* add predefined DAI links to the list */
        card->num_rtd = 0;
        for_each_card_prelinks(card, i, dai_link) {
                ret = snd_soc_add_pcm_runtime(card, dai_link);
                if (ret < 0)
                        goto probe_end;
        }

        soc_init_card_debugfs(card);

        soc_resume_init(card);

        ret = snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
                                        card->num_dapm_widgets);
        if (ret < 0)
                goto probe_end;

        ret = snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
                                        card->num_of_dapm_widgets);
        if (ret < 0)
                goto probe_end;

        /* initialise the sound card only once */
        ret = snd_soc_card_probe(card);
        if (ret < 0)
                goto probe_end;

        /* probe all components used by DAI links on this card */
        ret = soc_probe_link_components(card);
        if (ret < 0) {
                dev_err(card->dev,
                        "ASoC: failed to instantiate card %d\n", ret);
                goto probe_end;
        }

        /* probe auxiliary components */
        ret = soc_probe_aux_devices(card);
        if (ret < 0) {
                dev_err(card->dev,
                        "ASoC: failed to probe aux component %d\n", ret);
                goto probe_end;
        }

        /* probe all DAI links on this card */
        ret = soc_probe_link_dais(card);
        if (ret < 0) {
                dev_err(card->dev,
                        "ASoC: failed to instantiate card %d\n", ret);
                goto probe_end;
        }

        for_each_card_rtds(card, rtd) {
                ret = soc_init_pcm_runtime(card, rtd);
                if (ret < 0)
                        goto probe_end;
        }

        snd_soc_dapm_link_dai_widgets(card);
        snd_soc_dapm_connect_dai_link_widgets(card);

        ret = snd_soc_add_card_controls(card, card->controls,
                                        card->num_controls);
        if (ret < 0)
                goto probe_end;

        ret = snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                      card->num_dapm_routes);
        if (ret < 0) {
                if (card->disable_route_checks) {
                        dev_info(card->dev,
                                 "%s: disable_route_checks set, ignoring errors on add_routes\n",
                                 __func__);
                } else {
                        dev_err(card->dev,
                                 "%s: snd_soc_dapm_add_routes failed: %d\n",
                                 __func__, ret);
                        goto probe_end;
                }
        }

        ret = snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
                                      card->num_of_dapm_routes);
        if (ret < 0)
                goto probe_end;

        /* try to set some sane longname if DMI is available */
        snd_soc_set_dmi_name(card, NULL);

        soc_setup_card_name(card, card->snd_card->shortname,
                            card->name, NULL);
        soc_setup_card_name(card, card->snd_card->longname,
                            card->long_name, card->name);
        soc_setup_card_name(card, card->snd_card->driver,
                            card->driver_name, card->name);

        if (card->components) {
                /* the current implementation of snd_component_add() accepts */
                /* multiple components in the string separated by space, */
                /* but the string collision (identical string) check might */
                /* not work correctly */
                ret = snd_component_add(card->snd_card, card->components);
                if (ret < 0) {
                        dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n",
                                card->name, ret);
                        goto probe_end;
                }
        }

        ret = snd_soc_card_late_probe(card);
        if (ret < 0)
                goto probe_end;

        snd_soc_dapm_new_widgets(card);
        snd_soc_card_fixup_controls(card);

        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
                dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
                                ret);
                goto probe_end;
        }

        card->instantiated = 1;
        dapm_mark_endpoints_dirty(card);
        snd_soc_dapm_sync(&card->dapm);

        /* deactivate pins to sleep state */
        for_each_card_components(card, component)
                if (!snd_soc_component_active(component))
                        pinctrl_pm_select_sleep_state(component->dev);

probe_end:
        if (ret < 0)
                soc_cleanup_card_resources(card);

        mutex_unlock(&card->mutex);    // 释放互斥锁 
        mutex_unlock(&client_mutex);   // 释放互斥锁

        return ret;
}

该函数的主要功能包括初始化音频卡、绑定辅助设备、添加预定义的DAI等。以下是该函数的主要步骤:

 

  • 首先,通过调用 mutex_lock和 mutex_lock_nested函数,获取所需的互斥锁,以确保在进行初始化和绑定操作时不会出现冲突;
  • 初始化 DAPM:通过调用 snd_soc_dapm_init函数,对音频卡的 DAPM (Dynamic Audio Power Management) 进行初始化,DAPM 是一种用于管理音频设备功耗的机制;
  • 通过调用 soc_check_tplg_fes 函数,检查是否有任何平台忽略机器前端并使用拓扑(topology)配置;
  • 通过调用 soc_bind_aux_dev 函数,绑定音频卡的辅助设备;
  • 通过调用 snd_soc_add_pcm_runtime 函数,将预定义的 DAI 连接添加到列表中;
  • 通过调用 soc_init_card_debugfs 函数,初始化调试文件系统,为音频卡提供调试信息;
  • 通过调用 soc_resume_init 函数,初始化恢复机制;
  • 通过调用 snd_soc_dapm_new_controls 函数,创建和注册音频卡的DAPM控件;
  • 通过调用 snd_soc_card_probe 函数,探测音频卡并初始化其相关组件;
  • 通过调用 soc_probe_link_components 函数,探测音频卡上的连线组件;
  • 通过调用 soc_probe_aux_devices 函数,探测音频卡的辅助设备;
  • 通过调用 soc_probe_link_dais 函数,探测音频卡上的 DAI 连接;
  • 通过调用 soc_init_pcm_runtime  函数,初始化 PCM 运行时;
  • 通过调用 snd_soc_dapm_link_dai_widgets 函数,链接 DAI 控件;
  • 通过调用 snd_soc_dapm_connect_dai_link_widgets 函数,链接 DAI 连接的控件;
  • 通过调用 snd_soc_add_card_controls 函数,添加音频卡的控件;
  • 通过调用 snd_soc_dapm_add_routes 函数,添加音频卡的 DAPM 路由;
  • 通过调用 snd_soc_set_dmi_name 和 soc_setup_card_name 函数,设置音频卡的名称;
  • 通过调用 snd_component_add 函数,将组件添加到音频卡;
  • 通过调用 snd_soc_card_late_probe 函数,延迟探测音频卡;
  • 通过调用 snd_soc_dapm_new_widgets 函数,创建新的 DAPM 控件;
  • 通过调用 snd_soc_card_fixup_controls 函数,修复音频卡的控件;
  • 通过调用 snd_card_register 函数,注册声卡;
  • 通过调用 dapm_mark_endpoints_dirty 函数,标记终端点为脏状态;
  • 通过调用 snd_soc_dapm_sync 函数,同步 DAPM 状态;
  • 通过调用 pinctrl_pm_select_sleep_state 函数,将非活动的组件设置为睡眠状态;
  • 如果有错误发生,通过调用 soc_cleanup_card_resources 函数清理资源。最后,通过调用 mutex_unlock 函数解锁互斥锁;

 

参考文章

[1] Linux ALSA 音频系统:物理链路篇

[2] Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)

[3] Linux ALSA声卡驱动之六:ASoC架构中的Machine

[4] Linux ALSA声卡驱动之七:ASoC架构中的Codec

[5] Linux ALSA声卡驱动之八:ASoC架构中的Platform

[6] Linux音频驱动-ASOC之Machine

[7] Linux音频驱动-AOSC之Platform

[8] Linux音频驱动-AOSC之Codec

[9] ALSA SoC Layer