Files
linux-nv-oot/sound/soc/tegra/tegra_mixer_control.c
Sameer Pujar b0b6f09a6d Revert "Revert "ASoC: tegra: use upstreamed AHUB drivers""
This reverts commit 1d0f4c4ffc.
This is done to restore original commit of using upstreamed AHUB
drivers as the GVS intermittency issue is now root caused.

Bug 4508166

Change-Id: Iae064f8dae4cf79c639f060c3e24bda43a82b201
Signed-off-by: Sameer Pujar <spujar@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3083109
Reviewed-by: Mohan kumar <mkumard@nvidia.com>
Reviewed-by: Sharad Gupta <sharadg@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
2024-03-07 21:47:09 -08:00

528 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
//
// tegra_mixer_control.c - Override DAI PCM parameters
//
// SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved.
/* The driver is just for audio usecase testing purpose and has few limitations as
* mentioned below, which is fine looking at the overall requirements.
*
* - Client overrides are not possible for AHUB internal modules. This is
* because DAI hw_param() call can carry one configuration and thus both
* XBAR and client setting overrides are not possible.
*
* - No overrides are provided for ADMAIF. The client configuration is
* passed by aplay/arecord applications and DAI hw_param() call carries
* the same.
* - The DAI overrides need to be set every time before any use case and
* these are not persistent. This is because when an use case ends ASoC
* core clears DAI runtime settings. If necessary, it can be improved
* later by storing all DAI settings in the driver.
*/
#include <nvidia/conftest.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/version.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
static int dai_get_rate(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
int reg = mc->reg, i = 0;
struct snd_soc_dai *dai;
for_each_component_dais(component, dai) {
if (i++ != reg)
continue;
ucontrol->value.integer.value[0] = dai->rate;
break;
}
return 0;
}
static int dai_put_rate(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
int value = ucontrol->value.integer.value[0];
int reg = mc->reg, i = 0;
struct snd_soc_dai *dai;
bool change = false;
for_each_component_dais(component, dai) {
if (i++ != reg)
continue;
dai->rate = value;
change = true;
break;
}
return change;
}
static int dai_get_channel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
int reg = mc->reg, i = 0;
struct snd_soc_dai *dai;
for_each_component_dais(component, dai) {
if (i++ != reg)
continue;
ucontrol->value.integer.value[0] = dai->channels;
break;
}
return 0;
}
static int dai_put_channel(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
int value = ucontrol->value.integer.value[0];
int reg = mc->reg, i = 0;
struct snd_soc_dai *dai;
bool change = false;
for_each_component_dais(component, dai) {
if (i++ != reg)
continue;
dai->channels = value;
change = true;
break;
}
return change;
}
static int dai_get_bits(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
int reg = mc->reg, i = 0;
struct snd_soc_dai *dai;
for_each_component_dais(component, dai) {
if (i++ != reg)
continue;
ucontrol->value.integer.value[0] = dai->sample_bits;
break;
}
return 0;
}
static int dai_put_bits(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
int value = ucontrol->value.integer.value[0];
int reg = mc->reg, i = 0;
struct snd_soc_dai *dai;
bool change = false;
for_each_component_dais(component, dai) {
if (i++ != reg)
continue;
dai->sample_bits = value;
change = true;
break;
}
return change;
}
#define PCM_PARAMS_CONTROLS(reg, name_rate, name_ch, name_bits, \
max_rate, max_ch, max_bits) \
\
SOC_SINGLE_EXT(name_rate, reg, 0, max_rate, 0, \
dai_get_rate, dai_put_rate), \
SOC_SINGLE_EXT(name_ch, reg, 0, max_ch, 0, \
dai_get_channel, dai_put_channel), \
SOC_SINGLE_EXT(name_bits, reg, 0, max_bits, 0, \
dai_get_bits, dai_put_bits)
static const struct snd_kcontrol_new tegra210_i2s_ctls[] = {
PCM_PARAMS_CONTROLS(1, "Sample Rate", "Sample Channels",
"Sample Bits", 192000, 16, 32),
};
static const struct snd_kcontrol_new tegra210_dmic_ctls[] = {
PCM_PARAMS_CONTROLS(1, "Sample Rate", "Sample Channels",
"Sample Bits", 48000, 2, 32),
};
static const struct snd_kcontrol_new tegra186_dspk_ctls[] = {
PCM_PARAMS_CONTROLS(1, "Sample Rate", "Sample Channels",
"Sample Bits", 48000, 2, 32),
};
static struct snd_kcontrol_new tegra210_sfc_ctls[] = {
PCM_PARAMS_CONTROLS(0, "Input Sample Rate", "Input Sample Channels",
"Input Sample Bits", 192000, 2, 32),
PCM_PARAMS_CONTROLS(1, "Output Sample Rate", "Output Sample Channels",
"Output Sample Bits", 192000, 2, 32),
};
static struct snd_kcontrol_new tegra210_mvc_ctls[] = {
SOC_SINGLE_EXT("Sample Channels", 1, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("Sample Bits", 1, 0, 32, 0,
dai_get_bits, dai_put_bits),
};
static struct snd_kcontrol_new tegra210_mixer_ctls[] = {
SOC_SINGLE_EXT("RX1 Sample Channels", 0, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX2 Sample Channels", 1, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX3 Sample Channels", 2, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX4 Sample Channels", 3, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX5 Sample Channels", 4, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX6 Sample Channels", 5, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX7 Sample Channels", 6, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX8 Sample Channels", 7, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX9 Sample Channels", 8, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("RX10 Sample Channels", 9, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("TX1 Sample Channels", 10, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("TX2 Sample Channels", 11, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("TX3 Sample Channels", 12, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("TX4 Sample Channels", 13, 0, 8, 0,
dai_get_channel, dai_put_channel),
SOC_SINGLE_EXT("TX5 Sample Channels", 14, 0, 8, 0,
dai_get_channel, dai_put_channel),
};
#define TEGRA210_AMX_OUTPUT_CHANNELS_CTRL(reg) \
SOC_SINGLE_EXT("Output Sample Channels", reg - 1, 0, 16, 0, \
dai_get_channel, dai_put_channel)
#define TEGRA210_AMX_INPUT_CHANNELS_CTRL(reg) \
SOC_SINGLE_EXT("Input" #reg " Sample Channels", reg - 1, 0, \
16, 0, dai_get_channel, dai_put_channel)
static struct snd_kcontrol_new tegra210_amx_ctls[] = {
TEGRA210_AMX_INPUT_CHANNELS_CTRL(1),
TEGRA210_AMX_INPUT_CHANNELS_CTRL(2),
TEGRA210_AMX_INPUT_CHANNELS_CTRL(3),
TEGRA210_AMX_INPUT_CHANNELS_CTRL(4),
TEGRA210_AMX_OUTPUT_CHANNELS_CTRL(5),
};
#define TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(reg) \
SOC_SINGLE_EXT("Output" #reg " Sample Channels", reg, 0, \
16, 0, dai_get_channel, dai_put_channel)
#define TEGRA210_ADX_INPUT_CHANNELS_CTRL(reg) \
SOC_SINGLE_EXT("Input Sample Channels", reg - 1, 0, 16, 0, \
dai_get_channel, dai_put_channel)
static struct snd_kcontrol_new tegra210_adx_ctls[] = {
TEGRA210_ADX_INPUT_CHANNELS_CTRL(1),
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(1),
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(2),
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(3),
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(4),
};
static int dai_is_dummy(struct snd_soc_dai *dai)
{
if (!strcmp(dai->name, "snd-soc-dummy-dai"))
return 1;
return 0;
}
static void tegra_dai_fixup(struct snd_soc_dai *dai,
struct snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_mask *mask = hw_param_mask(params,
SNDRV_PCM_HW_PARAM_FORMAT);
if (dai->rate) {
rate->min =
rate->max = dai->rate;
}
if (dai->channels) {
channels->min =
channels->max = dai->channels;
}
if (dai->sample_bits) {
snd_mask_none(mask);
switch (dai->sample_bits) {
case 8:
snd_mask_set(mask, SNDRV_PCM_FORMAT_S8);
break;
case 16:
snd_mask_set(mask, SNDRV_PCM_FORMAT_S16_LE);
break;
case 24:
snd_mask_set(mask, SNDRV_PCM_FORMAT_S24_LE);
break;
case 32:
snd_mask_set(mask, SNDRV_PCM_FORMAT_S32_LE);
break;
default:
break;
}
}
}
static int tegra_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_soc_dai *dai;
/* Fixup CPU DAI */
#if KERNEL_VERSION(6, 7, 0) > LINUX_VERSION_CODE
dai = asoc_rtd_to_cpu(rtd, 0);
#else
dai = snd_soc_rtd_to_cpu(rtd, 0);
#endif
if (!dai_is_dummy(dai))
tegra_dai_fixup(dai, params);
/* Fixup Codec DAI */
#if KERNEL_VERSION(6, 7, 0) > LINUX_VERSION_CODE
dai = asoc_rtd_to_codec(rtd, 0);
#else
dai = snd_soc_rtd_to_codec(rtd, 0);
#endif
if (!dai_is_dummy(dai))
tegra_dai_fixup(dai, params);
return 0;
}
static int tegra_mixer_control_probe(struct platform_device *pdev)
{
struct platform_device *sound_pdev;
struct device_node *sound_node;
struct device *dev = &pdev->dev;
struct snd_soc_card *card;
struct snd_soc_component *component;
struct snd_soc_pcm_runtime *rtd;
sound_node = of_parse_phandle(dev->of_node, "nvidia,tegra-ape-link", 0);
if (!sound_node) {
dev_err(dev, "Failed to get phandle to sound device\n");
return -EINVAL;
}
/* Device not instantiated yet */
sound_pdev = of_find_device_by_node(sound_node);
if (!sound_pdev)
return -EPROBE_DEFER;
/* Sound card not registered yet */
card = dev_get_drvdata(&sound_pdev->dev);
if (!card)
return -EPROBE_DEFER;
dev_set_drvdata(dev, card);
if (!device_link_add(dev, &sound_pdev->dev, 0)) {
dev_err(dev, "Failed to add device link to sound\n");
return -EINVAL;
}
for_each_card_components(card, component) {
if (!component->name_prefix)
continue;
if (strstr(component->name_prefix, "I2S"))
snd_soc_add_component_controls(component,
tegra210_i2s_ctls,
ARRAY_SIZE(tegra210_i2s_ctls));
else if (strstr(component->name_prefix, "DMIC"))
snd_soc_add_component_controls(component,
tegra210_dmic_ctls,
ARRAY_SIZE(tegra210_dmic_ctls));
else if (strstr(component->name_prefix, "DSPK"))
snd_soc_add_component_controls(component,
tegra186_dspk_ctls,
ARRAY_SIZE(tegra186_dspk_ctls));
else if (strstr(component->name_prefix, "SFC"))
snd_soc_add_component_controls(component,
tegra210_sfc_ctls,
ARRAY_SIZE(tegra210_sfc_ctls));
else if (strstr(component->name_prefix, "MVC"))
snd_soc_add_component_controls(component,
tegra210_mvc_ctls,
ARRAY_SIZE(tegra210_mvc_ctls));
else if (strstr(component->name_prefix, "AMX"))
snd_soc_add_component_controls(component,
tegra210_amx_ctls,
ARRAY_SIZE(tegra210_amx_ctls));
else if (strstr(component->name_prefix, "ADX"))
snd_soc_add_component_controls(component,
tegra210_adx_ctls,
ARRAY_SIZE(tegra210_adx_ctls));
else if (strstr(component->name_prefix, "MIXER"))
snd_soc_add_component_controls(component,
tegra210_mixer_ctls,
ARRAY_SIZE(tegra210_mixer_ctls));
}
/* Fixup callback for BE codec2codec links */
for_each_card_rtds(card, rtd) {
/* Skip FE links for sound card with DPCM routes for AHUB */
if (rtd->card->component_chaining && !rtd->dai_link->no_pcm)
continue;
/* Skip FE links for sound card with codec2codc routes for AHUB */
#if defined(NV_SND_SOC_DAI_LINK_STRUCT_HAS_C2C_PARAMS_ARG) /* Linux v6.4 */
if (!rtd->card->component_chaining && !rtd->dai_link->c2c_params)
#else
if (!rtd->card->component_chaining && !rtd->dai_link->params)
#endif
continue;
rtd->dai_link->be_hw_params_fixup = tegra_hw_params_fixup;
}
return 0;
}
static void tegra_mixer_control_delete(struct device *dev, const char *prefix,
const struct snd_kcontrol_new *controls, int count)
{
struct snd_soc_card *card = dev_get_drvdata(dev);
struct snd_ctl_elem_id id;
int i, err = 0;
for (i = 0; i < count; i++) {
const struct snd_kcontrol_new *control = &controls[i];
snprintf(id.name, sizeof(id.name), "%s %s", prefix,
control->name);
id.numid = 0;
id.iface = control->iface;
id.device = control->device;
id.subdevice = control->subdevice;
id.index = control->index;
err = snd_ctl_remove_id(card->snd_card, &id);
if (err != 0)
dev_err(dev, "Failed to remove control %s: %d\n", id.name, err);
}
}
static int tegra_mixer_control_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct snd_soc_component *comp;
struct snd_soc_card *card = dev_get_drvdata(dev);
for_each_card_components(card, comp) {
if (!comp->name_prefix)
continue;
if (strstr(comp->name_prefix, "I2S"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_i2s_ctls, ARRAY_SIZE(tegra210_i2s_ctls));
else if (strstr(comp->name_prefix, "DMIC"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_dmic_ctls, ARRAY_SIZE(tegra210_dmic_ctls));
else if (strstr(comp->name_prefix, "DSPK"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra186_dspk_ctls, ARRAY_SIZE(tegra186_dspk_ctls));
else if (strstr(comp->name_prefix, "SFC"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_sfc_ctls, ARRAY_SIZE(tegra210_sfc_ctls));
else if (strstr(comp->name_prefix, "MVC"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_mvc_ctls, ARRAY_SIZE(tegra210_mvc_ctls));
else if (strstr(comp->name_prefix, "AMX"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_amx_ctls, ARRAY_SIZE(tegra210_amx_ctls));
else if (strstr(comp->name_prefix, "ADX"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_adx_ctls, ARRAY_SIZE(tegra210_adx_ctls));
else if (strstr(comp->name_prefix, "MIXER"))
tegra_mixer_control_delete(dev, comp->name_prefix,
tegra210_mixer_ctls, ARRAY_SIZE(tegra210_mixer_ctls));
}
return 0;
}
static const struct dev_pm_ops tegra_mixer_control_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
static const struct of_device_id tegra_mixer_control_of_match[] = {
{ .compatible = "nvidia,tegra234-mixer-control" },
{},
};
MODULE_DEVICE_TABLE(of, tegra_mixer_control_of_match);
static struct platform_driver tegra_mixer_control_driver = {
.driver = {
.name = "tegra-mixer-controls",
.of_match_table = tegra_mixer_control_of_match,
.pm = &tegra_mixer_control_pm_ops,
},
.probe = tegra_mixer_control_probe,
.remove = tegra_mixer_control_remove,
};
module_platform_driver(tegra_mixer_control_driver);
MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>");
MODULE_DESCRIPTION("Tegra Mixer Controls");
MODULE_LICENSE("GPL");