64035ac463
Prepare for better device categorization by moving everything to testing subdir first. [skip-ci]: chicken-egg problem: passing pmaports CI depends on pmbootstrap MR depends on this MR Related: postmarketos#16
459 lines
13 KiB
Diff
459 lines
13 KiB
Diff
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
|
|
|