1. 程式人生 > >[Android6.0][RK3399] 實現耳機和喇叭自動切換功能

[Android6.0][RK3399] 實現耳機和喇叭自動切換功能

Platform: RK3399
OS: Android 6.0
Kernel: Linux4.4
Version: v2017.03

需求分析

RK 預設的音效卡 RT5651(Card 0)是從耳機(device 0)輸出。
但是我們的產品上同時具有 Speaker 和 Headphone,預設驅動中沒有自動切換兩者的邏輯。
所以需要實現預設從喇叭輸出,插上耳機的情況下從耳機輸出的功能

實現方式

查閱原理圖
HP_DET_H 為耳機狀態檢測腳。

SPK_CTL_H 為控制 Speaker 使能管腳。

所以整個的邏輯很簡單即

HP_DET_H 檢測耳機狀態
————> 為高 耳機插入,拉底 SPK_CTL_H 禁能喇叭
————> 為低 耳機拔出,拉高 SPK_CTL_H 使能喇叭

進行編碼

dts 中新增相應 GPIO,並開啟音效卡驅動

diff --git a/arch/arm64/boot/dts/rockchip/rk3399-xxx.dts b/arch/arm64/boot/dts/rockchip/rk3399-xxx.dts
index b9002f7..039c95a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399-xxx.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3399-xxx.dts
@@ -63,6 +63,8 @@
        compatible = "rockchip,rockchip-rt5651-tc358749x-sound";
        rockchip,cpu = <&i2s0 &i2s0>;
        rockchip,codec = <&rt5651 &rt5651 &tc358749x>;
+       spk-con-gpio = <&gpio0 11 GPIO_ACTIVE_HIGH>;
+ hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_HIGH>; status = "okay"; }; @@ -147,7 +149,7 @@ int-gpios = <&gpio2 12 GPIO_ACTIVE_HIGH>; pinctrl-names = "default"; pinctrl-0 = <&hdmiin_gpios>; - status = "disabled"; + status = "okay";
}; };

修改 snd_soc_card 結構體新增相關成員變數

