联系管理员

开通文章发布权限

扫码 添加微信
微信图片
电话: QQ:1602036736

RK3566 Android13 耳机和喇叭音频自动切换

需求

接入耳机的时候,耳机有声喇叭没有声;
不接耳机的时候,耳机没声喇叭有声音;

硬件方案

图片#B #S #R #60% #auto

图片#B #S #R #60% #auto

这里喇叭硬件有一些问题。RK809 的 SPK_OUT 本身带有 1W 功放了,这里又再接了一个外部功放,导致声音有底噪。

实现思路

通过耳机的插入检测引脚,去判断是否接入,实现自动切换。之前测试时,就发现了使用或者不使用 use-ext-amplifier; 会导致是否使用耳机还是不使用耳机,所以就在设备树中,传入耳机的 插入检测引脚 参数到驱动中 ,在驱动中判断引脚状态,根据状态切换 use-ext-amplifier;

成功补丁

设备树

rk809_codec: codec {
            #sound-dai-cells = <0>;
            compatible = "rockchip,rk809-codec", "rockchip,rk817-codec";
            clocks = <&cru I2S1_MCLKOUT>;
            clock-names = "mclk";
            assigned-clocks = <&cru I2S1_MCLKOUT>, <&cru I2S1_MCLK_TX_IOE>;
            assigned-clock-rates = <12288000>;
            assigned-clock-parents = <&cru I2S1_MCLKOUT_TX>, <&cru I2S1_MCLKOUT_TX>;
            pinctrl-names = "default";
            pinctrl-0 = <&i2s1m0_mclk>;
            hp-volume = <20>;
            spk-volume = <5>;

            //使用外放功能设备树里面就要添加 use-ext-amplifier: 这个属性
            use-ext-amplifier; //用耳机则不用注释 用喇叭则注释
            // 添加耳机检测GPIO(使用您已定义的gpio3 RK_PB5)
            headset-detect-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_HIGH>;
            mic-in-differential;
            status = "okay";
        };
    };

音频驱动

android13-SDK/kernel-5.10/sound/soc/codecs/rk817_codec.c

