mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +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>
515 lines
15 KiB
C
515 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 device *dev = &pdev->dev;
|
|
struct snd_soc_card *card;
|
|
struct snd_soc_component *component;
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
dev_info(dev, "Begin probe of override control device\n");
|
|
|
|
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,
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
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");
|