diff --git a/include/sound/soc.h b/include/sound/soc.h
index fb955e6..e6b3ac7 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -1080,8 +1080,16 @@ struct snd_soc_card {
    struct mutex mutex;
    struct mutex dapm_mutex;

+   int debounce_time;
+   int hp_det_invert;
+   struct delayed_work work;
+   bool spk_active_level;
+   bool hp_inserted;
+
    bool instantiated;

+   int spk_ctl_gpio;
+   int hp_det_gpio;
    int (*probe)(struct snd_soc_card *card);
    int (*late_probe)(struct snd_soc_card *card);
    int (*remove)(struct snd_soc_card *card);

新增驅動程式碼

diff --git a/sound/soc/rockchip/rockchip_rt5651_tc358749x.c b/sound/soc/rockchip/rockchip_rt5651_tc358749x.c
index 21f8ee2..14acebd 100644
--- a/sound/soc/rockchip/rockchip_rt5651_tc358749x.c
+++ b/sound/soc/rockchip/rockchip_rt5651_tc358749x.c
@@ -18,12 +18,18 @@
 #include <linux/module.h>
 #include <sound/soc.h>

+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/clk.h>
+
 #include "rockchip_i2s.h"
 #include "../codecs/rt5651.h"
 #include "../codecs/tc358749x.h"

 #define DRV_NAME "rk3399-rt5651-tc358749x"

+#define INVALID_GPIO -1
+
 static const struct snd_soc_dapm_widget rockchip_dapm_widgets[] = {
    SND_SOC_DAPM_HP("Headphones", NULL),
    SND_SOC_DAPM_SPK("Lineout", NULL),
@@ -184,11 +190,58 @@ static struct snd_soc_card rockchip_sound_card = {
    .num_controls = ARRAY_SIZE(rockchip_controls),
 };

+static irqreturn_t rt5651_irq_handler(int irq, void *data)
+{
+   struct snd_soc_card *card = data;
+   queue_delayed_work(system_power_efficient_wq, &card->work,
+              msecs_to_jiffies(card->debounce_time));
+
+   return IRQ_HANDLED;
+}
+
+static void rt5651_enable_spk(struct snd_soc_card *card, bool enable)
+{
+   bool level;
+
+   level = enable ? card->spk_active_level : !card->spk_active_level;
+   gpio_set_value(card->spk_ctl_gpio, level);
+}
+
+static void hp_work(struct work_struct *work)
+{
+   struct snd_soc_card *card;
+   int enable;
+
+   card = container_of(work, struct snd_soc_card, work.work);
+   enable = gpio_get_value(card->hp_det_gpio);
+   if(card->hp_det_invert)
+       enable = !enable;
+
+   card->hp_inserted = enable ? true : false;
+   if(card->hp_inserted){
+       //printk("hp_work rt5651_enable_spk false\n");
+       rt5651_enable_spk(card, false);
+   }   else {
+       //printk("hp_work rt5651_enable_spk true\n");
+       rt5651_enable_spk(card,true);
+   }
+}
+
 static int rockchip_sound_probe(struct platform_device *pdev)
 {
    struct snd_soc_card *card = &rockchip_sound_card;
    struct device_node *cpu_node;
-   int i, ret;
+   int i;
+   int ret = -1;
+
+   int hp_irq;
+   enum of_gpio_flags flags;
+
+   card->debounce_time = 200;
+   card->hp_det_invert = 0;
+   card->hp_inserted = false;
+   card->spk_ctl_gpio = INVALID_GPIO;
+   card->hp_det_gpio = INVALID_GPIO;

    dev_info(&pdev->dev, "%s\n", __func__);

@@ -213,6 +266,47 @@ static int rockchip_sound_probe(struct platform_device *pdev)
        }
    }

+   card->spk_ctl_gpio = of_get_named_gpio_flags(pdev->dev.of_node, "spk-con-gpio", 0, &flags);
+   if(!gpio_is_valid(card->spk_ctl_gpio))  {
+       dev_err(&pdev->dev,"spk-ctl-gpio: %d is invalid\n", card->spk_ctl_gpio);
+       card->spk_ctl_gpio = INVALID_GPIO;
+   }else {
+       dev_info(&pdev->dev,"spk-ctl-gpio: %d is arrivable\n", card->spk_ctl_gpio);
+       card->spk_active_level = !(flags & OF_GPIO_ACTIVE_LOW);
+       ret = devm_gpio_request_one(&pdev->dev, card->spk_ctl_gpio,
+                                                               GPIOF_DIR_OUT,NULL);
+       if(ret) {
+           dev_err(&pdev->dev,"spk_ctl_gpio: request failed!\n");
+       }
+       rt5651_enable_spk(card, true);
+   }
+
+   card->hp_det_gpio = of_get_named_gpio_flags(pdev->dev.of_node,"hp-det-gpio", 0, &flags);
+   if(!gpio_is_valid(card->hp_det_gpio)) {
+       printk("hp-det-gpio: %d is invalid\n",card->hp_det_gpio);
+       card->hp_det_gpio = INVALID_GPIO;
+   }else {
+       INIT_DELAYED_WORK(&card->work, hp_work);
+       card->hp_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW);
+       ret = devm_gpio_request_one (&pdev->dev, card->hp_det_gpio,GPIOF_IN,"hp det");
+       if( ret < 0)
+           return ret;
+       hp_irq = gpio_to_irq(card->hp_det_gpio);
+       ret = devm_request_threaded_irq(&pdev->dev, hp_irq, NULL,
+                                       rt5651_irq_handler,
+                                       IRQF_TRIGGER_FALLING |
+                                       IRQF_TRIGGER_RISING |
+                                       IRQF_ONESHOT,
+                                       "rt5651_interrupt", card);
+       if( ret < 0) {
+           dev_err(&pdev->dev, "request_irq: failed %d\n", ret);
+           return ret;
+       }
+
+       schedule_delayed_work(&card->work,
+                   msecs_to_jiffies(card->debounce_time));
+   }
+
    card->dev = &pdev->dev;
    platform_set_drvdata(pdev, card);
    ret = devm_snd_soc_register_card(&pdev->dev, card);