程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - ASoC Codec驱动基础

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux   :6.3
----------------------------------------------------------------------------------------------------------------------------

Rockchip RK3399 - ALSA子系统我们介绍了ALSA子系统的软件架构,同时介绍了ALSA CORE核心数据结构和相关API。本节我们将会介绍ASoC软件体系中音频三大驱动模块(codec、platform 和machine)中的codec。

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

每个codec driver必须提供以下功能:

  • codec dai和pcm的配置信息;通过struct snd_soc_dai_driver描述,包括dai的能力描述和操作接口;
  • codec的控制接口:其控制接口一般是I2C或SPI。控制接口用于读写codec的寄存器。在struct snd_soc_component_driver结构体中,有大量字段描述codec的控制接口,比如read、write等;
  • Mixer和其它音频控件;
  • codec的音频操作:通过结构体struct snd_soc_dai_ops描述;
  • DAPM描述信息;
  • DAPM事件处理程序;

可选地,codec driver还可以提供:

  • DAC数字静音控制;

一、核心数据结构

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

  • snd_soc_dai:描述codec端的dai;
  • snd_soc_dai_driver:与snd_soc_dai对应的驱动数据结构就是snd_soc_dai_driver;
  • snd_soc_component:ASoC使用同一的数据结构来描述codec设备和platform设备,该数据结构就是snd_soc_component;也就是说,不管是platform、codec,核心层现在都统一用struct snd_soc_component来管理了, 一个component对应一个模块;
  • snd_soc_component_driver:与snd_soc_component对应的驱动数据结构就是snd_soc_component_driver;

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

关于snd_soc_dai,snd_soc_dai_driver数据结构具体参考:Rockchip RK3399 - ASoC Machine驱动基础 dai相关内容。

1.1 struct snd_soc_component

ASoC使用snd_soc_component来描述codec设备,定义在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;
};

其中:

  • name:名称,音频数据链路可以通过这个字段到全局链表component_list中查找与之匹配的component;
  • dapm:dapm域;
  • dev:一般为codec设备对应的struct device;
  • regmap:regmap实例,regmap一种通用的接口来操作底层硬件寄存器;
  • name_prefix:名称前缀,如果使用设备树的话,取自dev设备节点的属性"sound-name-prefix";
  • card:ASoC声卡设备;
  • dai_list:保存dai的链表;链表中的数据元素类型为struct  snd_soc_dai;
  • num_dai:dai_list链表的长度;
  • driver:component driver;
  • list:用于构建链表节点,将当前节点添加到全局链表component_list;
  • card_list:用于构建链表节点,将当前节点添加到声卡的component_dev_list链表;

1.2 struct snd_soc_component_driver

ASoC使用snd_soc_component_driver来描述codec 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
};

其中:

  • controls:指向动态分配得到的数组,每个元素都是一个struct snd_kcontrol;可以用来保存component的kcontrol;
  • num_controls:controls指向的数组的长度;
  • dapm_widgets:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_widget ;
  • num_dapm_widgets:dapm_widgets指向的数组的长度;
  • dapm_routes:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_route;
  • num_dapm_routes:dapm_routes指向的数组的长度;
  • probe:可选的conmponent探测回调函数;注册声卡时回调;
  • remove:可选的conmponent卸载回调函数;
  • probe_order:probe函数执行顺序;
  • remove_order:remove函数执行顺序;
  • read:用于读取寄存器的值,如果使用了regamp进行寄存器读写就不用配置该函数了;
  • write:用于向寄存器写入值,如果使用了regamp进行寄存器读写就不用配置该函数了;

1.3 数据结构关系图

为了更加形象的表示snd_soc_dai,snd_soc_dai_driver、snd_soc_component、snd_soc_component_driver之间的关系,我们绘制了如下关系框图:

二、注册component

devm_snd_soc_register_component是带有资源管理的component注册函数,该函数会动态申请一个component,并将其添加到全局链表component_list中,同时会为每个dai driver动态分配一个dai,并建立dai与component、dai与dai driver的关系。

