From c96dc7817d36eedb85d8ac3b8775e4d9803f8467 Mon Sep 17 00:00:00 2001 From: Sergey Larin 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 --- 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 + * Based on driver for wm8994 by: * Author: Stephen Warren - * Copyright (C) 2010-2011 - NVIDIA, Inc. + * Copyright (C) 2010-2012 - NVIDIA, Inc. * * Based on code copyright/by: * @@ -28,29 +30,29 @@ * */ -#include - #include #include #include #include -#include +#include #include +#include #include #include #include -#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 "); +MODULE_AUTHOR("Sergey Larin "); MODULE_DESCRIPTION("Tegra+WM8994 machine ASoC driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" DRV_NAME); -- 2.22.0