From c96dc7817d36eedb85d8ac3b8775e4d9803f8467 Mon Sep 17 00:00:00 2001
From: Sergey Larin <cerg2010cerg2010@mail.ru>
Date: Sun, 14 Jul 2019 20:22:44 +0300
Subject: [PATCH] ASoC: tegra_wm8994: New driver based on WM8903

This fully routed driver resolves issues with suspend/resume state not being
correctly restored. It also provides headphone detection and internal/external
mic switching (however, mic detection is probably broken).

Signed-off-by: Sergey Larin <cerg2010cerg2010@mail.ru>
---
 sound/soc/tegra/tegra_wm8994.c | 295 +++++++++++++++++++++------------
 1 file changed, 188 insertions(+), 107 deletions(-)

diff --git a/sound/soc/tegra/tegra_wm8994.c b/sound/soc/tegra/tegra_wm8994.c
index 87cb36df3bcf..5e59b3b34f72 100755
--- a/sound/soc/tegra/tegra_wm8994.c
+++ b/sound/soc/tegra/tegra_wm8994.c
@@ -1,8 +1,10 @@
 /*
  * tegra_wm8994.c - Tegra machine ASoC driver for boards using WM8994 codec.
  *
+ * Author: Sergey Larin <cerg2010cerg2010@mail.ru>
+ * Based on driver for wm8994 by:
  * Author: Stephen Warren <swarren@nvidia.com>
- * Copyright (C) 2010-2011 - NVIDIA, Inc.
+ * Copyright (C) 2010-2012 - NVIDIA, Inc.
  *
  * Based on code copyright/by:
  *
@@ -28,29 +30,29 @@
  *
  */
 
-#include <asm/mach-types.h>
-
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/gpio.h>
-#include <linux/of.h>
+#include <linux/of_gpio.h>
 
 #include <sound/core.h>
+#include <sound/jack.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
 
-#include "tegra_pcm.h"
-#include "tegra_asoc_utils.h"
+#include "../codecs/wm8994.h"
 
-#ifdef CONFIG_ARCH_TEGRA_2x_SOC
-#include "tegra20_das.h"
-#endif
+#include "tegra_asoc_utils.h"
 
 #define DRV_NAME "tegra-snd-wm8994"
 
 struct tegra_wm8994 {
+	int gpio_hp_det;
+	int gpio_ear_sel;
+	int gpio_int_mic_en;
+	int gpio_ext_mic_en;
 	struct tegra_asoc_utils_data util_data;
 };
 
@@ -59,10 +61,9 @@ static int tegra_wm8994_hw_params(struct snd_pcm_substream *substream,
 {
 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
 	struct snd_soc_dai *codec_dai = rtd->codec_dai;
-	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 	struct snd_soc_card *card = rtd->card;
 	struct tegra_wm8994 *machine = snd_soc_card_get_drvdata(card);
-	int srate, mclk, i2s_daifmt;
+	int srate, mclk;
 	int err;
 
 	srate = params_rate(params);
@@ -82,104 +83,152 @@ static int tegra_wm8994_hw_params(struct snd_pcm_substream *substream,
 
 	err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
 	if (err < 0) {
-		if (!(machine->util_data.set_mclk % mclk))
-			mclk = machine->util_data.set_mclk;
-		else {
-			dev_err(card->dev, "Can't configure clocks\n");
-			return err;
-		}
-	}
-
-	i2s_daifmt = SND_SOC_DAIFMT_NB_NF |
-		     SND_SOC_DAIFMT_CBS_CFS;
-
-	/* Use DSP mode for mono on Tegra20 */
-	// if ((params_channels(params) != 2) &&
-	//     (machine_is_ventana() || machine_is_harmony() ||
-	//     machine_is_kaen() || machine_is_aebl()))
-	// 	i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
-	// else
-		i2s_daifmt |= SND_SOC_DAIFMT_I2S;
-
-	err = snd_soc_dai_set_fmt(codec_dai, i2s_daifmt);
-	if (err < 0) {
-		dev_err(card->dev, "codec_dai fmt not set\n");
+		dev_err(card->dev, "Can't configure clocks\n");
 		return err;
 	}
 
-	err = snd_soc_dai_set_fmt(cpu_dai, i2s_daifmt);
-	if (err < 0) {
-		dev_err(card->dev, "cpu_dai fmt not set\n");
-		return err;
-	}
-	/* Need to check clk id */
-	err = snd_soc_dai_set_sysclk(codec_dai, 1, mclk,
+	err = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, mclk,
 					SND_SOC_CLOCK_IN);
 	if (err < 0) {
 		dev_err(card->dev, "codec_dai clock not set\n");
 		return err;
 	}
 
-#ifdef CONFIG_ARCH_TEGRA_2x_SOC
-	err = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAP_SEL_DAC1,
-					TEGRA20_DAS_DAP_ID_1);
-	if (err < 0) {
-		dev_err(card->dev, "failed to set dap-dac path\n");
-		return err;
-	}
-
-	err = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_1,
-					TEGRA20_DAS_DAP_SEL_DAC1);
-	if (err < 0) {
-		dev_err(card->dev, "failed to set dac-dap path\n");
-		return err;
-	}
-#endif
 	return 0;
 }
 
