Files
linux-nv-oot/sound/soc/tegra/tegra_machine_driver.c
Sameer Pujar 1d0f4c4ffc Revert "ASoC: tegra: use upstreamed AHUB drivers"
This reverts commit 363a679975.
This is done to unblock gvs intermittency of audio test and
kernel warning test failure.

Bug 4508166

Change-Id: I99e2b879c7ba1c7987d859b4ae9fce0ea317e41e
Signed-off-by: Sameer Pujar <spujar@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3082636
Reviewed-by: Shubham Chandra <shubhamc@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
Tested-by: Shubham Chandra <shubhamc@nvidia.com>
2024-02-23 08:27:14 -08:00

423 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2023 NVIDIA CORPORATION. All rights reserved.
*
* Tegra ASoC Machine driver
*/
#include <nvidia/conftest.h>
#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, sample_size;
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) : (1 << formats);
switch (formats) {
case SNDRV_PCM_FORMAT_S8:
sample_size = 8;
break;
case SNDRV_PCM_FORMAT_S16_LE:
sample_size = 16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
/*
* I2S bit clock is derived from PLLA_OUT0 and size of
* 24 bits results in fractional value and the clock
* is not accurate with this. To have integer clock
* division below is used. It means there are additional
* bit clocks (8 cycles) which are ignored. Codec picks
* up data for other channel when LRCK signal toggles.
*/
case SNDRV_PCM_FORMAT_S32_LE:
sample_size = 32;
break;
default:
pr_err("Wrong format!\n");
return -EINVAL;
}
err = tegra_asoc_utils_set_tegra210_rate(&machine->audio_clock, srate,
channels, sample_size);
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 defined(NV_SND_SOC_DAI_LINK_STRUCT_HAS_C2C_PARAMS_ARG) /* Linux v6.4 */
if (!rtd->dai_link->c2c_params)
continue;
dai_params = (struct snd_soc_pcm_stream *)rtd->dai_link->c2c_params;
#else
if (!rtd->dai_link->params)
continue;
dai_params = (struct snd_soc_pcm_stream *)rtd->dai_link->params;
#endif
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),
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_FORMAT_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,
.driver_name = "tegra-ape",
};
/* structure to match device tree node */
static const struct of_device_id tegra_machine_of_match[] = {
{ .compatible = "nvidia,tegra186-ape" },
{ .compatible = "nvidia,tegra210-ape" },
{},
};
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);