程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

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

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1009)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示