-static int tegra_hw_free(struct snd_pcm_substream *substream)
+static const struct snd_soc_ops tegra_wm8994_ops = {
+	.hw_params = tegra_wm8994_hw_params,
+};
+
+static struct snd_soc_jack tegra_wm8994_hp_jack;
+
+static struct snd_soc_jack_pin tegra_wm8994_hp_jack_pins[] = {
+	{
+		.pin = "Headphone Jack",
+		.mask = SND_JACK_HEADPHONE,
+	},
+};
+
+static struct snd_soc_jack_gpio tegra_wm8994_hp_jack_gpio = {
+	.name = "headphone detect",
+	.report = SND_JACK_HEADPHONE,
+	.debounce_time = 150,
+	.invert = 1,
+};
+
+static struct snd_soc_jack tegra_wm8994_mic_jack;
+
+static struct snd_soc_jack_pin tegra_wm8994_mic_jack_pins[] = {
+	{
+		.pin = "Mic Jack",
+		.mask = SND_JACK_MICROPHONE,
+	},
+};
+
+static int tegra_wm8994_event_ext_mic(struct snd_soc_dapm_widget *w,
+					struct snd_kcontrol *k, int event)
 {
+	struct snd_soc_dapm_context *dapm = w->dapm;
+	struct snd_soc_card *card = dapm->card;
+	struct tegra_wm8994 *machine = snd_soc_card_get_drvdata(card);
+
+	pr_info("EXT MIC event: %s", SND_SOC_DAPM_EVENT_ON(event) ? "on" : "off");
+
+	if (gpio_is_valid(machine->gpio_ext_mic_en))
+	{
+		gpio_set_value_cansleep(machine->gpio_ext_mic_en,
+				SND_SOC_DAPM_EVENT_ON(event));
+	}
+
+	if (gpio_is_valid(machine->gpio_int_mic_en))
+	{
+		gpio_set_value_cansleep(machine->gpio_int_mic_en,
+				!SND_SOC_DAPM_EVENT_ON(event));
+	}
+
+	/* Internal/external mic switch */
+	if (gpio_is_valid(machine->gpio_ear_sel))
+	{
+		gpio_set_value_cansleep(machine->gpio_ear_sel,
+				SND_SOC_DAPM_EVENT_ON(event));
+	}
+
 	return 0;
 }
 
-static struct snd_soc_ops tegra_wm8994_ops = {
-	.hw_params = tegra_wm8994_hw_params,
-	.hw_free = tegra_hw_free,
+static const struct snd_soc_dapm_widget tegra_wm8994_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Int Spk", NULL),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", tegra_wm8994_event_ext_mic),
+	SND_SOC_DAPM_SPK("Earpiece Spk", NULL),
+};
+
+static const struct snd_kcontrol_new tegra_wm8994_controls[] = {
+	SOC_DAPM_PIN_SWITCH("Int Spk"),
 };
 
 static int tegra_wm8994_init(struct snd_soc_pcm_runtime *rtd)
 {
-	pr_info("%s\n", __func__);
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_component *component = codec_dai->component;
+	struct snd_soc_card *card = rtd->card;
+	struct tegra_wm8994 *machine = snd_soc_card_get_drvdata(card);
+
+	if (gpio_is_valid(machine->gpio_hp_det)) {
+		tegra_wm8994_hp_jack_gpio.gpio = machine->gpio_hp_det;
+		snd_soc_card_jack_new(rtd->card, "Headphone Jack",
+				      SND_JACK_HEADPHONE, &tegra_wm8994_hp_jack,
+				      tegra_wm8994_hp_jack_pins,
+				      ARRAY_SIZE(tegra_wm8994_hp_jack_pins));
+		snd_soc_jack_add_gpios(&tegra_wm8994_hp_jack,
+					1,
+					&tegra_wm8994_hp_jack_gpio);
+	}
+
+	snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE,
+			      &tegra_wm8994_mic_jack,
+			      tegra_wm8994_mic_jack_pins,
+			      ARRAY_SIZE(tegra_wm8994_mic_jack_pins));
+	wm8994_mic_detect(component, &tegra_wm8994_mic_jack, 1);
+
 	return 0;
 }
 
 static int tegra_wm8994_remove(struct snd_soc_card *card)
 {
-	pr_info("%s\n", __func__);
+	struct snd_soc_pcm_runtime *rtd =
+		snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_component *component = codec_dai->component;
+
+	wm8994_mic_detect(component, NULL, 1);
+
 	return 0;
 }
 