diff --git a/sound/soc/codecs/rk817_codec.c b/sound/soc/codecs/rk817_codec.c
index c178da3e52a2..8775e08dddea 100644
--- a/sound/soc/codecs/rk817_codec.c
+++ b/sound/soc/codecs/rk817_codec.c
@@ -68,6 +68,7 @@ struct rk817_codec_priv {
 	struct rk808 *rk817;
 	struct clk *mclk;
 	struct mutex clk_lock;
+	struct mutex path_lock; // 用于保护路径切换
 
 	unsigned int clk_capture;
 	unsigned int clk_playback;
@@ -90,9 +91,15 @@ struct rk817_codec_priv {
 
 	struct gpio_desc *spk_ctl_gpio;
 	struct gpio_desc *hp_ctl_gpio;
+	struct gpio_desc *headset_detect_gpio; // 耳机检测GPIO
 	int spk_mute_delay;
 	int hp_mute_delay;
 	int chip_ver;
+	
+	// 工作队列相关
+	struct work_struct headset_detect_work;
+	bool headset_state_changed;
+	bool new_headset_state;
 };
 
 static const struct reg_default rk817_reg_defaults[] = {
@@ -512,7 +519,9 @@ static int rk817_codec_power_down(struct snd_soc_component *component, int type)
 
 /* For tiny alsa playback/capture/voice call path */
 static const char * const rk817_playback_path_mode[] = {
-	"OFF", "RCV", "SPK", "HP", "HP_NO_MIC", "BT", "SPK_HP", /* 0-6 */
+	//liguoyi 251017 audio
+	//"OFF", "RCV", "SPK", "HP", "HP_NO_MIC", "BT", "SPK_HP", /* 0-6 */
+	"SPK", "RCV", "OFF", "HP", "HP_NO_MIC", "BT", "SPK_HP", /* 0-6 */
 	"RING_SPK", "RING_HP", "RING_HP_NO_MIC", "RING_SPK_HP"}; /* 7-10 */
 
 static const char * const rk817_capture_path_mode[] = {
@@ -530,7 +539,7 @@ static SOC_ENUM_SINGLE_DECL(rk817_resume_path_type,
 	0, 0, rk817_binary_mode);
 
 static int rk817_playback_path_config(struct snd_soc_component *component,
-				      long pre_path, long target_path)
+		      long pre_path, long target_path)
 {
 	struct rk817_codec_priv *rk817 = snd_soc_component_get_drvdata(component);
 
@@ -539,6 +548,9 @@ static int rk817_playback_path_config(struct snd_soc_component *component,
 	DBG("%s : set playback_path %ld, pre_path %ld\n",
 	    __func__, rk817->playback_path, pre_path);
 
+	// 获取路径切换互斥锁
+	mutex_lock(&rk817->path_lock);
+	// 同时获取时钟锁
 	mutex_lock(&rk817->clk_lock);
 	if (rk817->playback_path != OFF) {
 		if (rk817->clk_playback == 0) {
@@ -552,6 +564,7 @@ static int rk817_playback_path_config(struct snd_soc_component *component,
 		}
 	}
 	mutex_unlock(&rk817->clk_lock);
+	mutex_unlock(&rk817->path_lock);
 
 	switch (rk817->playback_path) {
 	case OFF:
@@ -1215,13 +1228,100 @@ static int rk817_resume(struct snd_soc_component *component)
 	return 0;
 }
 
+static void rk817_headset_detect_work_func(struct work_struct *work)
+{
+	struct rk817_codec_priv *rk817 = container_of(work, struct rk817_codec_priv, headset_detect_work);
+	bool old_state;
+	// long int pre_path;
+	if (!rk817 || !rk817->component || !rk817->headset_detect_gpio) {
+		dev_err(rk817 ? rk817->component->dev : NULL, 
+			"%s: Invalid parameters\n", __func__);
+		return;
+	}
+
+	// 添加额外的调试信息
+	pr_info("%s: Work queue executed\n", __func__);
+
+	// 确保只处理状态变化的情况
+	if (!rk817->headset_state_changed) {
+		pr_info("%s: No state change detected\n", __func__);
+		return;
+	}
+
+	old_state = rk817->use_ext_amplifier;
+	pr_info("%s: Old state: %d, New state: %d\n", __func__, old_state, rk817->new_headset_state);
+
+	// 如果状态确实发生变化,更新use_ext_amplifier并重新配置音频路径
+	if (rk817->new_headset_state != old_state) {
+		pr_info("%s: Headset state changed, updating use_ext_amplifier\n", __func__);
+		
+		mutex_lock(&rk817->path_lock);
+		// 先静音避免爆音
+		// pr_info("%s: Muting audio before path change\n", __func__);
+		// rk817_codec_ctl_gpio(rk817, CODEC_SET_SPK, 0);
+		// rk817_codec_ctl_gpio(rk817, CODEC_SET_HP, 0);
+		
+		// 更新use_ext_amplifier参数(耳机插入时为true,拔出时为false)
+		rk817->use_ext_amplifier = rk817->new_headset_state;
+		pr_info("%s: use_ext_amplifier updated to %d\n", __func__, rk817->use_ext_amplifier);
+		
+		mutex_unlock(&rk817->path_lock);
+	}
+	
+	// 清除状态变化标志
+	rk817->headset_state_changed = false;
+	pr_info("%s: Work queue completed\n", __func__);
+}
+
+static irqreturn_t rk817_headset_detect_irq(int irq, void *data)
+{
+	struct rk817_codec_priv *rk817 = data;
+	bool current_state;
+	int gpio_val;
+	if (!rk817 || !rk817->component || !rk817->headset_detect_gpio) {
+		pr_err("%s: Invalid parameters\n", __func__);
+		return IRQ_NONE;
+	}
+
+	pr_info("%s: IRQ triggered\n", __func__);
+
+	// 在中断上下文中使用非睡眠版本的GPIO读取函数
+	// 注意:有些平台可能需要使用gpiod_get_value_cansleep,但在中断上下文中不安全
+	// 这里添加错误检查
+	gpio_val = gpiod_get_value(rk817->headset_detect_gpio);
+	current_state = (gpio_val > 0); // 确保是布尔值
+	
+	pr_info("%s: GPIO value read: %d, Current state: %d, use_ext_amplifier: %d\n", 
+		__func__, gpio_val, current_state, rk817->use_ext_amplifier);
+	
+	// 检查状态是否发生变化
+	if (current_state != rk817->use_ext_amplifier) {
+		pr_info("%s: State change detected, scheduling work\n", __func__);
+		// 设置状态变化标志和新状态
+		rk817->new_headset_state = current_state;
+		rk817->headset_state_changed = true;
+		
+		// 调度工作队列来处理实际的路径切换
+		if (schedule_work(&rk817->headset_detect_work)) {
+			pr_info("%s: Work scheduled successfully\n", __func__);
+		} else {
+			pr_err("%s: Failed to schedule work\n", __func__);
+		}
+	} else {
+		pr_info("%s: No state change\n", __func__);
+	}
+	
+	return IRQ_HANDLED;
+}
+
 static int rk817_probe(struct snd_soc_component *component)
 {
 	struct rk817_codec_priv *rk817 = snd_soc_component_get_drvdata(component);
 	int chip_name = 0;
 	int chip_ver = 0;
-
-	DBG("%s\n", __func__);
+	int irq;
+	int ret;
+	pr_info("%s: Entering probe function\n", __func__);
 
 	if (!rk817) {
 		dev_err(component->dev, "%s : rk817 priv is NULL!\n",
@@ -1232,6 +1332,7 @@ static int rk817_probe(struct snd_soc_component *component)
 	rk817->component = component;
 	rk817->playback_path = OFF;
 	rk817->capture_path = MIC_OFF;
+	rk817->headset_state_changed = false;
 
 	chip_name = snd_soc_component_read(component, RK817_PMIC_CHIP_NAME);
 	chip_ver = snd_soc_component_read(component, RK817_PMIC_CHIP_VER);
@@ -1242,11 +1343,45 @@ static int rk817_probe(struct snd_soc_component *component)
 	rk817_reset(component);
 	clk_disable_unprepare(rk817->mclk);
 	mutex_init(&rk817->clk_lock);
+	mutex_init(&rk817->path_lock); // 初始化路径切换互斥锁
+	
+	// 初始化工作队列
+	INIT_WORK(&rk817->headset_detect_work, rk817_headset_detect_work_func);
+	pr_info("%s: Work queue initialized\n", __func__);
+	
 	rk817->clk_capture = 0;
 	rk817->clk_playback = 0;
 
+	// 如果找到了耳机检测GPIO,设置中断处理
+	if (!IS_ERR_OR_NULL(rk817->headset_detect_gpio)) {
+		pr_info("%s: Headset detect GPIO found\n", __func__);
+		irq = gpiod_to_irq(rk817->headset_detect_gpio);
+		if (irq >= 0) {
+			pr_info("%s: IRQ number: %d\n", __func__, irq);
+			ret = devm_request_irq(component->dev, irq, rk817_headset_detect_irq,
+			      IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+			      "rk817_headset_detect", rk817);
+			if (ret < 0) {
+				dev_err(component->dev, "Failed to request headset detect IRQ: %d\n", ret);
+			} else {
+				pr_info("%s: IRQ requested successfully\n", __func__);
+				// 初始化时根据当前GPIO状态设置初始值
+				rk817->use_ext_amplifier = 
+					gpiod_get_value_cansleep(rk817->headset_detect_gpio);
+				pr_info("%s: Initial headset state: %d\n", __func__, 
+					rk817->use_ext_amplifier);
+			}
+		} else {
+			pr_err("%s: Failed to get IRQ from GPIO\n", __func__);
+		}
+	} else {
+		pr_warn("%s: No headset detect GPIO available\n", __func__);
+		// 如果没有耳机检测GPIO,设置一个默认值
+		rk817->use_ext_amplifier = false;
+	}
+
 	snd_soc_add_component_controls(component, rk817_snd_path_controls,
-				       ARRAY_SIZE(rk817_snd_path_controls));
+		       ARRAY_SIZE(rk817_snd_path_controls));
 	return 0;
 }
 
@@ -1262,9 +1397,13 @@ static void rk817_remove(struct snd_soc_component *component)
 		return;
 	}
 
+	// 取消调度工作队列并等待完成
+	cancel_work_sync(&rk817->headset_detect_work);
+
 	rk817_codec_power_down(component, RK817_CODEC_ALL);
 	snd_soc_component_exit_regmap(component);
 	mutex_destroy(&rk817->clk_lock);
+	mutex_destroy(&rk817->path_lock); // 销毁路径切换互斥锁
 	mdelay(10);
 
 }
@@ -1281,7 +1420,7 @@ static const struct snd_soc_component_driver soc_codec_dev_rk817 = {
 };
 
 static int rk817_codec_parse_dt_property(struct device *dev,
-					 struct rk817_codec_priv *rk817)
+			 struct rk817_codec_priv *rk817)
 {
 	struct device_node *node = dev->parent->of_node;
 	int ret;
@@ -1302,19 +1441,36 @@ static int rk817_codec_parse_dt_property(struct device *dev,
 	}
 
 	rk817->hp_ctl_gpio = devm_gpiod_get_optional(dev, "hp-ctl",
-						  GPIOD_OUT_LOW);
+				GPIOD_OUT_LOW);
 	if (!IS_ERR_OR_NULL(rk817->hp_ctl_gpio)) {
 		DBG("%s : hp-ctl-gpio %d\n", __func__,
 		    desc_to_gpio(rk817->hp_ctl_gpio));
 	}
 
 	rk817->spk_ctl_gpio = devm_gpiod_get_optional(dev, "spk-ctl",
-						  GPIOD_OUT_LOW);
+				GPIOD_OUT_LOW);
 	if (!IS_ERR_OR_NULL(rk817->spk_ctl_gpio)) {
 		DBG("%s : spk-ctl-gpio %d\n", __func__,
 		    desc_to_gpio(rk817->spk_ctl_gpio));
 	}
 
+	// 从设备树获取耳机检测GPIO
+	rk817->headset_detect_gpio = devm_gpiod_get_optional(dev, "headset-detect",
+				GPIOD_IN);
+	if (!IS_ERR_OR_NULL(rk817->headset_detect_gpio)) {
+		DBG("%s : headset-detect-gpio %d\n", __func__,
+		    desc_to_gpio(rk817->headset_detect_gpio));
+	} else {
+		DBG("%s : No headset-detect-gpio found\n", __func__);
+		// 如果找不到专用的headset-detect GPIO,尝试查找headset_gpio
+		rk817->headset_detect_gpio = devm_gpiod_get_optional(dev, "headset-gpio",
+				    GPIOD_IN);
+		if (!IS_ERR_OR_NULL(rk817->headset_detect_gpio)) {
+			DBG("%s : Using headset-gpio %d\n", __func__,
+			    desc_to_gpio(rk817->headset_detect_gpio));
+		}
+	}
+
 	ret = of_property_read_u32(node, "spk-mute-delay-ms",
 				   &rk817->spk_mute_delay);
 	if (ret < 0) {
@@ -1410,6 +1566,9 @@ static int rk817_platform_probe(struct platform_device *pdev)
 		return -ENOMEM;
 
 	platform_set_drvdata(pdev, rk817_codec_data);
+	
+	// 从设备树中读取use_ext_amplifier默认值
+	rk817_codec_data->use_ext_amplifier = of_property_read_bool(pdev->dev.parent->of_node, "use-ext-amplifier");
 
 	ret = rk817_codec_parse_dt_property(&pdev->dev, rk817_codec_data);
 	if (ret < 0) {

遇到的问题

可以实现音频自动切换了。但是:

  1. 正在播放音乐时,拔插耳机,不能切换音频链路。需要在没有音乐播放时,拔插耳机才有切换动作。

  2. 耳机插入图标没有了,是因为设备树中用的 headset-detect-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_HIGH>; 导致的,去掉图标就出来了,但是音频切换又没了。尝试过直接传入 109 ( gpio3 RK_PB5 = 109 ),也行不通。

 

评论

快捷导航

把好文章收藏到微信

打开微信,扫码查看

关闭

还没有账号?立即注册