From 7e04e693c17e7adccd232bf8a0706d683875e59a Mon Sep 17 00:00:00 2001 From: Sameer Pujar Date: Thu, 18 Jan 2024 12:31:41 +0000 Subject: [PATCH] NVIDIA: SAUCE: ASoC: tegra: support few external codecs BugLink: https://bugs.launchpad.net/bugs/2072591 Few external audio cards are supported out of the box. DT bindings provide specific DAI link names for an audio card, which are then parsed in driver to apply specific configuration. However this solution is rejected in upstream, because the generic machine driver is not supposed to include codec headers for macros which are used in set_sysclk() and set_pll() callbacks. Couple of attempts were previously made to fix this, but these have been rejected as well: 1. https://lore.kernel.org/all/1614276364-13655-1-git-send-email-spujar@nvidia.com/ 2. https://lore.kernel.org/lkml/1646912477-3160-1-git-send-email-spujar@nvidia.com/ In summary, the DT maintainers don't want to have another method to provide clock relationship for codec sysclk. Suggestion is to re-use existing standard clock bindings. The problem with standard bindings is fixes will be needed in codec driver and it is not clear who is going to own these changes. Unfortunately we need to maintain this change in downstream until we have a standard solution. DT binding reference: /* * "link-name" is used to apply specific codec config. * Driver parses this and applies necessary config at * runtime. */ codec_port { endpoint { remote-endpoint = <&cpu_ep>; mclk-fs = <256>; link-name = "rt5640-codec-sysclk-bclk1"; }; }; http://nvbugs/3585083 http://nvbugs/4451662 Signed-off-by: Sameer Pujar Reviewed-by: Mohan kumar Reviewed-by: Sharad Gupta Signed-off-by: Laxman Dewangan Acked-by: Jacob Martin Acked-by: Noah Wager Signed-off-by: Noah Wager --- sound/soc/generic/audio-graph-card.c | 6 + sound/soc/tegra/Makefile | 3 +- sound/soc/tegra/tegra_audio_graph_card.c | 15 +- sound/soc/tegra/tegra_codecs.c | 324 +++++++++++++++++++++++ sound/soc/tegra/tegra_codecs.h | 18 ++ 5 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 sound/soc/tegra/tegra_codecs.c create mode 100644 sound/soc/tegra/tegra_codecs.h diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 04dd82f67096..e85c52b7eb5c 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -250,6 +250,9 @@ static int graph_dai_link_of_dpcm(struct simple_util_priv *priv, ret = graph_link_init(priv, cpu_ep, codec_ep, li, dai_name); + if (ep && !of_property_read_string(ep, "link-name", &dai_link->name)) + dev_dbg(dev, "(%pOF) DAI link name = %s\n", ep, dai_link->name); + li->link++; return ret; @@ -294,6 +297,9 @@ static int graph_dai_link_of(struct simple_util_priv *priv, if (ret < 0) return ret; + if (codec_ep && !of_property_read_string(codec_ep, "link-name", &dai_link->name)) + dev_dbg(dev, "(%pOF) DAI link name = %s\n", codec_ep, dai_link->name); + li->link++; return 0; diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index b723c78e665d..2ea40b7d9ccd 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -44,7 +44,8 @@ obj-$(CONFIG_SND_SOC_TEGRA210_OPE) += snd-soc-tegra210-ope.o # Tegra machine Support snd-soc-tegra-wm8903-objs := tegra_wm8903.o snd-soc-tegra-machine-objs := tegra_asoc_machine.o -snd-soc-tegra-audio-graph-card-objs := tegra_audio_graph_card.o +snd-soc-tegra-audio-graph-card-objs := tegra_audio_graph_card.o \ + tegra_codecs.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o obj-$(CONFIG_SND_SOC_TEGRA_MACHINE_DRV) += snd-soc-tegra-machine.o diff --git a/sound/soc/tegra/tegra_audio_graph_card.c b/sound/soc/tegra/tegra_audio_graph_card.c index bc594766a07d..ac9712b90091 100644 --- a/sound/soc/tegra/tegra_audio_graph_card.c +++ b/sound/soc/tegra/tegra_audio_graph_card.c @@ -13,6 +13,8 @@ #include #include +#include "tegra_codecs.h" + #define MAX_PLLA_OUT0_DIV 128 #define simple_to_tegra_priv(simple) \ @@ -194,7 +196,11 @@ static int tegra_audio_graph_hw_params(struct snd_pcm_substream *substream, } } - return simple_util_hw_params(substream, params); + err = simple_util_hw_params(substream, params); + if (err < 0) + return err; + + return tegra_codecs_runtime_setup(rtd, params); } static const struct snd_soc_ops tegra_audio_graph_ops = { @@ -207,6 +213,7 @@ static int tegra_audio_graph_card_probe(struct snd_soc_card *card) { struct simple_util_priv *simple = snd_soc_card_get_drvdata(card); struct tegra_audio_priv *priv = simple_to_tegra_priv(simple); + int ret; priv->clk_plla = devm_clk_get(card->dev, "pll_a"); if (IS_ERR(priv->clk_plla)) { @@ -220,7 +227,11 @@ static int tegra_audio_graph_card_probe(struct snd_soc_card *card) return PTR_ERR(priv->clk_plla_out0); } - return graph_util_card_probe(card); + ret = graph_util_card_probe(card); + if (ret < 0) + return ret; + + return tegra_codecs_init(card); } static struct snd_soc_dai_driver tegra_dummy_dai = { diff --git a/sound/soc/tegra/tegra_codecs.c b/sound/soc/tegra/tegra_codecs.c new file mode 100644 index 000000000000..d7f1383fff11 --- /dev/null +++ b/sound/soc/tegra/tegra_codecs.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// tegra_codecs.c - External audio codec setup + +#include +#include +#include +#include +#include + +#include "../../../sound/soc/codecs/rt5640.h" +#include "../../../sound/soc/codecs/rt5659.h" +#include "../../../sound/soc/codecs/sgtl5000.h" +#include "tegra_codecs.h" + +/* + * The order of the following definitions should align with + * the 'snd_jack_types' enum as defined in include/sound/jack.h. + */ +static const char * const tegra_machine_jack_state_text[] = { + "None", + "HP", + "MIC", + "HS", +}; + +static const struct soc_enum tegra_machine_jack_state = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tegra_machine_jack_state_text), + tegra_machine_jack_state_text); + +static int tegra_machine_codec_get_jack_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_jack *jack = kcontrol->private_data; + + ucontrol->value.integer.value[0] = jack->status; + + return 0; +} + +static int tegra_machine_codec_put_jack_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_jack *jack = kcontrol->private_data; + + snd_soc_jack_report(jack, ucontrol->value.integer.value[0], + SND_JACK_HEADSET); + + return 0; +} + +static int tegra_machine_add_ctl(struct snd_soc_card *card, + struct snd_kcontrol_new *knew, + void *private_data, + const unsigned char *name) +{ + struct snd_kcontrol *kctl; + int ret; + + kctl = snd_ctl_new1(knew, private_data); + if (!kctl) + return -ENOMEM; + + ret = snd_ctl_add(card->snd_card, kctl); + if (ret < 0) + return ret; + + return 0; +} + +static int tegra_machine_add_codec_jack_control(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack) +{ + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = name, + .info = snd_soc_info_enum_double, + .index = 0, + .get = tegra_machine_codec_get_jack_state, + .put = tegra_machine_codec_put_jack_state, + .private_value = (unsigned long)&tegra_machine_jack_state, + }; + + if (rtd->dais[rtd->dai_link->num_cpus]->component->name_prefix) + snprintf(name, sizeof(name), "%s Jack-state", + rtd->dais[rtd->dai_link->num_cpus]->component->name_prefix); + else + snprintf(name, sizeof(name), "Jack-state"); + + return tegra_machine_add_ctl(card, &knew, jack, name); +} + +static int tegra_machine_rt56xx_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *cmpnt; + struct snd_soc_card *card = rtd->card; + struct snd_soc_jack *jack; + int err; + + cmpnt = rtd->dais[rtd->dai_link->num_cpus]->component; + if (!cmpnt->driver->set_jack) + goto dai_init; + + jack = devm_kzalloc(card->dev, sizeof(struct snd_soc_jack), GFP_KERNEL); + if (!jack) + return -ENOMEM; + + err = snd_soc_card_jack_new(card, "Headset Jack", SND_JACK_HEADSET, + jack); + if (err) { + dev_err(card->dev, "Headset Jack creation failed %d\n", err); + return err; + } + + err = tegra_machine_add_codec_jack_control(card, rtd, jack); + if (err) { + dev_err(card->dev, "Failed to add jack control: %d\n", err); + return err; + } + + err = cmpnt->driver->set_jack(cmpnt, jack, NULL); + if (err) { + dev_err(cmpnt->dev, "Failed to set jack: %d\n", err); + return err; + } + + /* single button supporting play/pause */ + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA); + + /* multiple buttons supporting play/pause and volume up/down */ + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_MEDIA); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + snd_soc_dapm_sync(&card->dapm); + + +dai_init: + return simple_util_dai_init(rtd); +} + +static int tegra_machine_fepi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct device *dev = rtd->card->dev; + int err; + + err = snd_soc_dai_set_sysclk(rtd->dais[rtd->dai_link->num_cpus], + SGTL5000_SYSCLK, 12288000, + SND_SOC_CLOCK_IN); + if (err) { + dev_err(dev, "failed to set sgtl5000 sysclk!\n"); + return err; + } + + return simple_util_dai_init(rtd); +} + +static int tegra_machine_respeaker_init(struct snd_soc_pcm_runtime *rtd) +{ + struct device *dev = rtd->card->dev; + int err; + + /* ac108 codec driver hardcodes the freq as 24000000 + * and source as PLL irrespective of args passed through + * this callback + */ + err = snd_soc_dai_set_sysclk(rtd->dais[rtd->dai_link->num_cpus], + 0, 24000000, SND_SOC_CLOCK_IN); + if (err) { + dev_err(dev, "failed to set ac108 sysclk!\n"); + return err; + } + + return simple_util_dai_init(rtd); +} + +static int set_pll_sysclk(struct snd_soc_pcm_runtime *rtd, int pll_src, + int clk_id, unsigned int srate, + unsigned int channels, unsigned int width, + unsigned int aud_mclk) +{ + unsigned int bclk_rate = srate * channels * width; + int err; + + + err = snd_soc_dai_set_pll(rtd->dais[rtd->dai_link->num_cpus], 0, + pll_src, bclk_rate, aud_mclk); + if (err < 0) { + dev_err(rtd->card->dev, "failed to set codec pll\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(rtd->dais[rtd->dai_link->num_cpus], clk_id, + aud_mclk, SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(rtd->card->dev, "dais[%d] clock not set\n", + rtd->dai_link->num_cpus); + return err; + } + + return 0; +} + +static struct snd_soc_pcm_runtime *get_pcm_runtime(struct snd_soc_card *card, + const char *link_name) +{ + struct snd_soc_pcm_runtime *rtd; + + for_each_card_rtds(card, rtd) { + if (!strcmp(rtd->dai_link->name, link_name)) + return rtd; + } + + return NULL; +} + +static struct snd_soc_pcm_runtime *find_rtd(struct snd_soc_pcm_runtime *rtd, + char *link_name) +{ + struct snd_soc_pcm_runtime *vrtd; + + if (rtd->card->component_chaining) { + if (!strcmp(rtd->dai_link->name, link_name)) + return rtd; + } else { + vrtd = get_pcm_runtime(rtd->card, link_name); + if (vrtd) + return vrtd; + } + + return NULL; +} + +int tegra_codecs_runtime_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct simple_util_priv *simple = snd_soc_card_get_drvdata(rtd->card); + unsigned int channels = params_channels(params); + unsigned int srate = params_rate(params); + unsigned int width = params_width(params); + struct snd_soc_pcm_runtime *vrtd; + struct simple_dai_props *props; + unsigned int aud_mclk; + int err; + + + vrtd = find_rtd(rtd, "rt565x-playback"); + if (vrtd) { + props = simple_priv_to_props(simple, vrtd->num); + aud_mclk = props->mclk_fs * srate; + + err = snd_soc_dai_set_sysclk(vrtd->dais[vrtd->dai_link->num_cpus], + RT5659_SCLK_S_MCLK, + aud_mclk, SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "dais[%d] clock not set\n", + vrtd->dai_link->num_cpus); + return err; + } + + } + + vrtd = find_rtd(rtd, "rt5640-playback"); + if (vrtd) { + props = simple_priv_to_props(simple, vrtd->num); + aud_mclk = props->mclk_fs * srate; + + err = snd_soc_dai_set_sysclk(vrtd->dais[vrtd->dai_link->num_cpus], + RT5640_SCLK_S_MCLK, + aud_mclk, SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "dais[%d] clock not set\n", + vrtd->dai_link->num_cpus); + return err; + } + } + + vrtd = find_rtd(rtd, "rt565x-codec-sysclk-bclk1"); + if (vrtd) { + err = set_pll_sysclk(vrtd, RT5659_PLL1_S_BCLK1, RT5659_SCLK_S_PLL1, + srate, channels, width, aud_mclk); + if (err < 0) + return err; + + } + + vrtd = find_rtd(rtd, "rt5640-codec-sysclk-bclk1"); + if (vrtd) { + err = set_pll_sysclk(vrtd, RT5640_PLL1_S_BCLK1, RT5640_SCLK_S_PLL1, + srate, channels, width, aud_mclk); + if (err < 0) + return err; + + } + + return 0; +} + +int tegra_codecs_init(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_links = card->dai_link; + int i; + + if (!dai_links || !card->num_links) + return -EINVAL; + + for (i = 0; i < card->num_links; i++) { + if (strstr(dai_links[i].name, "rt565x-playback") || + strstr(dai_links[i].name, "rt5640-playback") || + strstr(dai_links[i].name, "rt565x-codec-sysclk-bclk1") || + strstr(dai_links[i].name, "rt5640-codec-sysclk-bclk1")) + dai_links[i].init = tegra_machine_rt56xx_init; + else if (strstr(dai_links[i].name, "fe-pi-audio-z-v2")) + dai_links[i].init = tegra_machine_fepi_init; + else if (strstr(dai_links[i].name, "respeaker-4-mic-array")) + dai_links[i].init = tegra_machine_respeaker_init; + } + + return 0; +} diff --git a/sound/soc/tegra/tegra_codecs.h b/sound/soc/tegra/tegra_codecs.h new file mode 100644 index 000000000000..e7de3fde7d5b --- /dev/null +++ b/sound/soc/tegra/tegra_codecs.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ +/* + * tegra_codec.h + * + */ + +#ifndef __TEGRA_CODECS_H__ +#define __TEGRA_CODECS_H__ + +#include + +int tegra_codecs_init(struct snd_soc_card *card); + +int tegra_codecs_runtime_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params); + +#endif