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-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
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-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了