mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-24 18:21:35 +03:00
There is GVS intermittency where few audio tests fail because there is no APE sound card available. The card registration itself does not happen because there is kernel data abort and following dump is seen: [ 36.163223] Unable to handle kernel paging request at virtual address ffffffffffffffc0 [ 36.171180] Mem abort info: [ 36.171182] ESR = 0x0000000096000004 [ 36.171183] EC = 0x25: DABT (current EL), IL = 32 bits [ 36.171186] SET = 0, FnV = 0 [ 36.174218] EA = 0, S1PTW = 0 [ 36.174219] FSC = 0x04: level 0 translation fault [ 36.174221] Data abort info: [ 36.174222] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000 [ 36.174223] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 36.174224] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 36.174226] swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000813b67000 [ 36.174228] [ffffffffffffffc0] pgd=0000000000000000, p4d=0000000000000000 [ 36.174234] Internal error: Oops: 0000000096000004 [#1] PREEMPT SMP This seems to happen because mixer control override driver has the dependency over the sound card and this dependency check is incorrect. The attempt is made to register override controls before the sound card is fully ready. One option is to use 'card->instantiated' flag to add the dependency. However this flag update is protected by card mutext and client_mutex which is local to the core. So this flag check does not appear fully reliable. As a safer option, the override device is made child of sound device. This way the probe order is ensured and override probe happens only after a successful sound probe. This requires a change in machine driver to allow probe() happen for child devices. This may be a concern during usage of upstream machine driver which does not have provision for child device probing. For now unblock the upstream AHUB usage by using this safe option and investigate the best option to make the override driver independent. Bug 4508166 Bug 4451662 TAS-2251 Change-Id: Ib13f0a3a0ac272a0f2325b9d74efbc31128f0991 Signed-off-by: Sameer Pujar <spujar@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3083180 Reviewed-by: Mohan kumar <mkumard@nvidia.com> Reviewed-by: Sharad Gupta <sharadg@nvidia.com> GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
490 lines
13 KiB
C
490 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// SPDX-FileCopyrightText: Copyright (c) 2017-2024 NVIDIA CORPORATION. All rights reserved.
|
|
//
|
|
// tegra_machine_driver.c - 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 "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,
|
|
struct snd_pcm_hw_params *pcm_params)
|
|
{
|
|
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) {
|
|
/* fixup PCM params */
|
|
if (pcm_params && rtd->dai_link->be_hw_params_fixup) {
|
|
struct snd_pcm_hw_params new_params = *pcm_params;
|
|
|
|
err = rtd->dai_link->be_hw_params_fixup(rtd, &new_params);
|
|
if (err) {
|
|
dev_err(rtd->card->dev, "fixup failed for link %s\n",
|
|
rtd->dai_link->name);
|
|
return err;
|
|
}
|
|
|
|
srate = params_rate(&new_params);
|
|
channels = params_channels(&new_params);
|
|
format_k = 1 << params_format(&new_params);
|
|
}
|
|
|
|
#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),
|
|
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,
|
|
NULL);
|
|
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",
|
|
};
|
|
|
|
static struct snd_soc_dai_driver tegra_dummy_dai = {
|
|
.name = "tegra-snd-dummy-dai",
|
|
.playback = {
|
|
.stream_name = "Dummy Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 32,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
.capture = {
|
|
.stream_name = "Dummy Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 32,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
},
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget tegra_dummy_widgets[] = {
|
|
SND_SOC_DAPM_MIC("Dummy MIC", NULL),
|
|
SND_SOC_DAPM_SPK("Dummy SPK", NULL),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route tegra_dummy_routes[] = {
|
|
{"Dummy SPK", NULL, "Dummy Playback"},
|
|
{"Dummy Capture", NULL, "Dummy MIC"},
|
|
};
|
|
|
|
static const struct snd_soc_component_driver tegra_dummy_component = {
|
|
.dapm_widgets = tegra_dummy_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(tegra_dummy_widgets),
|
|
.dapm_routes = tegra_dummy_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(tegra_dummy_routes),
|
|
.endianness = 1,
|
|
};
|
|
|
|
/* 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 = devm_snd_soc_register_component(&pdev->dev, &tegra_dummy_component,
|
|
&tegra_dummy_dai, 1);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Tegra dummy component registration fails\n");
|
|
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);
|
|
|
|
of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
|
|
|
|
dev_info(&pdev->dev, "Registered legacy APE sound card\n");
|
|
|
|
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);
|