mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
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>
528 lines
15 KiB
C
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");
|