-static struct snd_soc_dai_link tegra_wm8994_dai[] = {
-	{
-		.name = "WM8994",
-		.stream_name = "WM8994 PCM",
-		.codec_dai_name = "wm8994-aif1",
-		.init = tegra_wm8994_init,
-
-		// .codec_name = "WM8994 I2C Codec.8-001b",
-		// .platform_name = "tegra-pcm-audio",
-		// .cpu_dai_name = "tegra20-i2s.0",
-		// .codec_dai_name = "WM8994 PAIFRX",
-		.ops = &tegra_wm8994_ops,
-	},
+static struct snd_soc_dai_link tegra_wm8994_dai = {
+	.name = "WM8994",
+	.stream_name = "WM8994 PCM",
+	.codec_dai_name = "wm8994-aif1",
+	.init = tegra_wm8994_init,
+	.ops = &tegra_wm8994_ops,
+	.dai_fmt = SND_SOC_DAIFMT_I2S |
+		   SND_SOC_DAIFMT_NB_NF |
+		   SND_SOC_DAIFMT_CBS_CFS,
 };
 
 static struct snd_soc_card snd_soc_tegra_wm8994 = {
 	.name = "tegra-wm8994",
-	.dai_link = tegra_wm8994_dai,
-	.num_links = ARRAY_SIZE(tegra_wm8994_dai),
+	.owner = THIS_MODULE,
+	.dai_link = &tegra_wm8994_dai,
+	.num_links = 1,
 	.remove = tegra_wm8994_remove,
+	.controls = tegra_wm8994_controls,
+	.num_controls = ARRAY_SIZE(tegra_wm8994_controls),
+	.dapm_widgets = tegra_wm8994_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(tegra_wm8994_dapm_widgets),
+	.fully_routed = true,
 };
 
 static int tegra_wm8994_driver_probe(struct platform_device *pdev)
@@ -189,59 +238,95 @@ static int tegra_wm8994_driver_probe(struct platform_device *pdev)
 	struct tegra_wm8994 *machine;
 	int ret;
 
-	pr_info("%s\n", __func__);
-
-	if (!pdev->dev.platform_data && !pdev->dev.of_node) {
-		dev_err(&pdev->dev, "No platform data supplied\n");
-		return -EINVAL;
-	}
-
 	machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8994),
-			GFP_KERNEL);
-	if (!machine) {
-		dev_err(&pdev->dev, "Can't allocate tegra_wm8994 struct\n");
-		ret = -ENOMEM;
-		goto err;
-	}
+			       GFP_KERNEL);
+	if (!machine)
+		return -ENOMEM;
 
 	card->dev = &pdev->dev;
-	platform_set_drvdata(pdev, card);
 	snd_soc_card_set_drvdata(card, machine);
 