在Machine驱动中匹配codec,实际上就是根据音频数据链路snd_soc_dai_link codec指定的name去遍历component_list找到匹配的component, 然后再根据dai_name从component->dai_list中获取到匹配的codec dai。

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

/**
 * devm_snd_soc_register_component - resource managed component registration
 * @dev: Device used to manage component
 * @cmpnt_drv: Component driver
 * @dai_drv: DAI driver
 * @num_dai: Number of DAIs to register
 *
 * Register a component with automatic unregistration when the device is
 * unregistered.
 */
int devm_snd_soc_register_component(struct device *dev,
                         const struct snd_soc_component_driver *cmpnt_drv,
                         struct snd_soc_dai_driver *dai_drv, int num_dai)
{
        const struct snd_soc_component_driver **ptr;
        int ret;
        // 动态分配内存,指向struct *snd_soc_component_driver,在设备释放时自动调用devm_component_release函数 
        ptr = devres_alloc(devm_component_release, sizeof(*ptr), GFP_KERNEL);
        if (!ptr)
                return -ENOMEM;

        ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
        if (ret == 0) {
                *ptr = cmpnt_drv;
                devres_add(dev, ptr);
        } else {
                devres_free(ptr);
        }

        return ret;
}

此函数主要通过devres_alloc函数完成了devm_component_release的注册,并申请了资源,然后通过snd_soc_register_component完成了snd_soc_component_driver和snd_soc_dai_driver的注册。

snd_soc_register_component定义在sound/soc/soc-core.c,此处来看一下snd_soc_register_component函数,此函数中主要通过devm_kzalloc函数来完成了struct snd_soc_component数据结构的申请,然后通过snd_soc_component_initialize函数来完成component的初始化,最后通过snd_soc_add_component函数将component添加到全局链表component_list中,具体代码实现如下:

int snd_soc_register_component(struct device *dev,
                        const struct snd_soc_component_driver *component_driver,
                        struct snd_soc_dai_driver *dai_drv,
                        int num_dai)
{
        struct snd_soc_component *component;
        int ret;

        component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL);  // 动态分配struct snd_soc_component
        if (!component)
                return -ENOMEM;

        ret = snd_soc_component_initialize(component, component_driver, dev); // 初始化成员name、dev、driver
        if (ret < 0)
                return ret;

        return snd_soc_add_component(component, dai_drv, num_dai); // 完成snd_soc_dai_driver的注册,并将component来添加到全局链表component_list中
}

2.1 snd_soc_component_initialize

再来看一下snd_soc_component_initialize函数,此函数通过INIT_LIST_HEAD这宏完成了四个list的初始化,有component->dai_list、component->dobj_list、component->card_list和component->list,然后通过fmt_single_name函数来完成component->id的component组件的匹配工作,函数定义在sound/soc/soc-core.c:

int snd_soc_component_initialize(struct snd_soc_component *component,
                                 const struct snd_soc_component_driver *driver,
                                 struct device *dev)
{
        INIT_LIST_HEAD(&component->dai_list);       // 初始化链表节点
        INIT_LIST_HEAD(&component->dobj_list);
        INIT_LIST_HEAD(&component->card_list);
        INIT_LIST_HEAD(&component->list);
        mutex_init(&component->io_mutex);  // 互斥锁初始化

        component->name = fmt_single_name(dev, &component->id);  // 设置为dev设备的名称,如果设备名称为%s.%d格式,将解析最后的数字存放到component->id中
        if (!component->name) {
                dev_err(dev, "ASoC: Failed to allocate name\n");
                return -ENOMEM;
        }

        component->dev          = dev;      // 设置dev 
        component->driver       = driver;   // 设置driver

#ifdef CONFIG_DEBUG_FS
        if (!component->debugfs_prefix)
                component->debugfs_prefix = driver->debugfs_prefix;
#endif

        return 0;
}

