RK3566 Android13 耳机和喇叭音频自动切换
需求
接入耳机的时候,耳机有声喇叭没有声;
不接耳机的时候,耳机没声喇叭有声音;
硬件方案


这里喇叭硬件有一些问题。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) {
遇到的问题
可以实现音频自动切换了。但是:
正在播放音乐时,拔插耳机,不能切换音频链路。需要在没有音乐播放时,拔插耳机才有切换动作。
耳机插入图标没有了,是因为设备树中用的
headset-detect-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_HIGH>;导致的,去掉图标就出来了,但是音频切换又没了。尝试过直接传入 109 ( gpio3 RK_PB5 = 109 ),也行不通。


评论