Rockchip RK3399 - ASoC驱动

发布时间 2023-06-15 23:56:25作者: 大奥特曼打小怪兽

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

一、ASoC核心数据结构

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

1.1 Machine

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:名称;
  • long_name:更详细的名称;
  • driver_name:驱动程序的名称;
  • components:组件名称;
  • dev:分配给此声卡的设备;一般设置为平台设备的device;
  • snd_card:ALSA CORE中的声卡设备;
  • owner:指向驱动程序拥有者模块的指针;
  • pcm_subclass:PCM子类的枚举类型;
  • probe:该数据结构注册到内核时调用;
  • late_probe:
  • remove:
  • add_dai_link:
  • remove_dai_link:
  • dai_link:指向struct snd_soc_dai_link数组,每一元素描述了CPU <--> Codec DAI links
  • num_links:dai_link指向的数组的长度;
  • codec_conf:
  • aux_dev:
  • num_controls:
  • num_dapm_widgets:
  • num_dapm_routes:
  • num_of_dapm_widgets:
  • num_of_dapm_routes:
  • component_dev_list:
  • widgets:DAPM控件的链表;
  • paths:DAPM路径的链表;
  • dapm_list:
  • dapm_dirty:
  • drvdata:驱动程序的私有数据结构;
1.1.1 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:指定CPU端的数字音频接口(DAI);
  • num_cpus:CPU端DAI 数量;
  • codecs:指定Codec端DAI;
  • num_codecs:Codec端 DAI数量;
  • platforms:指定platform驱动程序;
  • num_platforms:platform驱动程序数量;
  • 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.2 struct snd_soc_ops

ASoC使用struct snd_soc_ops来描述音频操作集,定义在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.1.3 struct snd_soc_dai

ALSA中使用structsnd_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.1.4  struct snd_soc_dai_driver

ALSA中使用structsnd_soc_dai_driver数据结构来描述DAI驱动,定义在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结构;
  • cops:DAI 压缩操作函数指针表;
  • capture:描述capture的能力;
  • playbook:描述playback的能力;
  • symmetric_rate:标记 DAI 采样率是否对称;
  • symmetric_channels:标记 DAI 通道数是否对称;
  • symmetric_sample_bits:标记 DAI 采样位数是否对称;
  • probe_order:DAI 探测顺序;
  • remove_order:DAI 卸载顺序;
1.1.5 struct snd_soc_dai_ops
ASoC使用struct snd_soc_dai_ops数据结构描述DAI的控制和参数配置,定义在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;
};

1.2 Codec

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

  • snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到;我们在Machine中已经介绍过;
  • Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。
1.2.1 struct snd_soc_codec
1.2.1 struct snd_soc_codec_driver

音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息、时钟配置、IO 控制等.

1.3 Platform

1.3.1 struct snd_soc_platform
1.3.2 struct snd_platform_driver

音频 dma 设备描述及操作函数

二、ASoC核心API

2.1 注册声卡设备

snd_soc_register_card

参考文章

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

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