Files
linux-nv-oot/sound/soc/tegra/tegra_mixer_control.c
Jon Hunter 52a4903b87 ASoC: tegra: Fix build for Linux v6.13
In Linux v6.13, commit 1bd775da9ba9 ("ASoC: add symmetric_ prefix for
dai->rate/channels/sample_bits") added a 'symmetric_' prefix to some
member of the 'snd_soc_dai' structure. Use conftest to determine if
these structure members have the 'symmetric_' prefix and update the
Tegra Mixer Control driver accordingly.

Bug 4991705

Change-Id: I366595f31932caaa447033c0d0e3b1d2b8dba6e3
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3261697
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Sameer Pujar <spujar@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
2025-07-24 10:19:11 +00:00

598 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// tegra_mixer_control.c - Override DAI PCM parameters
/* 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>
#include "tegra_asoc_machine.h"
#define TEGRA234_MAX_CHANNEL_COUNT 16
#define TEGRA264_MAX_CHANNEL_COUNT 32
struct tegra_mixer_soc_data {
const struct snd_kcontrol_new *i2s_ctls;
const struct snd_kcontrol_new *amx_ctls;
const struct snd_kcontrol_new *adx_ctls;
};
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;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
ucontrol->value.integer.value[0] = dai->symmetric_rate;
#else
ucontrol->value.integer.value[0] = dai->rate;
#endif
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;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
dai->symmetric_rate = value;
#else
dai->rate = value;
#endif
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;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
ucontrol->value.integer.value[0] = dai->symmetric_channels;
#else
ucontrol->value.integer.value[0] = dai->channels;
#endif
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;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
dai->symmetric_channels = value;
#else
dai->channels = value;
#endif
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;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
ucontrol->value.integer.value[0] = dai->symmetric_sample_bits;
#else
ucontrol->value.integer.value[0] = dai->sample_bits;
#endif
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;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
dai->symmetric_sample_bits = value;
#else
dai->sample_bits = value;
#endif
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)
#define TEGRA_I2S_CTLS(chip, channels) \
static const struct snd_kcontrol_new chip ## _i2s_ctls[] = { \
PCM_PARAMS_CONTROLS(1, "Sample Rate", "Sample Channels", \
"Sample Bits", 192000, channels, 32), \
};
TEGRA_I2S_CTLS(tegra210, TEGRA234_MAX_CHANNEL_COUNT)
TEGRA_I2S_CTLS(tegra264, TEGRA264_MAX_CHANNEL_COUNT)
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, channel) \
SOC_SINGLE_EXT("Output Sample Channels", reg - 1, 0, channel, 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)
#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, channel) \
SOC_SINGLE_EXT("Input Sample Channels", reg - 1, 0, channel, 0, \
dai_get_channel, dai_put_channel)
#define TEGRA_AMX_ADX_CTRLS(chip, channels) \
static struct snd_kcontrol_new chip ## _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, channels), \
}; \
\
static struct snd_kcontrol_new chip ## _adx_ctls[] = { \
TEGRA210_ADX_INPUT_CHANNELS_CTRL(1, channels), \
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(1), \
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(2), \
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(3), \
TEGRA210_ADX_OUTPUT_CHANNELS_CTRL(4), \
};
TEGRA_AMX_ADX_CTRLS(tegra210, TEGRA234_MAX_CHANNEL_COUNT)
TEGRA_AMX_ADX_CTRLS(tegra264, TEGRA264_MAX_CHANNEL_COUNT)
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);
unsigned int d_rate, d_channels, d_sample_bits;
#if defined(NV_SND_SOC_DAI_STRUCT_HAS_SYMMETRIC_PREFIX) /* Linux v6.13 */
d_rate = dai->symmetric_rate;
d_channels = dai->symmetric_channels;
d_sample_bits = dai->symmetric_sample_bits;
#else
d_rate = dai->rate;
d_channels = dai->channels;
d_sample_bits = dai->sample_bits;
#endif
if (d_rate)
rate->min = rate->max = d_rate;
if (d_channels)
channels->min = channels->max = d_channels;
if (d_sample_bits) {
snd_mask_none(mask);
switch (d_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 defined(NV_SND_SOC_RTD_TO_CODEC_PRESENT) /* Linux 6.7*/
dai = snd_soc_rtd_to_cpu(rtd, 0);
#else
dai = asoc_rtd_to_cpu(rtd, 0);
#endif
if (!dai_is_dummy(dai))
tegra_dai_fixup(dai, params);
/* Fixup Codec DAI */
#if defined(NV_SND_SOC_RTD_TO_CODEC_PRESENT) /* Linux 6.7*/
dai = snd_soc_rtd_to_codec(rtd, 0);
#else
dai = asoc_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 device *dev = &pdev->dev;
struct snd_soc_card *card;
struct snd_soc_component *component;
struct snd_soc_pcm_runtime *rtd;
const struct tegra_mixer_soc_data *soc_data;
dev_info(dev, "Begin probe of override control device\n");
soc_data = of_device_get_match_data(&pdev->dev);
card = dev_get_drvdata(dev->parent);
if (!card) {
dev_err(dev, "Failed to get APE card reference\n");
return -EINVAL;
}
dev_set_drvdata(dev, card);
for_each_card_components(card, component) {
if (!component->name_prefix)
continue;
if (strstr(component->name_prefix, "I2S"))
snd_soc_add_component_controls(component,
soc_data->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,
soc_data->amx_ctls,
ARRAY_SIZE(tegra210_amx_ctls));
else if (strstr(component->name_prefix, "ADX"))
snd_soc_add_component_controls(component,
soc_data->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));
}
tegra_machine_add_i2s_codec_controls(card);
/* 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;
}
dev_info(dev, "Registered override controls for APE sound card\n");
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);
const struct tegra_mixer_soc_data *soc_data;
soc_data = of_device_get_match_data(&pdev->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,
soc_data->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,
soc_data->amx_ctls, ARRAY_SIZE(tegra210_amx_ctls));
else if (strstr(comp->name_prefix, "ADX"))
tegra_mixer_control_delete(dev, comp->name_prefix,
soc_data->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 tegra_mixer_soc_data soc_data_tegra234 = {
.i2s_ctls = tegra210_i2s_ctls,
.amx_ctls = tegra210_amx_ctls,
.adx_ctls = tegra210_adx_ctls,
};
static const struct tegra_mixer_soc_data soc_data_tegra264 = {
.i2s_ctls = tegra264_i2s_ctls,
.amx_ctls = tegra264_amx_ctls,
.adx_ctls = tegra264_adx_ctls,
};
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", .data = &soc_data_tegra234 },
{ .compatible = "nvidia,tegra264-mixer-control", .data = &soc_data_tegra264 },
{},
};
MODULE_DEVICE_TABLE(of, tegra_mixer_control_of_match);
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
static void tegra_mixer_control_remove_wrapper(struct platform_device *pdev)
{
tegra_mixer_control_remove(pdev);
}
#else
static int tegra_mixer_control_remove_wrapper(struct platform_device *pdev)
{
return tegra_mixer_control_remove(pdev);
}
#endif
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_wrapper,
};
module_platform_driver(tegra_mixer_control_driver);
MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>");
MODULE_DESCRIPTION("Tegra Mixer Controls");
MODULE_LICENSE("GPL");