From 2a68fff8b84e5479ab33ca2c541917f618552528 Mon Sep 17 00:00:00 2001 From: Sameer Pujar Date: Sun, 21 Aug 2022 08:57:17 +0530 Subject: [PATCH] ASoC: tegra: Add driver for DAI PCM overrides Tegra audio uses multiple ASoC components in an audio path. In doing so, each component may require a separate set of DAI PCM configurations. Presently this is facilitated by exposing mixer control overrides from each of the component drivers to override sample rate, channels or bits. Above satisfies our needs. However this type of solution is rejected by upstream maintainers. The suggestion is to not bypass the configurations passed by ASoC framework via hw_params() calls. With kernel OOT model gaining momentum in downstream, we are stuck with partially upstreamed drivers where above mentioned feature is an important missing item. This commit adds a new driver to override DAI PCM parameters. Idea is to use this as an OOT driver and insmod it for our downstream applications or for testing.This comes with few limitations, 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. This driver is intended to be used on both DPCM/DAPM solutions. Bug 3583581 Change-Id: Ia05316a10eb9c298f2a56d2ef3ccaa37c5985ddd Signed-off-by: Sameer Pujar Signed-off-by: Sheetal Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3043569 Reviewed-by: Mohan Kumar D Reviewed-by: Sharad Gupta GVS: Gerrit_Virtual_Submit --- sound/soc/tegra/Makefile | 2 + sound/soc/tegra/tegra_mixer_control.c | 517 ++++++++++++++++++++++++++ 2 files changed, 519 insertions(+) create mode 100644 sound/soc/tegra/tegra_mixer_control.c 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");