这里面fmt_single_name函数就是确定codec的名字,这个名字很重要,在没有使用设备树的情景下,在Machine驱动中音频数据链路可以通过这个字段到全局链表component_list中查找与之匹配的component;

/*
 * Simplify DAI link configuration by removing ".-1" from device names
 * and sanitizing names.
 */
static char *fmt_single_name(struct device *dev, int *id)  // 以rt5651设备为例  
{
        const char *devname = dev_name(dev);  // 获取设备名称:1-001a
        char *found, *name;
        unsigned int id1, id2;

        if (devname == NULL)
                return NULL;

        name = devm_kstrdup(dev, devname, GFP_KERNEL);  // dump
        if (!name)
                return NULL;

        /* are we a "%s.%d" name (platform and SPI components) */
        found = strstr(name, dev->driver->name);  // 查找dev->driver->name("rt5651")在name第一次出现的地址
        if (found) {  // 未找到
                /* get ID */
                if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) {  // 解析出id

                        /* discard ID from name if ID == -1 */
                        if (*id == -1)
                                found[strlen(dev->driver->name)] = '\0';
                }

        /* I2C component devices are named "bus-addr" */
        } else if (sscanf(name, "%x-%x", &id1, &id2) == 2) { // id1="1" id2="001a"

                /* create unique ID number from I2C addr and bus */
                *id = ((id1 & 0xffff) << 16) + id2;

                devm_kfree(dev, name);

                /* sanitize component name for DAI link creation */
                name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", dev->driver->name, devname);  // name设置为rt5651.1-001a
        } else {
                *id = 0;
        }

        return name;
}

比如我们在设备树中配置的设备节点rt5651,该设备节点会被转换为platform_device,platform_device->dev的名称为1-001a;经过fmt_single_name函数处理返回"rt5651.1-001a";

&i2c1 {
    status = "okay";
    i2c-scl-rising-time-ns = <300>;
    i2c-scl-falling-time-ns = <15>;

    rt5651: rt5651@1a {
        #sound-dai-cells = <0>;
        compatible = "rockchip,rt5651";
        reg = <0x1a>;
        clocks = <&cru SCLK_I2S_8CH_OUT>;
        clock-names = "mclk";
        status = "okay";
    };
};

2.2 snd_soc_add_component

snd_soc_add_component函数主要流程如下:

  • 创建num_dai个dai,并为每个dai设置dai driver;
  • 然后将dai加入到component的dai_list链表中;
  • 初始化component成员regmap,用于对I2C/SPI设备底层硬件寄存器进行读写;
  • 最后再将component添加到全局链表component_list中;

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

int snd_soc_add_component(struct snd_soc_component *component,
                          struct snd_soc_dai_driver *dai_drv,
                          int num_dai)
{
        int ret;
        int i;

        mutex_lock(&client_mutex);

        if (component->driver->endianness) {
                for (i = 0; i < num_dai; i++) {
                        convert_endianness_formats(&dai_drv[i].playback);
                        convert_endianness_formats(&dai_drv[i].capture);
                }
        }

        ret = snd_soc_register_dais(component, dai_drv, num_dai);  // 注册dai driver列表(内部会创建num_dai个dai,每个dai对应一个dai driver)
        if (ret < 0) {
                dev_err(component->dev, "ASoC: Failed to register DAIs: %d\n",
                        ret);
                goto err_cleanup;
        }

        if (!component->driver->write && !component->driver->read) {  // 如果没有定义寄存器读、写函数
                if (!component->regmap)  // 为空
                        component->regmap = dev_get_regmap(component->dev,   // 获取设备的regmap,并赋值给component成员regmap
                                                           NULL);
                if (component->regmap)  // 走这里
                        snd_soc_component_setup_regmap(component);  // 从regmap中获取寄存器值位宽,赋值给component->val_bytes
        }

        /* see for_each_component */
        list_add(&component->list, &component_list); // 将当前component添加到全局链表component_list

err_cleanup:
        if (ret < 0)
                snd_soc_del_component_unlocked(component);

        mutex_unlock(&client_mutex);

        if (ret == 0)
                snd_soc_try_rebind_card();

        return ret;
}
2.2.1 snd_soc_register_dais