-	// Parse device tree nodes
+	machine->gpio_ear_sel = of_get_named_gpio(np, "nvidia,ear-sel-gpios",
+						  0);
+	if (machine->gpio_ear_sel == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	if (gpio_is_valid(machine->gpio_ear_sel)) {
+		ret = devm_gpio_request_one(&pdev->dev, machine->gpio_ear_sel,
+					    GPIOF_OUT_INIT_HIGH, "ear_sel");
+		if (ret) {
+			dev_err(card->dev, "cannot get ear_sel gpio\n");
+			return ret;
+		}
+		gpio_set_value_cansleep(machine->gpio_ear_sel, 0);
+	}
 
-	// ...
+	machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0);
+	if (machine->gpio_hp_det == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+
+	machine->gpio_int_mic_en = of_get_named_gpio(np,
+						"nvidia,int-mic-en-gpios", 0);
+	if (machine->gpio_int_mic_en == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	if (gpio_is_valid(machine->gpio_int_mic_en)) {
+		/* Disable int mic; enable signal is active-high */
+		ret = devm_gpio_request_one(&pdev->dev,
+					    machine->gpio_int_mic_en,
+					    GPIOF_OUT_INIT_LOW, "int_mic_en");
+		if (ret) {
+			dev_err(card->dev, "cannot get int_mic_en gpio\n");
+			return ret;
+		}
+		gpio_set_value_cansleep(machine->gpio_int_mic_en, 1);
+	}
+
+	machine->gpio_ext_mic_en = of_get_named_gpio(np,
+						"nvidia,ext-mic-en-gpios", 0);
+	if (machine->gpio_ext_mic_en == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	if (gpio_is_valid(machine->gpio_ext_mic_en)) {
+		/* Enable ext mic; enable signal is active-low */
+		ret = devm_gpio_request_one(&pdev->dev,
+					    machine->gpio_ext_mic_en,
+					    GPIOF_OUT_INIT_LOW, "ext_mic_en");
+		if (ret) {
+			dev_err(card->dev, "cannot get ext_mic_en gpio\n");
+			return ret;
+		}
+		gpio_set_value_cansleep(machine->gpio_ext_mic_en, 0);
+	}
 
 	ret = snd_soc_of_parse_card_name(card, "nvidia,model");
 	if (ret)
 		goto err;
 
+	ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing");
+	if (ret)
+		goto err;
 
-	tegra_wm8994_dai[0].codec_of_node = of_parse_phandle(np,
+	tegra_wm8994_dai.codec_of_node = of_parse_phandle(np,
 						"nvidia,audio-codec", 0);
-	if (!tegra_wm8994_dai[0].codec_of_node) {
+	if (!tegra_wm8994_dai.codec_of_node) {
 		dev_err(&pdev->dev,
 			"Property 'nvidia,audio-codec' missing or invalid\n");
 		ret = -EINVAL;
 		goto err;
 	}
 
-	tegra_wm8994_dai[0].cpu_of_node = of_parse_phandle(np,
+	tegra_wm8994_dai.cpu_of_node = of_parse_phandle(np,
 			"nvidia,i2s-controller", 0);
-	if (!tegra_wm8994_dai[0].cpu_of_node) {
+	if (!tegra_wm8994_dai.cpu_of_node) {
 		dev_err(&pdev->dev,
 			"Property 'nvidia,i2s-controller' missing or invalid\n");
 		ret = -EINVAL;
 		goto err;
 	}
 
-	tegra_wm8994_dai[0].platform_of_node = tegra_wm8994_dai[0].cpu_of_node;
+	tegra_wm8994_dai.platform_of_node = tegra_wm8994_dai.cpu_of_node;
+
 	ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev);
-	if (ret) {
-		dev_err(&pdev->dev, "tegra_asoc_utils_init failed (%d)\n",
-			ret);
+	if (ret)
 		goto err;
-	}
 
 	ret = snd_soc_register_card(card);
 	if (ret) {
@@ -250,9 +335,6 @@ static int tegra_wm8994_driver_probe(struct platform_device *pdev)
 		goto err_fini_utils;
 	}
 
-		pr_info("%s: probed\n", __func__);
-
-
 	return 0;
 
 err_fini_utils:
@@ -265,6 +347,7 @@ static int tegra_wm8994_driver_remove(struct platform_device *pdev)
 {
 	struct snd_soc_card *card = platform_get_drvdata(pdev);
 	struct tegra_wm8994 *machine = snd_soc_card_get_drvdata(card);
+
 	snd_soc_unregister_card(card);
 
 	tegra_asoc_utils_fini(&machine->util_data);
@@ -280,17 +363,15 @@ static const struct of_device_id tegra_wm8994_of_match[] = {
 static struct platform_driver tegra_wm8994_driver = {
 	.driver = {
 		.name = DRV_NAME,
-		.owner = THIS_MODULE,
 		.pm = &snd_soc_pm_ops,
 		.of_match_table = tegra_wm8994_of_match,
 	},
 	.probe = tegra_wm8994_driver_probe,
 	.remove = tegra_wm8994_driver_remove,
 };
-
 module_platform_driver(tegra_wm8994_driver);
 
-MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_AUTHOR("Sergey Larin <cerg2010cerg2010@mail.ru>");
 MODULE_DESCRIPTION("Tegra+WM8994 machine ASoC driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("platform:" DRV_NAME);
-- 
2.22.0