mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-24 10:11:26 +03:00
Tegra platforms support multiple external codecs which in some cases require initial setup or runtime setup. Since common machine driver is used, it uses DAI link names passed from DT to apply specific settings. Use the same logic for Tegra audio graph card driver as well. In doing so separate out codec setup part and move the same to tegra_codec.c source file. This is reference in current downstream machine driver and future Tegra audio graph card driver. Going ahead alternate solution need to be identified to avoid dependency on DAI link names. Upstream generic audio graph driver does not support such method and passing DAI link names from DT won't be entertained in upstream. Bug 200692799 Change-Id: I40d134e89405fd8bb2d9aebfe2bce82816b545bd Signed-off-by: Sameer Pujar <spujar@nvidia.com>
401 lines
10 KiB
C
401 lines
10 KiB
C
/*
|
|
* tegra_machine_driver_mobile.c - Tegra ASoC Machine driver for mobile
|
|
*
|
|
* Copyright (c) 2017-2021 NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_platform.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include "tegra210_ahub.h"
|
|
#include "tegra_asoc_machine.h"
|
|
#include "tegra_codecs.h"
|
|
|
|
#define DRV_NAME "tegra-asoc:"
|
|
|
|
static const char * const tegra_machine_srate_text[] = {
|
|
"None",
|
|
"8kHz",
|
|
"16kHz",
|
|
"44kHz",
|
|
"48kHz",
|
|
"11kHz",
|
|
"22kHz",
|
|
"24kHz",
|
|
"32kHz",
|
|
"88kHz",
|
|
"96kHz",
|
|
"176kHz",
|
|
"192kHz",
|
|
};
|
|
|
|
static const char * const tegra_machine_format_text[] = {
|
|
"None",
|
|
"16",
|
|
"32",
|
|
};
|
|
|
|
static const struct soc_enum tegra_machine_codec_rate =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tegra_machine_srate_text),
|
|
tegra_machine_srate_text);
|
|
|
|
static const struct soc_enum tegra_machine_codec_format =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tegra_machine_format_text),
|
|
tegra_machine_format_text);
|
|
|
|
static const int tegra_machine_srate_values[] = {
|
|
0,
|
|
8000,
|
|
16000,
|
|
44100,
|
|
48000,
|
|
11025,
|
|
22050,
|
|
24000,
|
|
32000,
|
|
88200,
|
|
96000,
|
|
176400,
|
|
192000,
|
|
};
|
|
|
|
static int tegra_machine_codec_get_rate(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
|
|
ucontrol->value.integer.value[0] = machine->rate_via_kcontrol;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_codec_put_rate(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
|
|
/* set the rate control flag */
|
|
machine->rate_via_kcontrol = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_codec_get_format(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
|
|
ucontrol->value.integer.value[0] = machine->fmt_via_kcontrol;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_codec_put_format(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
|
|
/* set the format control flag */
|
|
machine->fmt_via_kcontrol = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_dai_init(struct snd_soc_pcm_runtime *runtime,
|
|
unsigned int rate, unsigned int channels,
|
|
u64 formats)
|
|
{
|
|
unsigned int mask = (1 << channels) - 1;
|
|
struct snd_soc_card *card = runtime->card;
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
struct snd_soc_pcm_stream *dai_params;
|
|
unsigned int aud_mclk, srate;
|
|
u64 format_k, fmt;
|
|
int err;
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
srate = (machine->rate_via_kcontrol) ?
|
|
tegra_machine_srate_values[machine->rate_via_kcontrol] :
|
|
rate;
|
|
format_k = (machine->fmt_via_kcontrol == 2) ?
|
|
(1ULL << SNDRV_PCM_FORMAT_S32_LE) : formats;
|
|
|
|
err = tegra_asoc_utils_set_tegra210_rate(&machine->audio_clock, srate);
|
|
if (err < 0) {
|
|
dev_err(card->dev, "Can't configure clocks\n");
|
|
return err;
|
|
}
|
|
|
|
aud_mclk = machine->audio_clock.set_mclk;
|
|
|
|
pr_debug("pll_a_out0 = %u Hz, aud_mclk = %u Hz, sample rate = %u Hz\n",
|
|
machine->audio_clock.set_pll_out, aud_mclk, srate);
|
|
|
|
list_for_each_entry(rtd, &card->rtd_list, list) {
|
|
if (!rtd->dai_link->params)
|
|
continue;
|
|
dai_params = (struct snd_soc_pcm_stream *)rtd->dai_link->params;
|
|
dai_params->rate_min = srate;
|
|
dai_params->channels_min = channels;
|
|
dai_params->formats = format_k;
|
|
|
|
fmt = rtd->dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
|
/* set TDM slot mask */
|
|
if (fmt == SND_SOC_DAIFMT_DSP_A ||
|
|
fmt == SND_SOC_DAIFMT_DSP_B) {
|
|
err = snd_soc_dai_set_tdm_slot(
|
|
rtd->dais[0], mask,
|
|
mask, 0, 0);
|
|
if (err < 0) {
|
|
dev_err(card->dev,
|
|
"%s cpu DAI slot mask not set\n",
|
|
rtd->dais[0]->name);
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return tegra_codecs_runtime_setup(card, srate, channels, aud_mclk);
|
|
}
|
|
|
|
static int tegra_machine_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_card *card = rtd->card;
|
|
int err;
|
|
|
|
err = tegra_machine_dai_init(rtd, params_rate(params),
|
|
params_channels(params),
|
|
1ULL << params_format(params));
|
|
if (err < 0) {
|
|
dev_err(card->dev, "Failed dai init\n");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_pcm_startup(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card);
|
|
|
|
tegra_asoc_utils_clk_enable(&machine->audio_clock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_machine_pcm_shutdown(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card);
|
|
|
|
tegra_asoc_utils_clk_disable(&machine->audio_clock);
|
|
}
|
|
|
|
static int tegra_machine_compr_startup(struct snd_compr_stream *cstream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
struct snd_soc_card *card = rtd->card;
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
|
|
tegra_asoc_utils_clk_enable(&machine->audio_clock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_machine_compr_shutdown(struct snd_compr_stream *cstream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
struct snd_soc_card *card = rtd->card;
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
|
|
tegra_asoc_utils_clk_disable(&machine->audio_clock);
|
|
}
|
|
|
|
static int tegra_machine_compr_set_params(struct snd_compr_stream *cstream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
|
|
struct snd_soc_card *card = rtd->card;
|
|
struct snd_codec codec_params;
|
|
int err;
|
|
|
|
if (cstream->ops && cstream->ops->get_params) {
|
|
err = cstream->ops->get_params(cstream, &codec_params);
|
|
if (err < 0) {
|
|
dev_err(card->dev, "Failed to get compr params\n");
|
|
return err;
|
|
}
|
|
} else {
|
|
dev_err(card->dev, "compr ops not set\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = tegra_machine_dai_init(rtd, codec_params.sample_rate,
|
|
codec_params.ch_out,
|
|
SNDRV_PCM_FMTBIT_S16_LE);
|
|
if (err < 0) {
|
|
dev_err(card->dev, "Failed dai init\n");
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_ops tegra_machine_pcm_ops = {
|
|
.hw_params = tegra_machine_pcm_hw_params,
|
|
.startup = tegra_machine_pcm_startup,
|
|
.shutdown = tegra_machine_pcm_shutdown,
|
|
};
|
|
|
|
static struct snd_soc_compr_ops tegra_machine_compr_ops = {
|
|
.set_params = tegra_machine_compr_set_params,
|
|
.startup = tegra_machine_compr_startup,
|
|
.shutdown = tegra_machine_compr_shutdown,
|
|
};
|
|
|
|
static int add_dai_links(struct snd_soc_card *card)
|
|
{
|
|
int ret;
|
|
|
|
ret = parse_card_info(card, &tegra_machine_pcm_ops,
|
|
&tegra_machine_compr_ops);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = tegra_codecs_init(card);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new tegra_machine_controls[] = {
|
|
SOC_ENUM_EXT("codec-x rate", tegra_machine_codec_rate,
|
|
tegra_machine_codec_get_rate, tegra_machine_codec_put_rate),
|
|
SOC_ENUM_EXT("codec-x format", tegra_machine_codec_format,
|
|
tegra_machine_codec_get_format, tegra_machine_codec_put_format),
|
|
};
|
|
|
|
static struct snd_soc_card snd_soc_tegra_card = {
|
|
.owner = THIS_MODULE,
|
|
.controls = tegra_machine_controls,
|
|
.num_controls = ARRAY_SIZE(tegra_machine_controls),
|
|
.fully_routed = true,
|
|
};
|
|
|
|
/* structure to match device tree node */
|
|
static const struct of_device_id tegra_machine_of_match[] = {
|
|
{ .compatible = "nvidia,tegra-audio-t186ref-mobile-rt565x" },
|
|
{ .compatible = "nvidia,tegra-audio-t210ref-mobile-rt565x" },
|
|
{},
|
|
};
|
|
|
|
static int tegra_machine_driver_probe(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card = &snd_soc_tegra_card;
|
|
struct tegra_machine *machine;
|
|
int ret = 0;
|
|
|
|
machine = devm_kzalloc(&pdev->dev, sizeof(*machine), GFP_KERNEL);
|
|
if (!machine)
|
|
return -ENOMEM;
|
|
|
|
machine->asoc = devm_kzalloc(&pdev->dev, sizeof(*machine->asoc),
|
|
GFP_KERNEL);
|
|
if (!machine->asoc)
|
|
return -ENOMEM;
|
|
|
|
card->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, card);
|
|
snd_soc_card_set_drvdata(card, machine);
|
|
|
|
card->dapm.idle_bias_off = true;
|
|
|
|
memset(&machine->audio_clock, 0, sizeof(machine->audio_clock));
|
|
ret = tegra_asoc_utils_init(&machine->audio_clock, &pdev->dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = add_dai_links(card);
|
|
if (ret < 0)
|
|
goto cleanup_asoc;
|
|
|
|
ret = snd_soc_register_card(card);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
|
|
ret);
|
|
goto cleanup_asoc;
|
|
}
|
|
|
|
tegra_machine_add_i2s_codec_controls(card);
|
|
|
|
return 0;
|
|
cleanup_asoc:
|
|
release_asoc_phandles(machine);
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_machine_driver_remove(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
|
|
snd_soc_unregister_card(card);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_PM
|
|
static void tegra_asoc_machine_resume(struct device *dev)
|
|
{
|
|
WARN_ON(snd_soc_resume(dev));
|
|
}
|
|
#else
|
|
#define tegra_asoc_machine_resume NULL
|
|
#endif
|
|
|
|
static const struct dev_pm_ops tegra_asoc_machine_pm_ops = {
|
|
.prepare = snd_soc_suspend,
|
|
.complete = tegra_asoc_machine_resume,
|
|
.poweroff = snd_soc_poweroff,
|
|
};
|
|
|
|
static struct platform_driver tegra_asoc_machine_driver = {
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = &tegra_asoc_machine_pm_ops,
|
|
.of_match_table = tegra_machine_of_match,
|
|
},
|
|
.probe = tegra_machine_driver_probe,
|
|
.remove = tegra_machine_driver_remove,
|
|
};
|
|
module_platform_driver(tegra_asoc_machine_driver);
|
|
|
|
MODULE_AUTHOR("Mohan Kumar <mkumard@nvidia.com>, Sameer Pujar <spujar@nvidia.com>");
|
|
MODULE_DESCRIPTION("Tegra ASoC machine driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:" DRV_NAME);
|
|
MODULE_DEVICE_TABLE(of, tegra_machine_of_match);
|