我们在开发openharmony的时候,hcs的修改后编译内核,经常发现hcs不生效,本文基于解析hcs的编译逻辑确定问题,从而得出正确编译hcs的方法。从某种意义上这是其实是openharmony的本身bug。
参考链接如下:
OpenHarmony-HDF驱动框架介绍及加载过程
https://forums.openharmony.cn/forum.php?mod=viewthread&tid=766
OHOS HDF 图谱-1-驱动配置信息树状图
从上述文章我们可以知道
hcs模仿了dts的基本功能,对openharmony的设备驱动进行了抽象,简单的hcs配置如下:
display :: host { hostName = "display_host"; device_hdf_drm_panel :: device { device0 :: deviceNode { policy = 0; priority = 197; preload = 0; moduleName = "HDF_DRM_PANEL_SIMPLE"; } } }
这里描述了display模块,其对应的某个驱动为HDF_DRM_PANEL_SIMPLE
我们知道,在标准linux内核中,设备树很好的融入了总线设备驱动框架,对于设备和驱动,我们只需要在驱动写好我们的注册结构体即可,示例如下:
static struct mipi_dsi_driver panel_simple_dsi_driver = { .driver = { .name = "panel-simple-dsi", .of_match_table = dsi_of_match, }, .probe = panel_simple_dsi_probe, .remove = panel_simple_dsi_remove, .shutdown = panel_simple_dsi_shutdown, };
hcs模仿了linux的做法,所以我们可以对应的做如下的结构体的注册,示例如下:
struct HdfDriverEntry g_hdfDrmPanelSimpleEntry = { .moduleVersion = 1, .moduleName = "HDF_DRM_PANEL_SIMPLE", .Init = HdfDrmPanelSimpleEntryInit, };
这里注意的是,dts里面设备和驱动匹配成功了调用的是probe回调,而openharmony的hcs注册成功了调用的是Init回调
对于dts,我们可以知道通过dtc命令可以汇编和反汇编dts和dtb,这样对我们调试dts来说十分方便,同样的openharmony也借用了这样的概念。
openharmony通过hc-gen命令可以对hcs进行汇编和反汇编,示例如下
编译hcs:
$sdk/drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -b hdf.hcs -o hdf.hcb
这里将hdf.hcs编译成了hdf.hcb
反编译hdf_hcs.hcb:
$sdk/rk3568/drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -d hdf_hcs.hcb -o hdf.hcs 这里将hdf_hcs.hcb反汇编成了hdf.hcs。这里实际会输出hdf.d.hcs
我们知道设备树是通过标准的libfdt库来解析dtb的,类似的,我们可以知道hcb是通过华为开发的hcs parser库来解析,hcs parser库位置如下:
drivers/hdf_core/framework/utils/src/hcs_parser/
解析流程图如下
为了分析hcs的解析过程,我对kernel的make过程进行了分析,如下:
内核为了能够正常引入hcs,在hdf框架的Makefile有如下代码
HCS_DIR := ../../../../../$(PRODUCT_PATH)/hdf_config/khdf ifeq ($(wildcard $(CURRENT_DIR)/$(HCS_DIR)),) HCS_DIR := ../../../../../$(PRODUCT_PATH)/hdf_config endif ifeq ($(CONFIG_DRIVERS_HDF), y) ifeq ($(wildcard $(CURRENT_DIR)/$(HCS_DIR)),) HCS_ABS_DIR := $(abspath $(CURRENT_DIR)/$(HCS_DIR)) $(error miss hcs config in $(HCS_ABS_DIR) for small system\ or $(HCS_ABS_DIR)/khdf for standrad system) endif ifeq ($(CONFIG_DRIVERS_HDF_TEST), y) obj-$(CONFIG_DRIVERS_HDF_TEST) += test/ obj-$(CONFIG_DRIVERS_HDF_TEST) += $(HCS_DIR)/hdf_test/ $(warning HCS_DIR=$(HCS_DIR)) else obj-$(CONFIG_DRIVERS_HDF) += $(HCS_DIR)/ $(warning HCS_DIR=$(HCS_DIR)) endif endif
如上我们可以知道,内核编译hcs取决于两个配置:CONFIG_DRIVERS_HDF和CONFIG_DRIVERS_HDF_TEST
于是我们查看defconfig如下
arch/arm64/configs/rockchip_linux_defconfig
可以知道上面两个配置均打开
所以得出结论是:我们hcs的编译目录为:
obj-$(CONFIG_DRIVERS_HDF_TEST) += $(HCS_DIR)/hdf_test/
这个目录的路径如下:
vendor/hihope/rk3568/hdf_config/khdf/hdf_test/
根据hdf的Makefile我们可以知道真正起作用的hcs路径,现在开始分析hcs的Makefile核心代码如下:
$(obj)/$(HCS_OBJ): $(CONFIG_GEN_HEX_SRC) $(Q)$(CC) $(c_flags) -c -o $@ $< $(Q)rm -f $< $(CONFIG_GEN_HEX_SRC): $(LOCAL_HCS_ROOT)/%_hcs_hex.c: $(HCS_DIR)/%.hcs | $(HC_GEN) $(Q)echo gen hdf built-in config $(Q)if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi $(Q)$(HC_GEN) $(HCB_FLAGS) -o $(subst _hex.c,,$(@)) $< $(CONFIG_GEN_SRCS): $(CONFIG_OUT_DIR)%.c: $(HCS_DIR)/%.hcs | $(HC_GEN) $(Q)echo gen hdf driver config $(Q)if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi $(Q)$(HC_GEN) -t -o $@ $< $(HC_GEN): $(HIDE)make -C $(HC_GEN_DIR) BUILD_DIR=$(dir $@) $(obj)/$(HCS_MACRO_OBJ): $(HCS_MACRO_SRC) $(HCS_DEP) $(Q)$(CC) $(c_flags) -c -o $@ $< $(HCS_DEP): $(HC_GEN) $(Q)echo gen hdf built-in config macro $(Q)$(HC_GEN) -m -o $(HCS_MACRO_GEN_FILE) $(HCS_FILE) obj-$(CONFIG_DRIVERS_HDF) += $(HCS_OBJ) \ $(HCS_MACRO_OBJ)
我们可以知道,如果CONFIG_DRIVERS_HDF置位,则会编译HCS_OBJ和HCS_MACRO_OBJ
这里的核心代码如下:
$(Q)$(HC_GEN) $(HCB_FLAGS) -o $(subst _hex.c,,$(@)) $< $(Q)$(CC) $(c_flags) -c -o $@ $<
我把它做一下变量扩展,易于理解如下:
drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -b -i -a -o vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf.hcs cc -c -o /hdf_hcs_hex.o /root/tf/repo/rk3568/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.c
这里我们就可以发现,实际上内核在编译的时候,通过linux的hdf驱动的Makefile内迁到hcs的Makefile上编译hdf.hcs为hdf_hcs_hex.c,然后将hdf_hcs_hex.c编译成hdf_hcs_hex.o,然后合并到Image内使用。
根据上面的分析,我们完全可以清楚的知道,内核是将其作为o插入的Image内,这时候我们直接找built-in.a即可找到实际的文件路径,如下
../../OBJ/linux-5.10/drivers/hdf/built-in.a
这时候我们找到文件应该如下:
# cat ../../OBJ/linux-5.10/drivers/hdf/built-in.a | grep hdf_hcs_hex.o ../../../../vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o/
此时我们将相对路径转换成绝对路径,所以如下:
out/kernel/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o
此时,我们完全正确的找到了hcs的编译成果
上面介绍了hcs,也说明了hcs的最终编译成果,这里简单说明一下hcs在openharmony是如何修改的
我们知道openharmony使用的hcs应该如下:
vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf.hcs
内容如下:
#include "../hdf.hcs" #include "hdf_config_test.hcs" #include "hdf_test_manager/device_info.hcs" #include "adc_test_config.hcs" #include "gpio_test_config.hcs" #include "i2c_test_config.hcs" #include "pwm_test_config.hcs" #include "spi_test_config.hcs" #include "sdio_test_config.hcs" #include "emmc_test_config.hcs" #include "uart_test_config.hcs" #include "rtc_test_config.hcs" #include "watchdog_test_config.hcs" root { module = "hisilicon,hi35xx_chip"; }
上面可以知道, 其还是包含了上一层的hdf.hcs("../hdf.hcs")
这时候我们找上层的hdf.hcs如下:
#include "device_info/device_info.hcs" #include "platform/adc_config_linux.hcs" #include "platform/pwm_config.hcs" #include "platform/rk3568_watchdog_config.hcs" #include "platform/rk3568_uart_config.hcs" #include "platform/sdio_config.hcs" #include "platform/emmc_config.hcs" #include "platform/rk3568_spi_config.hcs" #include "input/input_config.hcs" #include "wifi/wlan_platform.hcs" #include "wifi/wlan_chip_ap6275s.hcs" #include "camera/camera_config.hcs" #include "sensor/sensor_config.hcs" #include "audio/audio_config.hcs" #include "audio/codec_config.hcs" #include "audio/dai_config.hcs" #include "audio/dma_config.hcs" #include "audio/dsp_config.hcs" #include "audio/analog_headset_config.hcs" #include "light/light_config.hcs" #include "vibrator/vibrator_config.hcs" #include "vibrator/linear_vibrator_config.hcs" #include "vibrator/drv2605l_linear_vibrator_config.hcs" #include "lcd/lcd_config.hcs" root { module = "rockchip,rk3568_chip"; }
此时我们知道设备的信息可以在:#include "device_info/device_info.hcs"
于是我们对设备驱动的修改可以在#include "device_info/device_info.hcs"中,修改即可生效
回到我们的问题,正常情况下,我们通过单独编译或者全量编译的命令是没办法编译到hcs文件的,这是openharmony的bug。
正常的单独编译命令如下:
KBUILD_OUTPUT=../../OBJ/linux-5.10 PRODUCT_PATH=vendor/hihope/rk3568 DEVICE_COMPANY=rockchip DEVICE_NAME=rk3568 PRODUCT_COMPANY=hihope GPUDRIVER=mali ./make-ohos.sh TB-RK3568X0 enable_ramdisk
此时我们无法编译我们修改的hcs文件,我们需要将中间文件单独删除如下:
rm ../../vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o
这样我们就可以在编译内核的时候编译更新hcs的修改了。
有同事根据上述方法修改了hcs还是无法生效,这里给出排除方法,帮助定位:
cd vendor/hihope/rk3568/hdf_config/khdf make
此时会在本地生成hcb文件,我们通常情况下编写的hcs文件带有字串标识符,所以此时使用grep可以判断是否已经包含,如下
grep -nr "HDF_DRMPANEL" device_info/device_info.hcs:244: moduleName = "HDF_DRMPANEL"; 匹配到二进制文件 hdf_test/hdf_hcs.hcb 匹配到二进制文件 hdf_hcs.hcb
如果进一步还想确认,可以使用上文的hc-gen命令,反编译后查看自己写的节点是否存在
$sdk/drivers/hdf_core/framework/tools/hc-gen/build/hc-gen -d hdf_hcs.hcb -o test.hcs
上面5.1.1确定没问题了,基本就不会出问题, 但是为了给个说法,这里也可以通过grep确定boot_linux.img是否包含,如下
grep -nr "HDF_DRMPANEL" $sdk/out/rk3568/packages/phone/images/boot_linux.img grep: 警告: GREP_OPTIONS 已被废弃;请使用别名或脚本 匹配到二进制文件 /$sdk/out/rk3568/packages/phone/images/boot_linux.img
如果不想确认,建议在编译内核的过程中打印日志,如果有日志,代表编译是进去的,如果没有,没编译进去。方便懒人。如下:
还是hcs的编译目录:
vendor/hihope/rk3568/hdf_config/khdf/hdf_test
打开Makefile,添加如下
diff --git a/rk3568/hdf_config/khdf/hdf_test/Makefile b/rk3568/hdf_config/khdf/hdf_test/Makefile index 7b53c08..6781b0a 100755 --- a/rk3568/hdf_config/khdf/hdf_test/Makefile +++ b/rk3568/hdf_config/khdf/hdf_test/Makefile @@ -68,11 +68,12 @@ HCS_MACRO_GEN_FILE := $(HDF_FRAMWORK_TEST_OUT)/hdf_macro_test HCS_FILE := $(HCS_DIR)/hdf.hcs $(obj)/$(HCS_OBJ): $(CONFIG_GEN_HEX_SRC) + $(Q)echo 你看编没编进去: [$(CC) $(c_flags) -c -o $@ $<] make o $(Q)$(CC) $(c_flags) -c -o $@ $< $(Q)rm -f $<
此时你编译内核的时候就会打印。打印了就编译进去了,没打印就编译没进去,那你检查hcs是不是写错了。
在调试Openharmony的时候,发现开机logo的图层并没有消失,故分析drmModeSet的api,发现了一个关于atomic modestting相关的宏可以workaround解决此问题,但是后面找到根本原因了,故此修改无效,仅作知识了解储备,此文章总结一下这两个宏的区别和联系
关于drmModeAtomicCommit的函数,其来源于libdrm库,参考kwin的实现,可以查到具体描述如下:
参考如下
https://github.com/KDE/kwin/blob/master/src/backends/drm/overview.md?plain=1
可以知道,drmModeAtomicCommit会提交atomic property change给内核drm框架
对于DRM_MODE_ATOMIC_NONBLOCK,代表这次提交的值不会block
对于DRM_MODE_ATOMIC_ALLOW_MODESET,
其会提交所有modeset的改变,与DRM_MODE_ATOMIC_NONBLOCK不同的是,他会阻塞直到内核drm完成modeset的property完成改变
drmModeAtomicCommit最终会调用到内核的ioctl判断,流程如下:
drmModeAtomicCommit----> DRM_IOCTL(fd, DRM_IOCTL_MODE_ATOMIC, &atomic);----> drm_ioctl---->DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATOMIC, drm_mode_atomic_ioctl, DRM_MASTER),--→drm_mode_atomic_ioctl
此时可以观察如下代码:
if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY) { ret = drm_atomic_check_only(state); } else if (arg->flags & DRM_MODE_ATOMIC_NONBLOCK) { ret = drm_atomic_nonblocking_commit(state); } else { if (drm_debug_enabled(DRM_UT_STATE)) drm_atomic_print_state(state); ret = drm_atomic_commit(state); }
这里可以发现,如果设置DRM_MODE_ATOMIC_NONBLOCK和不设置DRM_MODE_ATOMIC_NONBLOCK,代码调用函数不一样,如下:
int drm_atomic_nonblocking_commit(struct drm_atomic_state *state) { struct drm_mode_config *config = &state->dev->mode_config; int ret; ret = drm_atomic_check_only(state); if (ret) return ret; DRM_DEBUG_ATOMIC("committing %p nonblocking\n", state); return config->funcs->atomic_commit(state->dev, state, true); } int drm_atomic_commit(struct drm_atomic_state *state) { struct drm_mode_config *config = &state->dev->mode_config; int ret; ret = drm_atomic_check_only(state); if (ret) return ret; DRM_DEBUG_ATOMIC("committing %p\n", state); return config->funcs->atomic_commit(state->dev, state, false); }
对于回调函数atomic_commit,rockchip平台如下设置
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { .fb_create = rockchip_fb_create, .output_poll_changed = rockchip_drm_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, };
所以我们重点留意drm_atomic_helper_commit内的判断如下:
if (!nonblock) { ret = drm_atomic_helper_wait_for_fences(dev, state, true); if (ret) goto err; } if (nonblock) queue_work(system_unbound_wq, &state->commit_work); else commit_tail(state);
这里可以发现,如果是不是nonblock commit,则先会drm_atomic_helper_wait_for_fences,这里会调用dma_fence_wait,用于睡眠等待fence(sleep until the fence gets signaled),如果fence信号触发,则直接提交到尾 commit_tail(state) 直接渲染
而如果是nonblock commit,则直接将这个事件通过queue_work发送到wq上,这个wq的函数如下:
static void commit_work(struct work_struct *work) { struct drm_atomic_state *state = container_of(work, struct drm_atomic_state, commit_work); commit_tail(state); }
这里可以看到如果wq工作时,才调用commit_tail提交到尾
在Openharmony的代码中可以知道,只有在更新DrmMode的时候才执行阻塞提交DRM_MODE_ATOMIC_ALLOW_MODESET,而对于每一帧Composition的时候,只是DRM_MODE_ATOMIC_NONBLOCK非阻塞提交。鉴于此,我们可以在每一帧Composition执DRM_MODE_ATOMIC_ALLOW_MODESET,但根据上述代码分析,可能带来图形的性能损失,故不可行。
根据如上,了解到了drm在atomic commit的时候,可以选择block和nonblock,如果我都设置block,则开机logo的问题确实解决了,鉴于分析代码的时候发现,此方法可能带来性能损失,故仍继续寻找根因。
出现开机后开机logo的图层没消失的主要原因是应该是openharmony图层设置存在问题,故应该从Plane查起。
整个openharmony的ADM基本已经解析完成,如果我们了解过ALSA的话,我们知道整个ALSA中,DAPM关于控件设计的精髓,ADM为了不抛弃这么一个精髓,于是设计了SAPM,虽然我在编写es8388的时候不太敢用,但是这里还是有必要通过对比的方式解析一下ADM对的SAPM
在之前的文章提到过DAPM:内核alsa框架解析
这里简单的,通俗的理解一下DAPM。
DAPM是动态电源管理,我们知道codec有一个概念是音频路由,也就意味着如果我要声音能播放,我需要将codec内部的组件打开,这里可以在Openharmony audio(七) es8388的register说明找到
如果我们直接将寄存器打开关闭,那么就没必要设计一个所谓的DAPM的概念了。
那么DAPM的作用是什么呢。
我再说简单点,就是设计了一个能够让普通人能理解的概念,告诉大家,打开音频需要把某一路开关打开,其他开关就关闭,就跟开灯的开关一样。
这个思想好吗,我认为非常好,即使一个不懂得电子电路的人,他只需要知道打开开关就打开一条通路,然后声音就播放了。
模仿dapm的dapm_up_seq/dapm_down_seq,sapm实现了如下:
/* power up sequences */ static int32_t g_audioSapmPowerUpSeq[] = { [AUDIO_SAPM_PRE] = 0, /* 0 is audio sapm power up sequences */ [AUDIO_SAPM_SUPPLY] = 1, /* 1 is audio sapm power up sequences */ [AUDIO_SAPM_MICBIAS] = 2, /* 2 is audio sapm power up sequences */ [AUDIO_SAPM_AIF_IN] = 3, /* 3 is audio sapm power up sequences */ [AUDIO_SAPM_AIF_OUT] = 3, /* 3 is audio sapm power up sequences */ [AUDIO_SAPM_MIC] = 4, /* 4 is audio sapm power up sequences */ [AUDIO_SAPM_MUX] = 5, /* 5 is audio sapm power up sequences */ [AUDIO_SAPM_VIRT_MUX] = 5, /* 5 is audio sapm power up sequences */ [AUDIO_SAPM_VALUE_MUX] = 5, /* 5 is audio sapm power up sequences */ [AUDIO_SAPM_DAC] = 6, /* 6 is audio sapm power up sequences */ [AUDIO_SAPM_MIXER] = 7, /* 7 is audio sapm power up sequences */ [AUDIO_SAPM_MIXER_NAMED_CTRL] = 7, /* 7 is audio sapm power up sequences */ [AUDIO_SAPM_PGA] = 8, /* 8 is audio sapm power up sequences */ [AUDIO_SAPM_ADC] = 9, /* 9 is audio sapm power up sequences */ [AUDIO_SAPM_OUT_DRV] = 10, /* 10 is audio sapm power up sequences */ [AUDIO_SAPM_HP] = 10, /* 10 is audio sapm power up sequences */ [AUDIO_SAPM_SPK] = 10, /* 10 is audio sapm power up sequences */ [AUDIO_SAPM_POST] = 11, /* 11 is audio sapm power up sequences */ }; /* power down sequences */ static int32_t g_audioSapmPowerDownSeq[] = { [AUDIO_SAPM_PRE] = 0, /* 0 is audio sapm power down sequences */ [AUDIO_SAPM_ADC] = 1, /* 1 is audio sapm power down sequences */ [AUDIO_SAPM_HP] = 2, /* 2 is audio sapm power down sequences */ [AUDIO_SAPM_SPK] = 2, /* 2 is audio sapm power down sequences */ [AUDIO_SAPM_OUT_DRV] = 2, /* 2 is audio sapm power down sequences */ [AUDIO_SAPM_PGA] = 4, /* 4 is audio sapm power down sequences */ [AUDIO_SAPM_MIXER_NAMED_CTRL] = 5, /* 5 is audio sapm power down sequences */ [AUDIO_SAPM_MIXER] = 5, /* 5 is audio sapm power down sequences */ [AUDIO_SAPM_DAC] = 6, /* 6 is audio sapm power down sequences */ [AUDIO_SAPM_MIC] = 7, /* 7 is audio sapm power down sequences */ [AUDIO_SAPM_MICBIAS] = 8, /* 8 is audio sapm power down sequences */ [AUDIO_SAPM_MUX] = 9, /* 9 is audio sapm power down sequences */ [AUDIO_SAPM_VIRT_MUX] = 9, /* 9 is audio sapm power down sequences */ [AUDIO_SAPM_VALUE_MUX] = 9, /* 9 is audio sapm power down sequences */ [AUDIO_SAPM_AIF_IN] = 10, /* 10 is audio sapm power down sequences */ [AUDIO_SAPM_AIF_OUT] = 10, /* 10 is audio sapm power down sequences */ [AUDIO_SAPM_SUPPLY] = 11, /* 11 is audio sapm power down sequences */ [AUDIO_SAPM_POST] = 12, /* 12 is audio sapm power down sequences */ };
我们知道dapm会检查是否有一个链路通,如果通,则根据这条链路打开组件电源,这里dapm实现如下:
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) { int in, out; DAPM_UPDATE_STAT(w, power_checks); in = is_connected_input_ep(w, NULL, NULL); out = is_connected_output_ep(w, NULL, NULL); return out != 0 && in != 0; }
ADM也一样去实现了,实现如下
static int32_t AudioSapmGenericCheckPower(const struct AudioSapmComponent *sapmComponent) { int32_t input; int32_t output; if (sapmComponent == NULL) { ADM_LOG_ERR("input param cpt is NULL."); return HDF_FAILURE; } input = ConnectedInputEndPoint(sapmComponent); if (input == HDF_FAILURE) { ADM_LOG_ERR("input endpoint fail!"); return HDF_FAILURE; } output = ConnectedOutputEndPoint(sapmComponent); if (output == HDF_FAILURE) { ADM_LOG_ERR("output endpoint fail!"); return HDF_FAILURE; } if ((input == 0) || (output == 0)) { ADM_LOG_DEBUG("component %s is not in a complete path.", sapmComponent->componentName); return SAPM_POWER_DOWN; } return SAPM_POWER_UP; }
对于dapm,每个组件是widget,这个widget应该是codec注册,因为codec知道自己需要多少组件,所以有函数
snd_soc_dapm_new_widgets
对应驱动如下编写:
static const struct snd_soc_dapm_widget es8323_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINPUT1"), SND_SOC_DAPM_INPUT("LINPUT2"), SND_SOC_DAPM_INPUT("RINPUT1"), SND_SOC_DAPM_INPUT("RINPUT2"), SND_SOC_DAPM_MUX("Left PGA Mux", SND_SOC_NOPM, 0, 0, &es8323_left_dac_mux_controls), SND_SOC_DAPM_MUX("Right PGA Mux",SND_SOC_NOPM , 0, 0, &es8323_right_dac_mux_controls), SND_SOC_DAPM_MICBIAS("Mic Bias", ES8323_ADCPOWER, 3, 1), .... }
这样驱动就很灵活的定义widget,如下:
static struct snd_soc_component_driver soc_codec_dev_es8323 = { .dapm_widgets = es8323_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(es8323_dapm_widgets), };
这时候ADM不这么想了,他说我知道所有的组件,我定义了数组g_audioSapmCompNameList,如下
static char *g_audioSapmCompNameList[AUDIO_SAPM_COMP_NAME_LIST_MAX] = { "ADCL", "ADCR", "DACL", "DACR", // [0], [1] [2], [3] "LPGA", "RPGA", "SPKL", "SPKR", // [4], [5] [6], [7] "MIC", "LOUT", "HPL", "HPR", // [8], [9] [10], [11] "Stereo Mixer", "Line Mix", "Input Mixer", "Speaker Mix", // [12], [13] [14], [15] "Input Mux", "AuxOut Mux", "SPKL Mux", "SPKR Mux", // [16], [17] [18], [19] "AUXOUTL", "AUXOUTR", "LINEINL", "LINEINR", // [20], [21] [22], [23] "AUXINL", "AUXINR", "I2S Mix", "AuxI Mix", // [24], [25] [26], [27] "CaptureL Mix", "CaptureR Mix", "Mono1 Mixer", "Mono2 Mixer", // [28], [29] [30], [31] "DAC1", "DAC2", "DAC3", "DAC4", // [32], [33] [34], [35] "ADC1", "ADC2", "ADC3", "ADC4", // [36], [37] [38], [39] "MIC1", "MIC2", "MIC3", "MIC4", // [40], [41],[42], [43], "SPK1", "SPK2", "SPK3", "SPK4", // [44], [45],[46], [47], "DAC Mix", "DAC Mux", "ADC Mix", "ADC Mux", // [48], [49],[50], [51], "SPKL PGA", "SPKR PGA", "HPL PGA", "HPR PGA", // [52], [53],[54], [55], };
然后呢,你按照这个数组下标数字去注册,也就是在hcs中写数字
sapmComponent = [ 10, 0, 0xFFFF, 0x1, 7, 1, 0, 0, //ADCL 10, 1, 0xFFFF, 0x1, 6, 1, 0, 0, //ADCR 11, 32, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC1 11, 33, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC2 11, 34, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //DAC3 6, 52, 0xFFFF, 0xFFFF, 0, 0, 3, 1, //SPKL PGA 6, 54, 0xFFFF, 0xFFFF, 0, 0, 4, 1, //HPL PGA 6, 55, 0xFFFF, 0xFFFF, 0, 0, 5, 1, //HPR PGA 15, 6, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //SPK 14, 10, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //HPL 14, 11, 0xFFFF, 0xFFFF, 0, 0, 0, 0, //HPR 6, 4, 0xFFFF, 0xFFFF, 6, 0, 1, 1, //LPGA 6, 5, 0xFFFF, 0xFFFF, 6, 0, 2, 1, //RPGA 13, 40, 0xFFFF, 0xFFFF, 6, 0, 0, 0, //MIC1 13, 41, 0xFFFF, 0x1, 1, 0, 0, 0 //MIC2 ];
也就是说,ADM认为不管未来还是现在,Codec永远就只有这些组件了,如果再多,那就是你Codec的问题了。
此时我们需要主动调用AudioSapmNewComponents函数来注册上面的数字的组件,如下:
if (AudioSapmNewComponents(audioCard, device->devData->sapmComponents, device->devData->numSapmComponent) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("new components failed."); return HDF_FAILURE; }
所以此时dapm更灵活,它的widget是自己codec定义的,此时widgete可多可少,想怎么扩展怎么扩展。
我们知道alsa的框架下,路由也是通过驱动注册,如下:
.dapm_routes = audio_map, .num_dapm_routes = ARRAY_SIZE(audio_map),
这里的audio_map告诉你上面的组件的路由情况,和widget一致,需要在驱动中写明白你自己注册的组件,你需要怎么连接,具体可以参考内核alsa框架解析的第六章音频路由分析,如下:
static const struct snd_soc_dapm_route audio_map[] = { {"Left PGA Mux", "Line 1L", "LINPUT1"}, {"Left PGA Mux", "Line 2L", "LINPUT2"}, {"Left PGA Mux", "DifferentialL", "Differential Mux"}, {"Right PGA Mux", "Line 1R", "RINPUT1"}, {"Right PGA Mux", "Line 2R", "RINPUT2"}, {"Right PGA Mux", "DifferentialR", "Differential Mux"} }
这非常合乎常理,也很自然。
我们的ADM,它实现了函数AudioSapmAddRoutes,仿照了alsa的概念,实现了sink和source,如下:
struct AudioSapmRoute { const char *sink; const char *control; const char *source; /* Note: currently only supported for links where source is a supply */ uint32_t (*Connected)(struct AudioSapmComponent *source, struct AudioSapmComponent *sink); };
所以在codec的hdf的driver中,需要定义如下:
static const struct AudioSapmRoute g_audioRoutes[] = { { "SPKL", NULL, "SPKL PGA"}, { "HPL", NULL, "HPL PGA"}, { "HPR", NULL, "HPR PGA"}, { "SPKL PGA", "Speaker1 Switch", "DAC1"}, { "HPL PGA", "Headphone1 Switch", "DAC2"}, { "HPR PGA", "Headphone2 Switch", "DAC3"}, { "ADCL", NULL, "LPGA"}, { "ADCR", NULL, "RPGA"}, { "LPGA", "LPGA MIC Switch", "MIC1"}, { "RPGA", "RPGA MIC Switch", "MIC2"}, };
然后添加如下:
if (AudioSapmAddRoutes(audioCard, g_audioRoutes, HDF_ARRAY_SIZE(g_audioRoutes)) != HDF_SUCCESS) { AUDIO_DRIVER_LOG_ERR("add route failed."); return HDF_FAILURE; }
这里ADM使用了sapmcfg,也就是变量g_audioSapmCfgNameList,作为route的control,它对于hcs的如下:
/*array index, iface, mixer/mux, enable (g_audioSapmCfgNameList)*/ sapmConfig = [ 0, 2, 0, 1, 1, 2, 0, 0, 24, 2, 0, 1, 28, 2, 0, 0, 29, 2, 0, 1 ];
于是仿照了alsa的snd_soc_dapm_add_route实现了AudioSapmAddRoute如下:
static int32_t AudioSapmAddRoute(struct AudioCard *audioCard, const struct AudioSapmRoute *route) { struct AudioSapmpath *path = NULL; struct AudioSapmComponent *cptSource = NULL; struct AudioSapmComponent *cptSink = NULL; struct AudioSapmComponent *sapmComponent = NULL; int32_t ret; DLIST_FOR_EACH_ENTRY(sapmComponent, &audioCard->components, struct AudioSapmComponent, list) { if (sapmComponent->componentName == NULL) { continue; } if ((cptSource == NULL) && (strcmp(sapmComponent->componentName, route->source) == 0)) { cptSource = sapmComponent; continue; } if ((cptSink == NULL) && (strcmp(sapmComponent->componentName, route->sink) == 0)) { cptSink = sapmComponent; } if ((cptSource != NULL) && (cptSink != NULL)) { break; } } path = (struct AudioSapmpath *)OsalMemCalloc(sizeof(struct AudioSapmpath)); if (path == NULL) { ADM_LOG_ERR("malloc path fail!"); return HDF_FAILURE; } path->source = cptSource; path->sink = cptSink; DListHeadInit(&path->list); DListHeadInit(&path->listSink); DListHeadInit(&path->listSource); /* check for external components */ AudioSampExtComponentsCheck(cptSource, cptSink); ret = AudioSampStaticOrDynamicPath(audioCard, cptSource, cptSink, path, route); if (ret != HDF_SUCCESS) { OsalMemFree(path); ADM_LOG_ERR("static or dynamic path fail!"); return HDF_FAILURE; } return HDF_SUCCESS; }
同样的,仿照alsa的snd_soc_dapm_add_path,实现了AudioSampStaticOrDynamicPath,如下:
static int32_t AudioSampStaticOrDynamicPath(struct AudioCard *audioCard, struct AudioSapmComponent *source, struct AudioSapmComponent *sink, struct AudioSapmpath *path, const struct AudioSapmRoute *route) { int32_t ret; if (route->control == NULL) { DListInsertHead(&path->list, &audioCard->paths); DListInsertHead(&path->listSink, &sink->sources); DListInsertHead(&path->listSource, &source->sinks); path->connect = CONNECT_SINK_AND_SOURCE; return HDF_SUCCESS; } switch (sink->sapmType) { case AUDIO_SAPM_MUX: case AUDIO_SAPM_VIRT_MUX: case AUDIO_SAPM_VALUE_MUX: ret = AudioSapmConnectMux(audioCard, source, sink, path, route->control); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("connect mux fail!"); return HDF_FAILURE; } break; case AUDIO_SAPM_ANALOG_SWITCH: case AUDIO_SAPM_MIXER: case AUDIO_SAPM_MIXER_NAMED_CTRL: case AUDIO_SAPM_PGA: case AUDIO_SAPM_SPK: ret = AudioSapmConnectMixer(audioCard, source, sink, path, route->control); if (ret != HDF_SUCCESS) { ADM_LOG_ERR("connect mixer fail!"); return HDF_FAILURE; } break; case AUDIO_SAPM_HP: case AUDIO_SAPM_MIC: case AUDIO_SAPM_LINE: DListInsertHead(&path->list, &audioCard->paths); DListInsertHead(&path->listSink, &sink->sources); DListInsertHead(&path->listSource, &source->sinks); path->connect = CONNECT_SINK_AND_SOURCE; break; default: DListInsertHead(&path->list, &audioCard->paths); DListInsertHead(&path->listSink, &sink->sources); DListInsertHead(&path->listSource, &source->sinks); path->connect = CONNECT_SINK_AND_SOURCE; break; } return HDF_SUCCESS; }
至此,我们可以发现,SAPM和DAPM本质是一样的,相当于重复根据DAPM的设计思路造了一个SAPM的轮子。
根据上面的代码分析,我们可以发现sapm和dapm实现是类似的,但是根据我调试时发现,sapm定义是强制的,也就是
所以,只有完全了解sapm的玩法,并且按照ADM的规则,死死的配置,并按照数字的方式配置正确,你的驱动才能正常。
为了避免这个问题,我在ControlHostElemWrite中直接返回了,这样ADM就不走SAPM的control设置了。
虽然现在分析了sapm,它和DAPM完全一致,我应该也可以通过sapm的配置完成正确的声卡设置,但是我认为也没必要了。因为sapm目前还不是很成熟。
对于声卡的播放和录制,数据搬运到i2s上去发送是通过dma处理的,应用的数据通过stream dispatch下来,这时候,我们需要将其发送在dma的通道上,然后dma附在i2s的接收寄存器地址上,从而使得i2s直接可以发送音频数据,所以需要一个dma的hdf driver,这里分析这个驱动
对于dma的驱动,代码路径如下:
device/board/hihope/rk3568/audio_drivers/soc/src/
相应文件如下:
rk3568_dma_adapter.c rk3568_dma_ops.c
这里注册HDF driver,如下
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_platformDriverEntry = { .moduleVersion = 1, .moduleName = "DMA_RK3568", .Bind = PlatformDriverBind, .Init = PlatformDriverInit, .Release = PlatformDriverRelease, }; HDF_INIT(g_platformDriverEntry);
这里Bind提供服务,Init开始初始化,咱们关注Init,它提供ADM注册codec时的回调,并注册platform设备,如下:
static int32_t PlatformDriverInit(struct HdfDeviceObject *device) { int32_t ret; struct PlatformData *platformData = NULL; struct PlatformHost *platformHost = NULL; if (device == NULL) { AUDIO_DEVICE_LOG_ERR("device is NULL."); return HDF_ERR_INVALID_OBJECT; } platformHost = (struct PlatformHost *)device->service; if (platformHost == NULL) { AUDIO_DEVICE_LOG_ERR("platformHost is NULL"); return HDF_FAILURE; } platformData = (struct PlatformData *)OsalMemCalloc(sizeof(*platformData)); if (platformData == NULL) { AUDIO_DEVICE_LOG_ERR("malloc PlatformData fail!"); return HDF_FAILURE; } ret = PlatformGetServiceName(device, platformData); if (ret != HDF_SUCCESS) { OsalMemFree(platformData); return ret; } platformData->PlatformInit = AudioDmaDeviceInit; platformData->ops = &g_dmaDeviceOps; if (AudioDmaGetConfigInfo(device, platformData) != HDF_SUCCESS) { OsalMemFree(platformData); return HDF_FAILURE; } OsalMutexInit(&platformData->renderBufInfo.buffMutex); OsalMutexInit(&platformData->captureBufInfo.buffMutex); ret = AudioSocRegisterPlatform(device, platformData); if (ret != HDF_SUCCESS) { OsalMemFree(platformData); return ret; } platformHost->priv = platformData; AUDIO_DEVICE_LOG_DEBUG("success.\n"); return HDF_SUCCESS; }
这里需要提供ops回调,如下
struct AudioDmaOps g_dmaDeviceOps = { .DmaBufAlloc = Rk3568DmaBufAlloc, .DmaBufFree = Rk3568DmaBufFree, .DmaRequestChannel = Rk3568DmaRequestChannel, .DmaConfigChannel = Rk3568DmaConfigChannel, .DmaPrep = Rk3568DmaPrep, .DmaSubmit = Rk3568DmaSubmit, .DmaPending = Rk3568DmaPending, .DmaPause = Rk3568DmaPause, .DmaResume = Rk3568DmaResume, .DmaPointer = Rk3568PcmPointer, };
这个文件主要实现上述AudioDmaOps 需要填充的函数回调,以及实现ADM Platform driver的Init回调。
对于Init回调,主要解析hcs的配置中dma_config.hcs对dma的配置。主要如下:
idInfo { chipName = "/i2s@fe410000"; chipIdRegister = 0xfe410000; chipIdSize = 0x1000; }
这种情况下,我们通过chipIdRegister+offset就能找到i2s的搬运地址。这样dma就能直接处理
而对于dma的通道,还是基于dts的设置来通过内核dma的api来申请dma,如下
static int32_t GetDmaChannel(struct PlatformData *data) { struct DmaRuntimeData *dmaRtd = NULL; struct device_node *dmaOfNode = NULL; struct device *dmaDevice = NULL; struct property *dma_names = NULL; const char *dma_name = NULL; bool hasRender = false; bool hasCapture = false; static const char * const dmaChannelNames[] = { [DMA_TX_CHANNEL] = "tx", [DMA_RX_CHANNEL] = "rx", }; dmaRtd = (struct DmaRuntimeData *)data->dmaPrv; if (dmaRtd == NULL) { AUDIO_DEVICE_LOG_ERR("dmaRtd is null."); return HDF_FAILURE; } dmaOfNode = dmaRtd->dmaOfNode; if (dmaOfNode == NULL) { AUDIO_DEVICE_LOG_ERR("dmaOfNode is null."); return HDF_FAILURE; } of_property_for_each_string(dmaOfNode, "dma-names", dma_names, dma_name) { if (strcmp(dma_name, "rx") == 0) { hasCapture = true; } if (strcmp(dma_name, "tx") == 0) { hasRender = true; } } dmaDevice = dmaRtd->dmaDev; if (dmaDevice == NULL) { AUDIO_DEVICE_LOG_ERR("dmaDevice is null."); return HDF_FAILURE; } if (hasRender) { dmaRtd->dmaChn[DMA_TX_CHANNEL] = dma_request_slave_channel(dmaDevice, dmaChannelNames[DMA_TX_CHANNEL]); if (dmaRtd->dmaChn[DMA_TX_CHANNEL] == NULL) { AUDIO_DEVICE_LOG_ERR("dma_request_slave_channel DMA_TX_CHANNEL failed"); return HDF_FAILURE; } } if (hasCapture) { dmaRtd->dmaChn[DMA_RX_CHANNEL] = dma_request_slave_channel(dmaDevice, dmaChannelNames[DMA_RX_CHANNEL]); if (dmaRtd->dmaChn[DMA_RX_CHANNEL] == NULL) { AUDIO_DEVICE_LOG_ERR("dma_request_slave_channel DMA_RX_CHANNEL failed"); return HDF_FAILURE; } } return HDF_SUCCESS; }
这里要求了dts的dma配置如下:
i2s1_8ch: i2s@fe410000 { compatible = "rockchip,rk3568-i2s-tdm"; reg = <0x0 0xfe410000 0x0 0x1000>; interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru MCLK_I2S1_8CH_TX>, <&cru MCLK_I2S1_8CH_RX>, <&cru HCLK_I2S1_8CH>; clock-names = "mclk_tx", "mclk_rx", "hclk"; dmas = <&dmac1 2>, <&dmac1 3>; dma-names = "tx", "rx"; ........ }
这时候就有疑问了,为什么dts描述的很清楚(reg = <0x0 0xfe410000 0x0 0x1000>;),还需要hcs重复写一遍dma搬运地址和大小,直接从dts拿reg属性和node name就行了?这里我给出的结论就是:
对于Rk3568DmaBufAlloc,就是内核的dma_alloc_wc
对于Rk3568DmaBufFree,就是内核的dma_free_wc
对于Rk3568DmaRequestChannel,空的设计
对于Rk3568DmaConfigChannel,就是内核的dmaengine_slave_config
对于Rk3568DmaPrep,空的设计
对于Rk3568DmaSubmit,就是内核的dmaengine_prep_dma_cyclic
对于Rk3568DmaPause,就是内核的dmaengine_terminate_async
对于Rk3568PcmPointer,根据声卡的设计,将byte转换成audio的frame,如下
static int32_t BytesToFrames(uint32_t frameBits, uint32_t size, uint32_t *pointer) { if (pointer == NULL || frameBits == 0) { AUDIO_DEVICE_LOG_ERR("input para is error."); return HDF_FAILURE; } *pointer = size / frameBits; return HDF_SUCCESS; }
对于上述的设计,我们可以发现,其思想借鉴于ALSA的pcm_dmaengine.c。
与pcm_dmaengine.c不同的是,pcm_dmaengine.c实现了更通用的dmaengine,对于dai来说注册devm_snd_dmaengine_pcm_register即可,pcm_dmaengine.c只做audio的抽象。分层使得代码更健壮。
而这里的rk3568_dma_ops.c是直接指定了dma,并简化了pcm_dmaengine.c的实现。相当于将一个良好的,通用的,成熟的方案,写成了一个固定的,不灵活的,残缺的方案。
我们根据分析dma的hdf driver,可以知道openharmony通过直接了当的方式,将数据通过dma在i2s上搬运进出。同时也知道了,目前的dma驱动实现的非常简陋,而且十分固定,不过代码能跑就是福。
对于声卡的dai部分,因为i2s是rk3568侧的通讯总线,所以我们对于i2s的控制器这侧也需要配置正确,这样rk3568的i2s发送音频数据的时候,es8388能够正常的接收,所以有必要介绍一下rk侧的i2s的驱动,也就是dai驱动
对于i2s的驱动,代码路径如下:
device/board/hihope/rk3568/audio_drivers/dai/
相应文件如下:
rk3568_dai_adapter.c rk3568_dai_linux_driver.c rk3568_dai_ops.c
此驱动主要注册platfrom设备,读取dts的配置,设置时钟和分频,大致如下
static struct platform_driver rockchip_i2s_tdm_driver = { .probe = rockchip_i2s_tdm_probe, .remove = rockchip_i2s_tdm_remove, .driver = { .name = DRV_NAME, .of_match_table = of_match_ptr(rockchip_i2s_tdm_match), .pm = NULL, }, }; module_platform_driver(rockchip_i2s_tdm_driver); static int rockchip_i2s_tdm_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; const struct of_device_id *of_id; struct rk3568_i2s_tdm_dev *i2s_tdm; struct resource *res; struct device *temp_i2s_dev; ...... i2s_tdm->bclk_fs = 64; // default-freq div factor is 64 if (!of_property_read_u32(node, "rockchip,bclk-fs", &val)) { if ((val >= 32) && (val % 2 == 0)) // min-freq div factor is 32, and it is an integer multiple of 2 i2s_tdm->bclk_fs = val; } ...... i2s_tdm->mclk_tx = devm_clk_get(&pdev->dev, "mclk_tx"); if (IS_ERR(i2s_tdm->mclk_tx)) { return PTR_ERR(i2s_tdm->mclk_tx); } i2s_tdm->mclk_rx = devm_clk_get(&pdev->dev, "mclk_rx"); if (IS_ERR(i2s_tdm->mclk_rx)) { return PTR_ERR(i2s_tdm->mclk_rx); } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); i2s_tdm->regmap = devm_regmap_init_mmio(&pdev->dev, devm_ioremap_resource(&pdev->dev, res), &rockchip_i2s_tdm_regmap_config); if (IS_ERR(i2s_tdm->regmap)) { return PTR_ERR(i2s_tdm->regmap); } ...... }
这份驱动是hdf的驱动程序,它通过HDF_INIT(g_daiDriverEntry);
来注册一个hdf的驱动
/* HdfDriverEntry definitions */ struct HdfDriverEntry g_daiDriverEntry = { .moduleVersion = 1, .moduleName = "DAI_RK3568", .Bind = DaiDriverBind, .Init = DaiDriverInit, .Release = DaiDriverRelease, }; HDF_INIT(g_daiDriverEntry);
对于Bind回调,绑定服务,对于Init,用作注册dai,如下
daiData = (struct DaiData *)OsalMemCalloc(sizeof(*daiData)); if (daiData == NULL) { AUDIO_DEVICE_LOG_ERR("malloc DaiData fail!"); return HDF_FAILURE; } daiData->Read = Rk3568DeviceReadReg, daiData->Write = Rk3568DeviceWriteReg, daiData->DaiInit = Rk3568DaiDeviceInit, daiData->ops = &g_daiDeviceOps, daiData->daiInitFlag = false; OsalMutexInit(&daiData->mutex); daiHost->priv = daiData; if (DaiGetConfigInfo(device, daiData) != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("get dai data fail."); OsalMemFree(daiData); return HDF_FAILURE; } if (DaiGetServiceName(device, daiData) != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("get service name fail."); OsalMemFree(daiData); return HDF_FAILURE; } ret = AudioSocRegisterDai(device, (void *)daiData);
所以这里涉及一个dai的结构体,用于触发startup和hwparams以及trigger时的i2s的寄存器设置,如下:
struct AudioDaiOps g_daiDeviceOps = { .Startup = Rk3568DaiStartup, .HwParams = Rk3568DaiHwParams, .Trigger = Rk3568NormalTrigger, };
对于startup回调,这里i2s不需要做任何事情
对于hwparams回调,这里需要根据实际的参数,设置i2s的寄存器,时钟,如下:
int32_t Rk3568DaiHwParams(const struct AudioCard *card, const struct AudioPcmHwParams *param) { int ret; uint32_t bitWidth; struct DaiDevice *dai = NULL; struct DaiData *data = DaiDataFromCard(card); struct platform_device *platformdev = NULL; struct rk3568_i2s_tdm_dev *i2sTdm = NULL; if (card == NULL || card->rtd == NULL || param == NULL || param->cardServiceName == NULL) { AUDIO_DEVICE_LOG_ERR("input para is NULL."); return HDF_ERR_INVALID_PARAM; } if (data == NULL) { AUDIO_DEVICE_LOG_ERR("data is nullptr."); return HDF_FAILURE; } dai = card->rtd->cpuDai; platformdev = GetPlatformDev(dai); if (platformdev == NULL) { AUDIO_DEVICE_LOG_ERR("platformdev is NULL."); return HDF_FAILURE; } data->pcmInfo.channels = param->channels; AUDIO_DEVICE_LOG_DEBUG("channels count : %d .", param->channels); if (AudioFormatToBitWidth(param->format, &bitWidth) != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("AudioFormatToBitWidth error"); return HDF_FAILURE; } data->pcmInfo.bitWidth = bitWidth; data->pcmInfo.rate = param->rate; data->pcmInfo.streamType = param->streamType; i2sTdm = dev_get_drvdata(&platformdev->dev); if (i2sTdm == NULL) { AUDIO_DEVICE_LOG_ERR("i2sTdm is null"); return HDF_FAILURE; } ret = RK3568I2sTdmSetSysClk(i2sTdm, param); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("RK3568I2sTdmSetSysClk error"); return HDF_FAILURE; } ret = RK3568I2sTdmSetMclk(i2sTdm, param); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("RK3568I2sTdmSetMclk error"); return HDF_FAILURE; } AUDIO_DEVICE_LOG_DEBUG("success"); return HDF_SUCCESS; }
对于trigger回调,判断是否是stop/pause,start/resume,来控制i2s的寄存器,如下
static int32_t Rk3568TxAndRxSetReg(struct rk3568_i2s_tdm_dev *i2sTdm, enum AudioStreamType streamType, int on) { int ret; if (i2sTdm == NULL || i2sTdm->regmap == NULL) { AUDIO_DEVICE_LOG_ERR("i2sTdm is null"); return HDF_FAILURE; } if (on) { // when start/resume ret = Rk3568TxAndRxStart(i2sTdm, streamType); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Rk3568TxAndRxStart is failed"); return HDF_FAILURE; } } else { // when stop/pause ret = Rk3568TxAndRxStop(i2sTdm, streamType); if (ret != HDF_SUCCESS) { AUDIO_DEVICE_LOG_ERR("Rk3568TxAndRxStop is failed"); return HDF_FAILURE; } } AUDIO_DEVICE_LOG_DEBUG("success"); return HDF_SUCCESS; }
至此,rk3568的i2s的hdf驱动解析完成,我们可以知道,对于i2s,我们应该在合适的时候对其设置rk3568这侧的寄存器,让其按照指定的方式和codec进行通讯,所以需要这么一个hdf driver。