snd_soc_register_dais函数定义在sound/soc/soc-core.c,内部循环调用snd_soc_register_dai依次注册每个dai driver;

/**
 * snd_soc_register_dais - Register a DAI with the ASoC core
 *
 * @component: The component the DAIs are registered for
 * @dai_drv: DAI driver to use for the DAIs
 * @count: Number of DAIs
 */
static int snd_soc_register_dais(struct snd_soc_component *component,
                                 struct snd_soc_dai_driver *dai_drv, // dai driver
                                 size_t count) // dai driver数量
{
        struct snd_soc_dai *dai;
        unsigned int i;
        int ret;

        for (i = 0; i < count; i++) {
                dai = snd_soc_register_dai(component, dai_drv + i, count == 1 &&  // 注册每个dai driver
                                           component->driver->legacy_dai_naming);
                if (dai == NULL) {
                        ret = -ENOMEM;
                        goto err;
                }
        }

        return 0;

err:
        snd_soc_unregister_dais(component);

        return ret;
}
2.2.2 snd_soc_register_dai

snd_soc_register_dai函数如下:

/**
 * snd_soc_register_dai - Register a DAI dynamically & create its widgets
 *
 * @component: The component the DAIs are registered for
 * @dai_drv: DAI driver to use for the DAI
 * @legacy_dai_naming: if %true, use legacy single-name format;
 *      if %false, use multiple-name format;
 *
 * Topology can use this API to register DAIs when probing a component.
 * These DAIs's widgets will be freed in the card cleanup and the DAIs
 * will be freed in the component cleanup.
 */
struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
                                         struct snd_soc_dai_driver *dai_drv,
                                         bool legacy_dai_naming)
{
        struct device *dev = component->dev;
        struct snd_soc_dai *dai;

        dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev));

        lockdep_assert_held(&client_mutex);

        dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL);  // 动态分配分配struct snd_soc_dai
        if (dai == NULL)
                return NULL;

        /*
         * Back in the old days when we still had component-less DAIs,
         * instead of having a static name, component-less DAIs would
         * inherit the name of the parent device so it is possible to
         * register multiple instances of the DAI. We still need to keep
         * the same naming style even though those DAIs are not
         * component-less anymore.
         */
        if (legacy_dai_naming &&          // 设置dai名称
            (dai_drv->id == 0 || dai_drv->name == NULL)) {
                dai->name = fmt_single_name(dev, &dai->id);  // 设置为dev设备的名称
        } else {
                dai->name = fmt_multiple_name(dev, dai_drv);  // 设置为dai_drv->name,即dai driver的名称
                if (dai_drv->id)
                        dai->id = dai_drv->id;
                else
                        dai->id = component->num_dai;
        }
        if (!dai->name)
                return NULL;

        dai->component = component;   // 初始化dai成员
        dai->dev = dev;
        dai->driver = dai_drv;

        /* see for_each_component_dais */
        list_add_tail(&dai->list, &component->dai_list);  // 将当前dai添加到component的dai_list链表中
        component->num_dai++;  // 计数

        dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name);
        return dai;
}

具体流程如下:

  • 首先分配struct snd_soc_dai;
  • 设置dai->name参数;在machine驱动注册过程中,将会用snd_soc_dai_link中codecs的dai_name和这里的name做匹配;
  • 初始化dai成员component、dev、driver;
  • 将dai加入到component的dai_list链表中;

参考文章

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

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

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

[4] ASoC Codec Class Driver

[5] ALSA驱动源码之devm_snd_soc_register_component源码分析

[6] 声卡驱动02-自己实现alsa驱动-虚拟声卡-匹配

posted @ 2023-07-19 23:26  大奥特曼打小怪兽  阅读(435)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步