diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 0943ddf0..5830274b 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -20,6 +20,7 @@ snd-soc-tegra186-arad-oot-objs := tegra186_arad.o snd-soc-tegra210-ope-oot-objs := tegra210_ope.o tegra210_peq.o \ tegra210_mbdrc.o snd-soc-tegra-machine-driver-oot-objs := tegra_machine_driver.o +snd-soc-tegra-controls-objs := tegra_mixer_control.o obj-m += snd-soc-tegra-utils-oot.o obj-m += snd-soc-tegra210-dmic-oot.o @@ -38,3 +39,4 @@ obj-m += snd-soc-tegra210-ope-oot.o obj-m += snd-soc-tegra186-arad-oot.o obj-m += snd-soc-tegra186-asrc-oot.o obj-m += snd-soc-tegra-machine-driver-oot.o +obj-m += snd-soc-tegra-controls.o diff --git a/sound/soc/tegra/tegra_mixer_control.c b/sound/soc/tegra/tegra_mixer_control.c new file mode 100644 index 00000000..800686ba --- /dev/null +++ b/sound/soc/tegra/tegra_mixer_control.c @@ -0,0 +1,517 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +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 */ + if (!rtd->dai_link->no_pcm) + 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 "); +MODULE_DESCRIPTION("Tegra Mixer Controls"); +MODULE_LICENSE("GPL");