diff options
Diffstat (limited to 'sound/soc/tegra')
28 files changed, 4184 insertions, 367 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index cd454871d654..b6712a3d1fa1 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -85,6 +85,27 @@ config SND_SOC_TEGRA210_I2S compatible devices. Say Y or M if you want to add support for Tegra210 I2S module. +config SND_SOC_TEGRA210_OPE + tristate "Tegra210 OPE module" + help + Config to enable the Output Processing Engine (OPE) which includes + Parametric Equalizer (PEQ) and Multi Band Dynamic Range Compressor + (MBDRC) sub blocks for data processing. It can support up to 8 + channels. + Say Y or M if you want to add support for Tegra210 OPE module. + +config SND_SOC_TEGRA186_ASRC + tristate "Tegra186 ASRC module" + help + Config to enable the Asynchronous Sample Rate Converter (ASRC), + which converts the sampling frequency of the input signal from + one frequency to another. It can handle over a wide range of + sample rate ratios (freq_in/freq_out) from 1:24 to 24:1. + ASRC has two modes of operation. One where ratio can be programmed + in SW and the other where it gets information from ratio estimator + module. + Say Y or M if you want to add support for Tegra186 ASRC module. + config SND_SOC_TEGRA186_DSPK tristate "Tegra186 DSPK module" help diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index f19d56690a0d..b723c78e665d 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -11,6 +11,7 @@ snd-soc-tegra30-i2s-objs := tegra30_i2s.o snd-soc-tegra210-ahub-objs := tegra210_ahub.o snd-soc-tegra210-dmic-objs := tegra210_dmic.o snd-soc-tegra210-i2s-objs := tegra210_i2s.o +snd-soc-tegra186-asrc-objs := tegra186_asrc.o snd-soc-tegra186-dspk-objs := tegra186_dspk.o snd-soc-tegra210-admaif-objs := tegra210_admaif.o snd-soc-tegra210-mvc-objs := tegra210_mvc.o @@ -18,6 +19,7 @@ snd-soc-tegra210-sfc-objs := tegra210_sfc.o snd-soc-tegra210-amx-objs := tegra210_amx.o snd-soc-tegra210-adx-objs := tegra210_adx.o snd-soc-tegra210-mixer-objs := tegra210_mixer.o +snd-soc-tegra210-ope-objs := tegra210_ope.o tegra210_mbdrc.o tegra210_peq.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o @@ -29,6 +31,7 @@ obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o obj-$(CONFIG_SND_SOC_TEGRA210_DMIC) += snd-soc-tegra210-dmic.o obj-$(CONFIG_SND_SOC_TEGRA210_AHUB) += snd-soc-tegra210-ahub.o obj-$(CONFIG_SND_SOC_TEGRA210_I2S) += snd-soc-tegra210-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA186_ASRC) += snd-soc-tegra186-asrc.o obj-$(CONFIG_SND_SOC_TEGRA186_DSPK) += snd-soc-tegra186-dspk.o obj-$(CONFIG_SND_SOC_TEGRA210_ADMAIF) += snd-soc-tegra210-admaif.o obj-$(CONFIG_SND_SOC_TEGRA210_MVC) += snd-soc-tegra210-mvc.o @@ -36,6 +39,7 @@ obj-$(CONFIG_SND_SOC_TEGRA210_SFC) += snd-soc-tegra210-sfc.o obj-$(CONFIG_SND_SOC_TEGRA210_AMX) += snd-soc-tegra210-amx.o obj-$(CONFIG_SND_SOC_TEGRA210_ADX) += snd-soc-tegra210-adx.o obj-$(CONFIG_SND_SOC_TEGRA210_MIXER) += snd-soc-tegra210-mixer.o +obj-$(CONFIG_SND_SOC_TEGRA210_OPE) += snd-soc-tegra210-ope.o # Tegra machine Support snd-soc-tegra-wm8903-objs := tegra_wm8903.o diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c new file mode 100644 index 000000000000..9f12faaa609d --- /dev/null +++ b/sound/soc/tegra/tegra186_asrc.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra186_asrc.c - Tegra186 ASRC driver +// +// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra186_asrc.h" +#include "tegra_cif.h" + +#define ASRC_STREAM_SOURCE_SELECT(id) \ + (TEGRA186_ASRC_CFG + ((id) * TEGRA186_ASRC_STREAM_STRIDE)) + +#define ASRC_STREAM_REG(reg, id) ((reg) + ((id) * TEGRA186_ASRC_STREAM_STRIDE)) + +#define ASRC_STREAM_REG_DEFAULTS(id) \ + { ASRC_STREAM_REG(TEGRA186_ASRC_CFG, id), \ + (((id) + 1) << 4) }, \ + { ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id), \ + 0x1 }, \ + { ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id), \ + 0x0 }, \ + { ASRC_STREAM_REG(TEGRA186_ASRC_MUTE_UNMUTE_DURATION, id), \ + 0x400 }, \ + { ASRC_STREAM_REG(TEGRA186_ASRC_RX_CIF_CTRL, id), \ + 0x7500 }, \ + { ASRC_STREAM_REG(TEGRA186_ASRC_TX_CIF_CTRL, id), \ + 0x7500 } + +static const struct reg_default tegra186_asrc_reg_defaults[] = { + ASRC_STREAM_REG_DEFAULTS(0), + ASRC_STREAM_REG_DEFAULTS(1), + ASRC_STREAM_REG_DEFAULTS(2), + ASRC_STREAM_REG_DEFAULTS(3), + ASRC_STREAM_REG_DEFAULTS(4), + ASRC_STREAM_REG_DEFAULTS(5), + + { TEGRA186_ASRC_GLOBAL_ENB, 0}, + { TEGRA186_ASRC_GLOBAL_SOFT_RESET, 0}, + { TEGRA186_ASRC_GLOBAL_CG, 0x1 }, + { TEGRA186_ASRC_GLOBAL_CFG, 0x0 }, + { TEGRA186_ASRC_GLOBAL_SCRATCH_ADDR, 0}, + { TEGRA186_ASRC_GLOBAL_SCRATCH_CFG, 0x0c207980 }, + { TEGRA186_ASRC_RATIO_UPD_RX_CIF_CTRL, 0x00115500 }, + { TEGRA186_ASRC_GLOBAL_INT_MASK, 0x0}, + { TEGRA186_ASRC_GLOBAL_INT_SET, 0x0}, + { TEGRA186_ASRC_GLOBAL_INT_CLEAR, 0x0}, + { TEGRA186_ASRC_GLOBAL_APR_CTRL, 0x0}, + { TEGRA186_ASRC_GLOBAL_APR_CTRL_ACCESS_CTRL, 0x0}, + { TEGRA186_ASRC_GLOBAL_DISARM_APR, 0x0}, + { TEGRA186_ASRC_GLOBAL_DISARM_APR_ACCESS_CTRL, 0x0}, + { TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS, 0x0}, + { TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS_CTRL, 0x0}, + { TEGRA186_ASRC_CYA, 0x0}, +}; + +static void tegra186_asrc_lock_stream(struct tegra186_asrc *asrc, + unsigned int id) +{ + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_LOCK_STATUS, + id), + 1); +} + +static int __maybe_unused tegra186_asrc_runtime_suspend(struct device *dev) +{ + struct tegra186_asrc *asrc = dev_get_drvdata(dev); + + regcache_cache_only(asrc->regmap, true); + regcache_mark_dirty(asrc->regmap); + + return 0; +} + +static int __maybe_unused tegra186_asrc_runtime_resume(struct device *dev) +{ + struct tegra186_asrc *asrc = dev_get_drvdata(dev); + int id; + + regcache_cache_only(asrc->regmap, false); + + /* + * Below sequence is recommended after a runtime PM cycle. + * This otherwise leads to transfer failures. The cache + * sync is done after this to restore other settings. + */ + regmap_write(asrc->regmap, TEGRA186_ASRC_GLOBAL_SCRATCH_ADDR, + TEGRA186_ASRC_ARAM_START_ADDR); + regmap_write(asrc->regmap, TEGRA186_ASRC_GLOBAL_ENB, + TEGRA186_ASRC_GLOBAL_EN); + + regcache_sync(asrc->regmap); + + for (id = 0; id < TEGRA186_ASRC_STREAM_MAX; id++) { + if (asrc->lane[id].ratio_source != + TEGRA186_ASRC_RATIO_SOURCE_SW) + continue; + + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, + id), + asrc->lane[id].int_part); + + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, + id), + asrc->lane[id].frac_part); + + tegra186_asrc_lock_stream(asrc, id); + } + + return 0; +} + +static int tegra186_asrc_set_audio_cif(struct tegra186_asrc *asrc, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + int channels, audio_bits; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = TEGRA_ACIF_BITS_24; + + tegra_set_cif(asrc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra186_asrc_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra186_asrc *asrc = snd_soc_dai_get_drvdata(dai); + int ret, id = dai->id; + + /* Set input threshold */ + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, dai->id), + asrc->lane[id].input_thresh); + + ret = tegra186_asrc_set_audio_cif(asrc, params, + ASRC_STREAM_REG(TEGRA186_ASRC_RX_CIF_CTRL, dai->id)); + if (ret) { + dev_err(dev, "Can't set ASRC RX%d CIF: %d\n", dai->id, ret); + return ret; + } + + return ret; +} + +static int tegra186_asrc_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra186_asrc *asrc = snd_soc_dai_get_drvdata(dai); + int ret, id = dai->id - 7; + + /* Set output threshold */ + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, id), + asrc->lane[id].output_thresh); + + ret = tegra186_asrc_set_audio_cif(asrc, params, + ASRC_STREAM_REG(TEGRA186_ASRC_TX_CIF_CTRL, id)); + if (ret) { + dev_err(dev, "Can't set ASRC TX%d CIF: %d\n", id, ret); + return ret; + } + + /* Set ENABLE_HW_RATIO_COMP */ + if (asrc->lane[id].hwcomp_disable) { + regmap_update_bits(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, id), + TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_MASK, + TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_DISABLE); + } else { + regmap_update_bits(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, id), + TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_MASK, + TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_ENABLE); + + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_COMP, id), + TEGRA186_ASRC_STREAM_DEFAULT_HW_COMP_BIAS_VALUE); + } + + /* Set lock */ + regmap_update_bits(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, id), + 1, asrc->lane[id].ratio_source); + + if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_SW) { + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id), + asrc->lane[id].int_part); + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id), + asrc->lane[id].frac_part); + tegra186_asrc_lock_stream(asrc, id); + } + + return ret; +} + +static int tegra186_asrc_get_ratio_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *asrc_private = + (struct soc_enum *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + + ucontrol->value.enumerated.item[0] = asrc->lane[id].ratio_source; + + return 0; +} + +static int tegra186_asrc_put_ratio_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *asrc_private = + (struct soc_enum *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + bool change = false; + + asrc->lane[id].ratio_source = ucontrol->value.enumerated.item[0]; + + regmap_update_bits_check(asrc->regmap, asrc_private->reg, + TEGRA186_ASRC_STREAM_RATIO_TYPE_MASK, + asrc->lane[id].ratio_source, + &change); + + return change ? 1 : 0; +} + +static int tegra186_asrc_get_ratio_int(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + + regmap_read(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, id), + &asrc->lane[id].int_part); + + ucontrol->value.integer.value[0] = asrc->lane[id].int_part; + + return 0; +} + +static int tegra186_asrc_put_ratio_int(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + bool change = false; + + if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD) { + dev_err(cmpnt->dev, + "Lane %d ratio source is ARAD, invalid SW update\n", + id); + return -EINVAL; + } + + asrc->lane[id].int_part = ucontrol->value.integer.value[0]; + + regmap_update_bits_check(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, + id), + TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, + asrc->lane[id].int_part, &change); + + tegra186_asrc_lock_stream(asrc, id); + + return change ? 1 : 0; +} + +static int tegra186_asrc_get_ratio_frac(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mreg_control *asrc_private = + (struct soc_mreg_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->regbase / TEGRA186_ASRC_STREAM_STRIDE; + + regmap_read(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, id), + &asrc->lane[id].frac_part); + + ucontrol->value.integer.value[0] = asrc->lane[id].frac_part; + + return 0; +} + +static int tegra186_asrc_put_ratio_frac(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mreg_control *asrc_private = + (struct soc_mreg_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->regbase / TEGRA186_ASRC_STREAM_STRIDE; + bool change = false; + + if (asrc->lane[id].ratio_source == TEGRA186_ASRC_RATIO_SOURCE_ARAD) { + dev_err(cmpnt->dev, + "Lane %d ratio source is ARAD, invalid SW update\n", + id); + return -EINVAL; + } + + asrc->lane[id].frac_part = ucontrol->value.integer.value[0]; + + regmap_update_bits_check(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, + id), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + asrc->lane[id].frac_part, &change); + + tegra186_asrc_lock_stream(asrc, id); + + return change ? 1 : 0; +} + +static int tegra186_asrc_get_hwcomp_disable(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + + ucontrol->value.integer.value[0] = asrc->lane[id].hwcomp_disable; + + return 0; +} + +static int tegra186_asrc_put_hwcomp_disable(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + int value = ucontrol->value.integer.value[0]; + + if (value == asrc->lane[id].hwcomp_disable) + return 0; + + asrc->lane[id].hwcomp_disable = value; + + return 1; +} + +static int tegra186_asrc_get_input_threshold(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + + ucontrol->value.integer.value[0] = (asrc->lane[id].input_thresh & 0x3); + + return 0; +} + +static int tegra186_asrc_put_input_threshold(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + int value = (asrc->lane[id].input_thresh & ~(0x3)) | + ucontrol->value.integer.value[0]; + + if (value == asrc->lane[id].input_thresh) + return 0; + + asrc->lane[id].input_thresh = value; + + return 1; +} + +static int tegra186_asrc_get_output_threshold(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + + ucontrol->value.integer.value[0] = (asrc->lane[id].output_thresh & 0x3); + + return 0; +} + +static int tegra186_asrc_put_output_threshold(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *asrc_private = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra186_asrc *asrc = snd_soc_component_get_drvdata(cmpnt); + unsigned int id = asrc_private->reg / TEGRA186_ASRC_STREAM_STRIDE; + int value = (asrc->lane[id].output_thresh & ~(0x3)) | + ucontrol->value.integer.value[0]; + + if (value == asrc->lane[id].output_thresh) + return 0; + + asrc->lane[id].output_thresh = value; + + return 1; +} + +static int tegra186_asrc_widget_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct tegra186_asrc *asrc = dev_get_drvdata(cmpnt->dev); + unsigned int id = + (w->reg - TEGRA186_ASRC_ENABLE) / TEGRA186_ASRC_STREAM_STRIDE; + + regmap_write(asrc->regmap, + ASRC_STREAM_REG(TEGRA186_ASRC_SOFT_RESET, id), + 0x1); + + return 0; +} + +static const struct snd_soc_dai_ops tegra186_asrc_in_dai_ops = { + .hw_params = tegra186_asrc_in_hw_params, +}; + +static const struct snd_soc_dai_ops tegra186_asrc_out_dai_ops = { + .hw_params = tegra186_asrc_out_hw_params, +}; + +#define IN_DAI(id) \ + { \ + .name = "ASRC-RX-CIF"#id, \ + .playback = { \ + .stream_name = "RX" #id "-CIF-Playback",\ + .channels_min = 1, \ + .channels_max = 12, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "RX" #id "-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 12, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra186_asrc_in_dai_ops, \ + } + +#define OUT_DAI(id) \ + { \ + .name = "ASRC-TX-CIF"#id, \ + .playback = { \ + .stream_name = "TX" #id "-CIF-Playback",\ + .channels_min = 1, \ + .channels_max = 12, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "TX" #id "-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 12, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra186_asrc_out_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra186_asrc_dais[] = { + /* ASRC Input */ + IN_DAI(1), + IN_DAI(2), + IN_DAI(3), + IN_DAI(4), + IN_DAI(5), + IN_DAI(6), + IN_DAI(7), + /* ASRC Output */ + OUT_DAI(1), + OUT_DAI(2), + OUT_DAI(3), + OUT_DAI(4), + OUT_DAI(5), + OUT_DAI(6), +}; + +static const struct snd_soc_dapm_widget tegra186_asrc_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX5", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX6", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX7", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT_E("TX1", NULL, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_ENABLE, 0), + TEGRA186_ASRC_STREAM_EN_SHIFT, 0, + tegra186_asrc_widget_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("TX2", NULL, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_ENABLE, 1), + TEGRA186_ASRC_STREAM_EN_SHIFT, 0, + tegra186_asrc_widget_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("TX3", NULL, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_ENABLE, 2), + TEGRA186_ASRC_STREAM_EN_SHIFT, 0, + tegra186_asrc_widget_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("TX4", NULL, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_ENABLE, 3), + TEGRA186_ASRC_STREAM_EN_SHIFT, 0, + tegra186_asrc_widget_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("TX5", NULL, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_ENABLE, 4), + TEGRA186_ASRC_STREAM_EN_SHIFT, 0, + tegra186_asrc_widget_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_AIF_OUT_E("TX6", NULL, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_ENABLE, 5), + TEGRA186_ASRC_STREAM_EN_SHIFT, 0, + tegra186_asrc_widget_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SPK("Depacketizer", NULL), +}; + +#define ASRC_STREAM_ROUTE(id, sname) \ + { "RX" #id " XBAR-" sname, NULL, "RX" #id " XBAR-TX" }, \ + { "RX" #id "-CIF-" sname, NULL, "RX" #id " XBAR-" sname }, \ + { "RX" #id, NULL, "RX" #id "-CIF-" sname }, \ + { "TX" #id, NULL, "RX" #id }, \ + { "TX" #id "-CIF-" sname, NULL, "TX" #id }, \ + { "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \ + { "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname }, + +#define ASRC_ROUTE(id) \ + ASRC_STREAM_ROUTE(id, "Playback") \ + ASRC_STREAM_ROUTE(id, "Capture") + +#define ASRC_RATIO_ROUTE(sname) \ + { "RX7 XBAR-" sname, NULL, "RX7 XBAR-TX" }, \ + { "RX7-CIF-" sname, NULL, "RX7 XBAR-" sname }, \ + { "RX7", NULL, "RX7-CIF-" sname }, \ + { "Depacketizer", NULL, "RX7" }, + +static const struct snd_soc_dapm_route tegra186_asrc_routes[] = { + ASRC_ROUTE(1) + ASRC_ROUTE(2) + ASRC_ROUTE(3) + ASRC_ROUTE(4) + ASRC_ROUTE(5) + ASRC_ROUTE(6) + ASRC_RATIO_ROUTE("Playback") + ASRC_RATIO_ROUTE("Capture") +}; + +static const char * const tegra186_asrc_ratio_source_text[] = { + "ARAD", + "SW", +}; + +#define ASRC_SOURCE_DECL(name, id) \ + static const struct soc_enum name = \ + SOC_ENUM_SINGLE(ASRC_STREAM_SOURCE_SELECT(id), \ + 0, 2, tegra186_asrc_ratio_source_text) + +ASRC_SOURCE_DECL(src_select1, 0); +ASRC_SOURCE_DECL(src_select2, 1); +ASRC_SOURCE_DECL(src_select3, 2); +ASRC_SOURCE_DECL(src_select4, 3); +ASRC_SOURCE_DECL(src_select5, 4); +ASRC_SOURCE_DECL(src_select6, 5); + +#define SOC_SINGLE_EXT_FRAC(xname, xregbase, xmax, xget, xput) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = (xname), \ + .info = snd_soc_info_xr_sx, \ + .get = xget, \ + .put = xput, \ + \ + .private_value = (unsigned long)&(struct soc_mreg_control) \ + { \ + .regbase = xregbase, \ + .regcount = 1, \ + .nbits = 32, \ + .invert = 0, \ + .min = 0, \ + .max = xmax \ + } \ +} + +static const struct snd_kcontrol_new tegra186_asrc_controls[] = { + /* Controls for integer part of ratio */ + SOC_SINGLE_EXT("Ratio1 Integer Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 0), + 0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0, + tegra186_asrc_get_ratio_int, + tegra186_asrc_put_ratio_int), + + SOC_SINGLE_EXT("Ratio2 Integer Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 1), + 0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0, + tegra186_asrc_get_ratio_int, + tegra186_asrc_put_ratio_int), + + SOC_SINGLE_EXT("Ratio3 Integer Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 2), + 0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0, + tegra186_asrc_get_ratio_int, + tegra186_asrc_put_ratio_int), + + SOC_SINGLE_EXT("Ratio4 Integer Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 3), + 0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0, + tegra186_asrc_get_ratio_int, + tegra186_asrc_put_ratio_int), + + SOC_SINGLE_EXT("Ratio5 Integer Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 4), + 0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0, + tegra186_asrc_get_ratio_int, + tegra186_asrc_put_ratio_int), + + SOC_SINGLE_EXT("Ratio6 Integer Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_INT_PART, 5), + 0, TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK, 0, + tegra186_asrc_get_ratio_int, + tegra186_asrc_put_ratio_int), + + /* Controls for fractional part of ratio */ + SOC_SINGLE_EXT_FRAC("Ratio1 Fractional Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 0), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + tegra186_asrc_get_ratio_frac, + tegra186_asrc_put_ratio_frac), + + SOC_SINGLE_EXT_FRAC("Ratio2 Fractional Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 1), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + tegra186_asrc_get_ratio_frac, + tegra186_asrc_put_ratio_frac), + + SOC_SINGLE_EXT_FRAC("Ratio3 Fractional Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 2), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + tegra186_asrc_get_ratio_frac, + tegra186_asrc_put_ratio_frac), + + SOC_SINGLE_EXT_FRAC("Ratio4 Fractional Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 3), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + tegra186_asrc_get_ratio_frac, + tegra186_asrc_put_ratio_frac), + + SOC_SINGLE_EXT_FRAC("Ratio5 Fractional Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 4), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + tegra186_asrc_get_ratio_frac, + tegra186_asrc_put_ratio_frac), + + SOC_SINGLE_EXT_FRAC("Ratio6 Fractional Part", + ASRC_STREAM_REG(TEGRA186_ASRC_RATIO_FRAC_PART, 5), + TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK, + tegra186_asrc_get_ratio_frac, + tegra186_asrc_put_ratio_frac), + + /* Source of ratio provider */ + SOC_ENUM_EXT("Ratio1 Source", src_select1, + tegra186_asrc_get_ratio_source, + tegra186_asrc_put_ratio_source), + + SOC_ENUM_EXT("Ratio2 Source", src_select2, + tegra186_asrc_get_ratio_source, + tegra186_asrc_put_ratio_source), + + SOC_ENUM_EXT("Ratio3 Source", src_select3, + tegra186_asrc_get_ratio_source, + tegra186_asrc_put_ratio_source), + + SOC_ENUM_EXT("Ratio4 Source", src_select4, + tegra186_asrc_get_ratio_source, + tegra186_asrc_put_ratio_source), + + SOC_ENUM_EXT("Ratio5 Source", src_select5, + tegra186_asrc_get_ratio_source, + tegra186_asrc_put_ratio_source), + + SOC_ENUM_EXT("Ratio6 Source", src_select6, + tegra186_asrc_get_ratio_source, + tegra186_asrc_put_ratio_source), + + /* Disable HW managed overflow/underflow issue at input and output */ + SOC_SINGLE_EXT("Stream1 HW Component Disable", + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, 0), 0, 1, 0, + tegra186_asrc_get_hwcomp_disable, + tegra186_asrc_put_hwcomp_disable), + + SOC_SINGLE_EXT("Stream2 HW Component Disable", + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, 1), 0, 1, 0, + tegra186_asrc_get_hwcomp_disable, + tegra186_asrc_put_hwcomp_disable), + + SOC_SINGLE_EXT("Stream3 HW Component Disable", + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, 2), 0, 1, 0, + tegra186_asrc_get_hwcomp_disable, + tegra186_asrc_put_hwcomp_disable), + + SOC_SINGLE_EXT("Stream4 HW Component Disable", + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, 3), 0, 1, 0, + tegra186_asrc_get_hwcomp_disable, + tegra186_asrc_put_hwcomp_disable), + + SOC_SINGLE_EXT("Stream5 HW Component Disable", + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, 4), 0, 1, 0, + tegra186_asrc_get_hwcomp_disable, + tegra186_asrc_put_hwcomp_disable), + + SOC_SINGLE_EXT("Stream6 HW Component Disable", + ASRC_STREAM_REG(TEGRA186_ASRC_CFG, 5), 0, 1, 0, + tegra186_asrc_get_hwcomp_disable, + tegra186_asrc_put_hwcomp_disable), + + /* Input threshold for watermark fields */ + SOC_SINGLE_EXT("Stream1 Input Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 0), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream2 Input Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 1), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream3 Input Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 2), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream4 Input Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 3), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream5 Input Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream6 Input Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + + /* Output threshold for watermark fields */ + SOC_SINGLE_EXT("Stream1 Output Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, 0), 0, 3, 0, + tegra186_asrc_get_output_threshold, + tegra186_asrc_put_output_threshold), + + SOC_SINGLE_EXT("Stream2 Output Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, 1), 0, 3, 0, + tegra186_asrc_get_output_threshold, + tegra186_asrc_put_output_threshold), + + SOC_SINGLE_EXT("Stream3 Output Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, 2), 0, 3, 0, + tegra186_asrc_get_output_threshold, + tegra186_asrc_put_output_threshold), + + SOC_SINGLE_EXT("Stream4 Output Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, 3), 0, 3, 0, + tegra186_asrc_get_output_threshold, + tegra186_asrc_put_output_threshold), + + SOC_SINGLE_EXT("Stream5 Output Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, 4), 0, 3, 0, + tegra186_asrc_get_output_threshold, + tegra186_asrc_put_output_threshold), + + SOC_SINGLE_EXT("Stream6 Output Threshold", + ASRC_STREAM_REG(TEGRA186_ASRC_TX_THRESHOLD, 5), 0, 3, 0, + tegra186_asrc_get_output_threshold, + tegra186_asrc_put_output_threshold), +}; + +static const struct snd_soc_component_driver tegra186_asrc_cmpnt = { + .dapm_widgets = tegra186_asrc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra186_asrc_widgets), + .dapm_routes = tegra186_asrc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra186_asrc_routes), + .controls = tegra186_asrc_controls, + .num_controls = ARRAY_SIZE(tegra186_asrc_controls), +}; + +static bool tegra186_asrc_wr_reg(struct device *dev, unsigned int reg) +{ + if (reg < TEGRA186_ASRC_STREAM_LIMIT) + reg %= TEGRA186_ASRC_STREAM_STRIDE; + + switch (reg) { + case TEGRA186_ASRC_CFG ... TEGRA186_ASRC_RATIO_COMP: + case TEGRA186_ASRC_RX_CIF_CTRL: + case TEGRA186_ASRC_TX_CIF_CTRL: + case TEGRA186_ASRC_ENABLE: + case TEGRA186_ASRC_SOFT_RESET: + case TEGRA186_ASRC_GLOBAL_ENB ... TEGRA186_ASRC_RATIO_UPD_RX_CIF_CTRL: + case TEGRA186_ASRC_GLOBAL_INT_MASK ... TEGRA186_ASRC_GLOBAL_INT_CLEAR: + case TEGRA186_ASRC_GLOBAL_APR_CTRL ... TEGRA186_ASRC_CYA: + return true; + default: + return false; + } +} + +static bool tegra186_asrc_rd_reg(struct device *dev, unsigned int reg) +{ + if (reg < TEGRA186_ASRC_STREAM_LIMIT) + reg %= TEGRA186_ASRC_STREAM_STRIDE; + + if (tegra186_asrc_wr_reg(dev, reg)) + return true; + + switch (reg) { + case TEGRA186_ASRC_RX_STATUS: + case TEGRA186_ASRC_TX_STATUS: + case TEGRA186_ASRC_STATUS ... TEGRA186_ASRC_OUTSAMPLEBUF_CFG: + case TEGRA186_ASRC_RATIO_UPD_RX_STATUS: + case TEGRA186_ASRC_GLOBAL_STATUS ... TEGRA186_ASRC_GLOBAL_INT_STATUS: + case TEGRA186_ASRC_GLOBAL_TRANSFER_ERROR_LOG: + return true; + default: + return false; + } +} + +static bool tegra186_asrc_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg < TEGRA186_ASRC_STREAM_LIMIT) + reg %= TEGRA186_ASRC_STREAM_STRIDE; + + switch (reg) { + case TEGRA186_ASRC_RX_STATUS: + case TEGRA186_ASRC_TX_STATUS: + case TEGRA186_ASRC_SOFT_RESET: + case TEGRA186_ASRC_RATIO_INT_PART: + case TEGRA186_ASRC_RATIO_FRAC_PART: + case TEGRA186_ASRC_STATUS: + case TEGRA186_ASRC_RATIO_LOCK_STATUS: + case TEGRA186_ASRC_RATIO_UPD_RX_STATUS: + case TEGRA186_ASRC_GLOBAL_SOFT_RESET: + case TEGRA186_ASRC_GLOBAL_STATUS: + case TEGRA186_ASRC_GLOBAL_STREAM_ENABLE_STATUS: + case TEGRA186_ASRC_GLOBAL_INT_STATUS: + case TEGRA186_ASRC_GLOBAL_TRANSFER_ERROR_LOG: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra186_asrc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA186_ASRC_CYA, + .writeable_reg = tegra186_asrc_wr_reg, + .readable_reg = tegra186_asrc_rd_reg, + .volatile_reg = tegra186_asrc_volatile_reg, + .reg_defaults = tegra186_asrc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra186_asrc_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id tegra186_asrc_of_match[] = { + { .compatible = "nvidia,tegra186-asrc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra186_asrc_of_match); + +static int tegra186_asrc_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra186_asrc *asrc; + void __iomem *regs; + unsigned int i; + int err; + + asrc = devm_kzalloc(dev, sizeof(*asrc), GFP_KERNEL); + if (!asrc) + return -ENOMEM; + + dev_set_drvdata(dev, asrc); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + asrc->regmap = devm_regmap_init_mmio(dev, regs, + &tegra186_asrc_regmap_config); + if (IS_ERR(asrc->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(asrc->regmap); + } + + regcache_cache_only(asrc->regmap, true); + + regmap_write(asrc->regmap, TEGRA186_ASRC_GLOBAL_CFG, + TEGRA186_ASRC_GLOBAL_CFG_FRAC_32BIT_PRECISION); + + /* Initialize default output srate */ + for (i = 0; i < TEGRA186_ASRC_STREAM_MAX; i++) { + asrc->lane[i].ratio_source = TEGRA186_ASRC_RATIO_SOURCE_SW; + asrc->lane[i].int_part = 1; + asrc->lane[i].frac_part = 0; + asrc->lane[i].hwcomp_disable = 0; + asrc->lane[i].input_thresh = + TEGRA186_ASRC_STREAM_DEFAULT_INPUT_HW_COMP_THRESH_CFG; + asrc->lane[i].output_thresh = + TEGRA186_ASRC_STREAM_DEFAULT_OUTPUT_HW_COMP_THRESH_CFG; + } + + err = devm_snd_soc_register_component(dev, &tegra186_asrc_cmpnt, + tegra186_asrc_dais, + ARRAY_SIZE(tegra186_asrc_dais)); + if (err) { + dev_err(dev, "can't register ASRC component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra186_asrc_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra186_asrc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra186_asrc_runtime_suspend, + tegra186_asrc_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra186_asrc_driver = { + .driver = { + .name = "tegra186-asrc", + .of_match_table = tegra186_asrc_of_match, + .pm = &tegra186_asrc_pm_ops, + }, + .probe = tegra186_asrc_platform_probe, + .remove = tegra186_asrc_platform_remove, +}; +module_platform_driver(tegra186_asrc_driver) + +MODULE_AUTHOR("Junghyun Kim <juskim@nvidia.com>"); +MODULE_DESCRIPTION("Tegra186 ASRC ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra186_asrc.h b/sound/soc/tegra/tegra186_asrc.h new file mode 100644 index 000000000000..094fcc723c02 --- /dev/null +++ b/sound/soc/tegra/tegra186_asrc.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra186_asrc.h - Definitions for Tegra186 ASRC driver + * + * Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA186_ASRC_H__ +#define __TEGRA186_ASRC_H__ + +/* ASRC stream related offset */ +#define TEGRA186_ASRC_CFG 0x0 +#define TEGRA186_ASRC_RATIO_INT_PART 0x4 +#define TEGRA186_ASRC_RATIO_FRAC_PART 0x8 +#define TEGRA186_ASRC_RATIO_LOCK_STATUS 0xc +#define TEGRA186_ASRC_MUTE_UNMUTE_DURATION 0x10 +#define TEGRA186_ASRC_TX_THRESHOLD 0x14 +#define TEGRA186_ASRC_RX_THRESHOLD 0x18 +#define TEGRA186_ASRC_RATIO_COMP 0x1c +#define TEGRA186_ASRC_RX_STATUS 0x20 +#define TEGRA186_ASRC_RX_CIF_CTRL 0x24 +#define TEGRA186_ASRC_TX_STATUS 0x2c +#define TEGRA186_ASRC_TX_CIF_CTRL 0x30 +#define TEGRA186_ASRC_ENABLE 0x38 +#define TEGRA186_ASRC_SOFT_RESET 0x3c +#define TEGRA186_ASRC_STATUS 0x4c +#define TEGRA186_ASRC_STATEBUF_ADDR 0x5c +#define TEGRA186_ASRC_STATEBUF_CFG 0x60 +#define TEGRA186_ASRC_INSAMPLEBUF_ADDR 0x64 +#define TEGRA186_ASRC_INSAMPLEBUF_CFG 0x68 +#define TEGRA186_ASRC_OUTSAMPLEBUF_ADDR 0x6c +#define TEGRA186_ASRC_OUTSAMPLEBUF_CFG 0x70 + +/* ASRC Global registers offset */ +#define TEGRA186_ASRC_GLOBAL_ENB 0x2f4 +#define TEGRA186_ASRC_GLOBAL_SOFT_RESET 0x2f8 +#define TEGRA186_ASRC_GLOBAL_CG 0x2fc +#define TEGRA186_ASRC_GLOBAL_CFG 0x300 +#define TEGRA186_ASRC_GLOBAL_SCRATCH_ADDR 0x304 +#define TEGRA186_ASRC_GLOBAL_SCRATCH_CFG 0x308 +#define TEGRA186_ASRC_RATIO_UPD_RX_CIF_CTRL 0x30c +#define TEGRA186_ASRC_RATIO_UPD_RX_STATUS 0x310 +#define TEGRA186_ASRC_GLOBAL_STATUS 0x314 +#define TEGRA186_ASRC_GLOBAL_STREAM_ENABLE_STATUS 0x318 +#define TEGRA186_ASRC_GLOBAL_INT_STATUS 0x324 +#define TEGRA186_ASRC_GLOBAL_INT_MASK 0x328 +#define TEGRA186_ASRC_GLOBAL_INT_SET 0x32c +#define TEGRA186_ASRC_GLOBAL_INT_CLEAR 0x330 +#define TEGRA186_ASRC_GLOBAL_TRANSFER_ERROR_LOG 0x334 +#define TEGRA186_ASRC_GLOBAL_APR_CTRL 0x1000 +#define TEGRA186_ASRC_GLOBAL_APR_CTRL_ACCESS_CTRL 0x1004 +#define TEGRA186_ASRC_GLOBAL_DISARM_APR 0x1008 +#define TEGRA186_ASRC_GLOBAL_DISARM_APR_ACCESS_CTRL 0x100c +#define TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS 0x1010 +#define TEGRA186_ASRC_GLOBAL_RATIO_WR_ACCESS_CTRL 0x1014 +#define TEGRA186_ASRC_CYA 0x1018 + +#define TEGRA186_ASRC_STREAM_DEFAULT_HW_COMP_BIAS_VALUE 0xaaaa +#define TEGRA186_ASRC_STREAM_DEFAULT_INPUT_HW_COMP_THRESH_CFG 0x00201002 +#define TEGRA186_ASRC_STREAM_DEFAULT_OUTPUT_HW_COMP_THRESH_CFG 0x00201002 + +#define TEGRA186_ASRC_GLOBAL_CFG_FRAC_28BIT_PRECISION 0 +#define TEGRA186_ASRC_GLOBAL_CFG_FRAC_32BIT_PRECISION 1 + +#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT 31 +#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_MASK (1 << TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT) +#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_ENABLE (1 << TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT) +#define TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_DISABLE (0 << TEGRA186_ASRC_STREAM_ENABLE_HW_RATIO_COMP_SHIFT) + +#define TEGRA186_ASRC_STREAM_RATIO_TYPE_SHIFT 0 +#define TEGRA186_ASRC_STREAM_RATIO_TYPE_MASK (1 << TEGRA186_ASRC_STREAM_RATIO_TYPE_SHIFT) + +#define TEGRA186_ASRC_STREAM_EN_SHIFT 0 +#define TEGRA186_ASRC_STREAM_EN (1 << TEGRA186_ASRC_STREAM_EN_SHIFT) +#define TEGRA186_ASRC_GLOBAL_EN_SHIFT 0 +#define TEGRA186_ASRC_GLOBAL_EN (1 << TEGRA186_ASRC_GLOBAL_EN_SHIFT) + +#define TEGRA186_ASRC_STREAM_STATEBUF_CFG_SIZE_SHIFT 0 +#define TEGRA186_ASRC_STREAM_STATEBUF_CFG_SIZE_MASK (0xffff << TEGRA186_ASRC_STREAM_STATEBUF_CFG_SIZE_SHIFT) +#define TEGRA186_ASRC_STREAM_INSAMPLEBUF_CFG_SIZE_SHIFT 0 +#define TEGRA186_ASRC_STREAM_INSAMPLEBUF_CFG_SIZE_MASK (0xffff << TEGRA186_ASRC_STREAM_INSAMPLEBUF_CFG_SIZE_SHIFT) +#define TEGRA186_ASRC_STREAM_OUTSAMPLEBUF_CFG_SIZE_SHIFT 0 +#define TEGRA186_ASRC_STREAM_OUTSAMPLEBUF_CFG_SIZE_MASK (0xffff << TEGRA186_ASRC_STREAM_OUTSAMPLEBUF_CFG_SIZE_SHIFT) + +#define TEGRA186_ASRC_STREAM_RATIO_INT_PART_MASK 0x1f +#define TEGRA186_ASRC_STREAM_RATIO_FRAC_PART_MASK 0xffffffff + +#define TEGRA186_ASRC_STREAM_STRIDE 0x80 +#define TEGRA186_ASRC_STREAM_MAX 0x6 +#define TEGRA186_ASRC_STREAM_LIMIT 0x2f0 + +#define TEGRA186_ASRC_RATIO_SOURCE_ARAD 0x0 +#define TEGRA186_ASRC_RATIO_SOURCE_SW 0x1 + +#define TEGRA186_ASRC_ARAM_START_ADDR 0x3f800000 + +struct tegra186_asrc_lane { + unsigned int int_part; + unsigned int frac_part; + unsigned int ratio_source; + unsigned int hwcomp_disable; + unsigned int input_thresh; + unsigned int output_thresh; +}; + +struct tegra186_asrc { + struct tegra186_asrc_lane lane[TEGRA186_ASRC_STREAM_MAX]; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c index c454a34c15c4..87facfbcdd11 100644 --- a/sound/soc/tegra/tegra20_ac97.c +++ b/sound/soc/tegra/tegra20_ac97.c @@ -239,7 +239,8 @@ static struct snd_soc_dai_driver tegra20_ac97_dai = { }; static const struct snd_soc_component_driver tegra20_ac97_component = { - .name = DRV_NAME, + .name = DRV_NAME, + .legacy_dai_naming = 1, }; static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg) @@ -353,6 +354,7 @@ static int tegra20_ac97_platform_probe(struct platform_device *pdev) } } else { dev_err(&pdev->dev, "no codec-reset GPIO supplied\n"); + ret = -EINVAL; goto err_clk_put; } @@ -360,6 +362,7 @@ static int tegra20_ac97_platform_probe(struct platform_device *pdev) "nvidia,codec-sync-gpio", 0); if (!gpio_is_valid(ac97->sync_gpio)) { dev_err(&pdev->dev, "no codec-sync GPIO supplied\n"); + ret = -EINVAL; goto err_clk_put; } diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c index 69c651274c12..c620ab0c601f 100644 --- a/sound/soc/tegra/tegra20_das.c +++ b/sound/soc/tegra/tegra20_das.c @@ -13,84 +13,119 @@ #include <linux/regmap.h> #include <linux/slab.h> #include <sound/soc.h> -#include "tegra20_das.h" #define DRV_NAME "tegra20-das" -static struct tegra20_das *das; +/* Register TEGRA20_DAS_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_CTRL_SEL 0x00 +#define TEGRA20_DAS_DAP_CTRL_SEL_COUNT 5 +#define TEGRA20_DAS_DAP_CTRL_SEL_STRIDE 4 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 + +/* Values for field TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_SEL_DAC1 0 +#define TEGRA20_DAS_DAP_SEL_DAC2 1 +#define TEGRA20_DAS_DAP_SEL_DAC3 2 +#define TEGRA20_DAS_DAP_SEL_DAP1 16 +#define TEGRA20_DAS_DAP_SEL_DAP2 17 +#define TEGRA20_DAS_DAP_SEL_DAP3 18 +#define TEGRA20_DAS_DAP_SEL_DAP4 19 +#define TEGRA20_DAS_DAP_SEL_DAP5 20 + +/* Register TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL */ +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 -static inline void tegra20_das_write(u32 reg, u32 val) -{ - regmap_write(das->regmap, reg, val); -} - -static inline u32 tegra20_das_read(u32 reg) -{ - u32 val; +/* + * Values for: + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL + */ +#define TEGRA20_DAS_DAC_SEL_DAP1 0 +#define TEGRA20_DAS_DAC_SEL_DAP2 1 +#define TEGRA20_DAS_DAC_SEL_DAP3 2 +#define TEGRA20_DAS_DAC_SEL_DAP4 3 +#define TEGRA20_DAS_DAC_SEL_DAP5 4 - regmap_read(das->regmap, reg, &val); - return val; -} +/* + * Names/IDs of the DACs/DAPs. + */ -int tegra20_das_connect_dap_to_dac(int dap, int dac) -{ - u32 addr; - u32 reg; +#define TEGRA20_DAS_DAP_ID_1 0 +#define TEGRA20_DAS_DAP_ID_2 1 +#define TEGRA20_DAS_DAP_ID_3 2 +#define TEGRA20_DAS_DAP_ID_4 3 +#define TEGRA20_DAS_DAP_ID_5 4 - if (!das) - return -ENODEV; +#define TEGRA20_DAS_DAC_ID_1 0 +#define TEGRA20_DAS_DAC_ID_2 1 +#define TEGRA20_DAS_DAC_ID_3 2 - addr = TEGRA20_DAS_DAP_CTRL_SEL + - (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); - reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; +struct tegra20_das { + struct regmap *regmap; +}; - tegra20_das_write(addr, reg); +/* + * Terminology: + * DAS: Digital audio switch (HW module controlled by this driver) + * DAP: Digital audio port (port/pins on Tegra device) + * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) + * + * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific + * DAC, or another DAP. When DAPs are connected, one must be the master and + * one the slave. Each DAC allows selection of a specific DAP for input, to + * cater for the case where N DAPs are connected to 1 DAC for broadcast + * output. + * + * This driver is dumb; no attempt is made to ensure that a valid routing + * configuration is programmed. + */ - return 0; +static inline void tegra20_das_write(struct tegra20_das *das, u32 reg, u32 val) +{ + regmap_write(das->regmap, reg, val); } -EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dac); -int tegra20_das_connect_dap_to_dap(int dap, int otherdap, int master, - int sdata1rx, int sdata2rx) +static void tegra20_das_connect_dap_to_dac(struct tegra20_das *das, int dap, int dac) { u32 addr; u32 reg; - if (!das) - return -ENODEV; - addr = TEGRA20_DAS_DAP_CTRL_SEL + (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); - reg = (otherdap << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P) | - (!!sdata2rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P) | - (!!sdata1rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P) | - (!!master << TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P); - - tegra20_das_write(addr, reg); + reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; - return 0; + tegra20_das_write(das, addr, reg); } -EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dap); -int tegra20_das_connect_dac_to_dap(int dac, int dap) +static void tegra20_das_connect_dac_to_dap(struct tegra20_das *das, int dac, int dap) { u32 addr; u32 reg; - if (!das) - return -ENODEV; - addr = TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL + (dac * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); reg = dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; - tegra20_das_write(addr, reg); - - return 0; + tegra20_das_write(das, addr, reg); } -EXPORT_SYMBOL_GPL(tegra20_das_connect_dac_to_dap); #define LAST_REG(name) \ (TEGRA20_DAS_##name + \ @@ -120,73 +155,31 @@ static const struct regmap_config tegra20_das_regmap_config = { static int tegra20_das_probe(struct platform_device *pdev) { void __iomem *regs; - int ret = 0; - - if (das) - return -ENODEV; + struct tegra20_das *das; das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); - if (!das) { - ret = -ENOMEM; - goto err; - } - das->dev = &pdev->dev; + if (!das) + return -ENOMEM; regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(regs)) { - ret = PTR_ERR(regs); - goto err; - } + if (IS_ERR(regs)) + return PTR_ERR(regs); das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, &tegra20_das_regmap_config); if (IS_ERR(das->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); - ret = PTR_ERR(das->regmap); - goto err; - } - - ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_1, - TEGRA20_DAS_DAP_SEL_DAC1); - if (ret) { - dev_err(&pdev->dev, "Can't set up DAS DAP connection\n"); - goto err; - } - ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAC_ID_1, - TEGRA20_DAS_DAC_SEL_DAP1); - if (ret) { - dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); - goto err; - } - - ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_3, - TEGRA20_DAS_DAP_SEL_DAC3); - if (ret) { - dev_err(&pdev->dev, "Can't set up DAS DAP connection\n"); - goto err; + return PTR_ERR(das->regmap); } - ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAC_ID_3, - TEGRA20_DAS_DAC_SEL_DAP3); - if (ret) { - dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); - goto err; - } - - platform_set_drvdata(pdev, das); - - return 0; - -err: - das = NULL; - return ret; -} - -static int tegra20_das_remove(struct platform_device *pdev) -{ - if (!das) - return -ENODEV; - das = NULL; + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); + tegra20_das_connect_dac_to_dap(das, TEGRA20_DAS_DAC_ID_1, + TEGRA20_DAS_DAC_SEL_DAP1); + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_3, + TEGRA20_DAS_DAP_SEL_DAC3); + tegra20_das_connect_dac_to_dap(das, TEGRA20_DAS_DAC_ID_3, + TEGRA20_DAS_DAC_SEL_DAP3); return 0; } @@ -198,7 +191,6 @@ static const struct of_device_id tegra20_das_of_match[] = { static struct platform_driver tegra20_das_driver = { .probe = tegra20_das_probe, - .remove = tegra20_das_remove, .driver = { .name = DRV_NAME, .of_match_table = tegra20_das_of_match, diff --git a/sound/soc/tegra/tegra20_das.h b/sound/soc/tegra/tegra20_das.h deleted file mode 100644 index 18e832ded73a..000000000000 --- a/sound/soc/tegra/tegra20_das.h +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * tegra20_das.h - Definitions for Tegra20 DAS driver - * - * Author: Stephen Warren <swarren@nvidia.com> - * Copyright (C) 2010,2012 - NVIDIA, Inc. - */ - -#ifndef __TEGRA20_DAS_H__ -#define __TEGRA20_DAS_H__ - -/* Register TEGRA20_DAS_DAP_CTRL_SEL */ -#define TEGRA20_DAS_DAP_CTRL_SEL 0x00 -#define TEGRA20_DAS_DAP_CTRL_SEL_COUNT 5 -#define TEGRA20_DAS_DAP_CTRL_SEL_STRIDE 4 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 -#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 - -/* Values for field TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ -#define TEGRA20_DAS_DAP_SEL_DAC1 0 -#define TEGRA20_DAS_DAP_SEL_DAC2 1 -#define TEGRA20_DAS_DAP_SEL_DAC3 2 -#define TEGRA20_DAS_DAP_SEL_DAP1 16 -#define TEGRA20_DAS_DAP_SEL_DAP2 17 -#define TEGRA20_DAS_DAP_SEL_DAP3 18 -#define TEGRA20_DAS_DAP_SEL_DAP4 19 -#define TEGRA20_DAS_DAP_SEL_DAP5 20 - -/* Register TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL */ -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 -#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 - -/* - * Values for: - * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL - * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL - * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL - */ -#define TEGRA20_DAS_DAC_SEL_DAP1 0 -#define TEGRA20_DAS_DAC_SEL_DAP2 1 -#define TEGRA20_DAS_DAC_SEL_DAP3 2 -#define TEGRA20_DAS_DAC_SEL_DAP4 3 -#define TEGRA20_DAS_DAC_SEL_DAP5 4 - -/* - * Names/IDs of the DACs/DAPs. - */ - -#define TEGRA20_DAS_DAP_ID_1 0 -#define TEGRA20_DAS_DAP_ID_2 1 -#define TEGRA20_DAS_DAP_ID_3 2 -#define TEGRA20_DAS_DAP_ID_4 3 -#define TEGRA20_DAS_DAP_ID_5 4 - -#define TEGRA20_DAS_DAC_ID_1 0 -#define TEGRA20_DAS_DAC_ID_2 1 -#define TEGRA20_DAS_DAC_ID_3 2 - -struct tegra20_das { - struct device *dev; - struct regmap *regmap; -}; - -/* - * Terminology: - * DAS: Digital audio switch (HW module controlled by this driver) - * DAP: Digital audio port (port/pins on Tegra device) - * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) - * - * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific - * DAC, or another DAP. When DAPs are connected, one must be the master and - * one the slave. Each DAC allows selection of a specific DAP for input, to - * cater for the case where N DAPs are connected to 1 DAC for broadcast - * output. - * - * This driver is dumb; no attempt is made to ensure that a valid routing - * configuration is programmed. - */ - -/* - * Connect a DAP to a DAC - * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* - * dac_sel: DAC to connect to: TEGRA20_DAS_DAP_SEL_DAC* - */ -extern int tegra20_das_connect_dap_to_dac(int dap, int dac); - -/* - * Connect a DAP to another DAP - * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* - * other_dap_sel: DAP to connect to: TEGRA20_DAS_DAP_SEL_DAP* - * master: Is this DAP the master (1) or slave (0) - * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) - * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) - */ -extern int tegra20_das_connect_dap_to_dap(int dap, int otherdap, - int master, int sdata1rx, - int sdata2rx); - -/* - * Connect a DAC's input to a DAP - * (DAC outputs are selected by the DAP) - * dac_id: DAC ID to connect: TEGRA20_DAS_DAC_ID_* - * dap_sel: DAP to receive input from: TEGRA20_DAS_DAC_SEL_DAP* - */ -extern int tegra20_das_connect_dac_to_dap(int dac, int dap); - -#endif diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index 266d2cab9f49..fff0cd6588f5 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -95,11 +95,11 @@ static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai, } mask |= TEGRA20_I2S_CTRL_MASTER_ENABLE; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: val |= TEGRA20_I2S_CTRL_MASTER_ENABLE; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_BC_FC: break; default: return -EINVAL; @@ -262,10 +262,59 @@ static int tegra20_i2s_probe(struct snd_soc_dai *dai) return 0; } +static const unsigned int tegra20_i2s_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000 +}; + +static int tegra20_i2s_filter_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + struct snd_soc_dai *dai = rule->private; + struct tegra20_i2s *i2s = dev_get_drvdata(dai->dev); + struct clk *parent = clk_get_parent(i2s->clk_i2s); + long i, parent_rate, valid_rates = 0; + + parent_rate = clk_get_rate(parent); + if (parent_rate <= 0) { + dev_err(dai->dev, "Can't get parent clock rate: %ld\n", + parent_rate); + return parent_rate ?: -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(tegra20_i2s_rates); i++) { + if (parent_rate % (tegra20_i2s_rates[i] * 128) == 0) + valid_rates |= BIT(i); + } + + /* + * At least one rate must be valid, otherwise the parent clock isn't + * audio PLL. Nothing should be filtered in this case. + */ + if (!valid_rates) + valid_rates = BIT(ARRAY_SIZE(tegra20_i2s_rates)) - 1; + + return snd_interval_list(r, ARRAY_SIZE(tegra20_i2s_rates), + tegra20_i2s_rates, valid_rates); +} + +static int tegra20_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate")) + return 0; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + tegra20_i2s_filter_rates, dai, + SNDRV_PCM_HW_PARAM_RATE, -1); +} + static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { .set_fmt = tegra20_i2s_set_fmt, .hw_params = tegra20_i2s_hw_params, .trigger = tegra20_i2s_trigger, + .startup = tegra20_i2s_startup, }; static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { @@ -289,7 +338,8 @@ static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { }; static const struct snd_soc_component_driver tegra20_i2s_component = { - .name = DRV_NAME, + .name = DRV_NAME, + .legacy_dai_naming = 1, }; static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg) diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 7751575cd6d6..ca7b222e07d0 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -7,12 +7,15 @@ */ #include <linux/clk.h> +#include <linux/delay.h> #include <linux/device.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> +#include <linux/reset.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -22,12 +25,12 @@ #include "tegra20_spdif.h" -#define DRV_NAME "tegra20-spdif" - static __maybe_unused int tegra20_spdif_runtime_suspend(struct device *dev) { struct tegra20_spdif *spdif = dev_get_drvdata(dev); + regcache_cache_only(spdif->regmap, true); + clk_disable_unprepare(spdif->clk_spdif_out); return 0; @@ -38,23 +41,45 @@ static __maybe_unused int tegra20_spdif_runtime_resume(struct device *dev) struct tegra20_spdif *spdif = dev_get_drvdata(dev); int ret; + ret = reset_control_assert(spdif->reset); + if (ret) + return ret; + ret = clk_prepare_enable(spdif->clk_spdif_out); if (ret) { dev_err(dev, "clk_enable failed: %d\n", ret); return ret; } + usleep_range(10, 100); + + ret = reset_control_deassert(spdif->reset); + if (ret) + goto disable_clocks; + + regcache_cache_only(spdif->regmap, false); + regcache_mark_dirty(spdif->regmap); + + ret = regcache_sync(spdif->regmap); + if (ret) + goto disable_clocks; + return 0; + +disable_clocks: + clk_disable_unprepare(spdif->clk_spdif_out); + + return ret; } static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { - struct device *dev = dai->dev; - struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); unsigned int mask = 0, val = 0; int ret, spdifclock; + long rate; mask |= TEGRA20_SPDIF_CTRL_PACK | TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; @@ -69,6 +94,14 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val); + /* + * FIFO trigger level must be bigger than DMA burst or equal to it, + * otherwise data is discarded on overflow. + */ + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_DATA_FIFO_CSR, + TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK, + TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL); + switch (params_rate(params)) { case 32000: spdifclock = 4096000; @@ -97,10 +130,16 @@ static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); if (ret) { - dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); + dev_err(dai->dev, "Can't set SPDIF clock rate: %d\n", ret); return ret; } + rate = clk_get_rate(spdif->clk_spdif_out); + if (rate != spdifclock) + dev_warn_once(dai->dev, + "SPDIF clock rate %d doesn't match requested rate %lu\n", + spdifclock, rate); + return 0; } @@ -118,9 +157,9 @@ static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) } static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) + struct snd_soc_dai *dai) { - struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -140,9 +179,62 @@ static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } +static int tegra20_spdif_filter_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + struct snd_soc_dai *dai = rule->private; + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); + struct clk *parent = clk_get_parent(spdif->clk_spdif_out); + static const unsigned int rates[] = { 32000, 44100, 48000 }; + long i, parent_rate, valid_rates = 0; + + parent_rate = clk_get_rate(parent); + if (parent_rate <= 0) { + dev_err(dai->dev, "Can't get parent clock rate: %ld\n", + parent_rate); + return parent_rate ?: -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(rates); i++) { + if (parent_rate % (rates[i] * 128) == 0) + valid_rates |= BIT(i); + } + + /* + * At least one rate must be valid, otherwise the parent clock isn't + * audio PLL. Nothing should be filtered in this case. + */ + if (!valid_rates) + valid_rates = BIT(ARRAY_SIZE(rates)) - 1; + + return snd_interval_list(r, ARRAY_SIZE(rates), rates, valid_rates); +} + +static int tegra20_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!device_property_read_bool(dai->dev, "nvidia,fixed-parent-rate")) + return 0; + + /* + * SPDIF and I2S share audio PLL. HDMI takes audio packets from SPDIF + * and audio may not work on some TVs if clock rate isn't precise. + * + * PLL rate is controlled by I2S side. Filter out audio rates that + * don't match PLL rate at the start of stream to allow both SPDIF + * and I2S work simultaneously, assuming that PLL rate won't be + * changed later on. + */ + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + tegra20_spdif_filter_rates, dai, + SNDRV_PCM_HW_PARAM_RATE, -1); +} + static int tegra20_spdif_probe(struct snd_soc_dai *dai) { - struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); dai->capture_dma_data = NULL; dai->playback_dma_data = &spdif->playback_dma_data; @@ -151,26 +243,28 @@ static int tegra20_spdif_probe(struct snd_soc_dai *dai) } static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { - .hw_params = tegra20_spdif_hw_params, - .trigger = tegra20_spdif_trigger, + .hw_params = tegra20_spdif_hw_params, + .trigger = tegra20_spdif_trigger, + .startup = tegra20_spdif_startup, }; static struct snd_soc_dai_driver tegra20_spdif_dai = { - .name = DRV_NAME, + .name = "tegra20-spdif", .probe = tegra20_spdif_probe, .playback = { .stream_name = "Playback", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000, + SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &tegra20_spdif_dai_ops, }; static const struct snd_soc_component_driver tegra20_spdif_component = { - .name = DRV_NAME, + .name = "tegra20-spdif", + .legacy_dai_naming = 1, }; static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg) @@ -251,7 +345,7 @@ static const struct regmap_config tegra20_spdif_regmap_config = { static int tegra20_spdif_platform_probe(struct platform_device *pdev) { struct tegra20_spdif *spdif; - struct resource *mem, *dmareq; + struct resource *mem; void __iomem *regs; int ret; @@ -262,89 +356,77 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, spdif); - spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); + spdif->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(spdif->reset)) { + dev_err(&pdev->dev, "Can't retrieve spdif reset\n"); + return PTR_ERR(spdif->reset); + } + + spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "out"); if (IS_ERR(spdif->clk_spdif_out)) { - pr_err("Can't retrieve spdif clock\n"); - ret = PTR_ERR(spdif->clk_spdif_out); - return ret; + dev_err(&pdev->dev, "Could not retrieve spdif clock\n"); + return PTR_ERR(spdif->clk_spdif_out); } regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); if (IS_ERR(regs)) return PTR_ERR(regs); - dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!dmareq) { - dev_err(&pdev->dev, "No DMA resource\n"); - return -ENODEV; - } - spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, - &tegra20_spdif_regmap_config); + &tegra20_spdif_regmap_config); if (IS_ERR(spdif->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); - ret = PTR_ERR(spdif->regmap); - return ret; + return PTR_ERR(spdif->regmap); } spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; spdif->playback_dma_data.maxburst = 4; - spdif->playback_dma_data.slave_id = dmareq->start; - pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; - ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component, - &tegra20_spdif_dai, 1); + ret = devm_snd_soc_register_component(&pdev->dev, + &tegra20_spdif_component, + &tegra20_spdif_dai, 1); if (ret) { dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); - ret = -ENOMEM; - goto err_pm_disable; + return ret; } - ret = tegra_pcm_platform_register(&pdev->dev); + ret = devm_tegra_pcm_platform_register(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); - goto err_unregister_component; + return ret; } return 0; - -err_unregister_component: - snd_soc_unregister_component(&pdev->dev); -err_pm_disable: - pm_runtime_disable(&pdev->dev); - - return ret; -} - -static int tegra20_spdif_platform_remove(struct platform_device *pdev) -{ - tegra_pcm_platform_unregister(&pdev->dev); - snd_soc_unregister_component(&pdev->dev); - - pm_runtime_disable(&pdev->dev); - - return 0; } static const struct dev_pm_ops tegra20_spdif_pm_ops = { SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, tegra20_spdif_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; +static const struct of_device_id tegra20_spdif_of_match[] = { + { .compatible = "nvidia,tegra20-spdif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra20_spdif_of_match); + static struct platform_driver tegra20_spdif_driver = { .driver = { - .name = DRV_NAME, + .name = "tegra20-spdif", .pm = &tegra20_spdif_pm_ops, + .of_match_table = tegra20_spdif_of_match, }, .probe = tegra20_spdif_platform_probe, - .remove = tegra20_spdif_platform_remove, }; - module_platform_driver(tegra20_spdif_driver); MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h index 1973ffc2d5c7..ff4b79e2052f 100644 --- a/sound/soc/tegra/tegra20_spdif.h +++ b/sound/soc/tegra/tegra20_spdif.h @@ -451,6 +451,7 @@ struct tegra20_spdif { struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; struct regmap *regmap; + struct reset_control *reset; }; #endif diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c index 3785cade2d9a..49691d2cce50 100644 --- a/sound/soc/tegra/tegra210_adx.c +++ b/sound/soc/tegra/tegra210_adx.c @@ -191,7 +191,7 @@ static int tegra210_adx_put_byte_map(struct snd_kcontrol *kcontrol, unsigned char *bytes_map = (unsigned char *)&adx->map; int value = ucontrol->value.integer.value[0]; struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value;; + (struct soc_mixer_control *)kcontrol->private_value; if (value == bytes_map[mc->reg]) return 0; diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index 388b815443c7..b38d205b69cc 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -2,7 +2,7 @@ // // tegra210_ahub.c - Tegra210 AHUB driver // -// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. +// Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. #include <linux/clk.h> #include <linux/device.h> @@ -170,6 +170,11 @@ static struct snd_soc_dai_driver tegra210_ahub_dais[] = { DAI(MIXER1 TX3), DAI(MIXER1 TX4), DAI(MIXER1 TX5), + /* XBAR -> OPE -> XBAR */ + DAI(OPE1 RX), + DAI(OPE1 TX), + DAI(OPE2 RX), + DAI(OPE2 TX), }; static struct snd_soc_dai_driver tegra186_ahub_dais[] = { @@ -280,6 +285,23 @@ static struct snd_soc_dai_driver tegra186_ahub_dais[] = { DAI(MIXER1 TX3), DAI(MIXER1 TX4), DAI(MIXER1 TX5), + /* XBAR -> ASRC -> XBAR */ + DAI(ASRC1 RX1), + DAI(ASRC1 TX1), + DAI(ASRC1 RX2), + DAI(ASRC1 TX2), + DAI(ASRC1 RX3), + DAI(ASRC1 TX3), + DAI(ASRC1 RX4), + DAI(ASRC1 TX4), + DAI(ASRC1 RX5), + DAI(ASRC1 TX5), + DAI(ASRC1 RX6), + DAI(ASRC1 TX6), + DAI(ASRC1 RX7), + /* XBAR -> OPE -> XBAR */ + DAI(OPE1 RX), + DAI(OPE1 TX), }; static const char * const tegra210_ahub_mux_texts[] = { @@ -323,6 +345,8 @@ static const char * const tegra210_ahub_mux_texts[] = { "MIXER1 TX3", "MIXER1 TX4", "MIXER1 TX5", + "OPE1", + "OPE2", }; static const char * const tegra186_ahub_mux_texts[] = { @@ -388,6 +412,13 @@ static const char * const tegra186_ahub_mux_texts[] = { "MIXER1 TX3", "MIXER1 TX4", "MIXER1 TX5", + "ASRC1 TX1", + "ASRC1 TX2", + "ASRC1 TX3", + "ASRC1 TX4", + "ASRC1 TX5", + "ASRC1 TX6", + "OPE1", }; static const unsigned int tegra210_ahub_mux_values[] = { @@ -439,6 +470,9 @@ static const unsigned int tegra210_ahub_mux_values[] = { MUX_VALUE(1, 2), MUX_VALUE(1, 3), MUX_VALUE(1, 4), + /* OPE */ + MUX_VALUE(2, 0), + MUX_VALUE(2, 1), }; static const unsigned int tegra186_ahub_mux_values[] = { @@ -513,6 +547,15 @@ static const unsigned int tegra186_ahub_mux_values[] = { MUX_VALUE(1, 2), MUX_VALUE(1, 3), MUX_VALUE(1, 4), + /* ASRC */ + MUX_VALUE(3, 24), + MUX_VALUE(3, 25), + MUX_VALUE(3, 26), + MUX_VALUE(3, 27), + MUX_VALUE(3, 28), + MUX_VALUE(3, 29), + /* OPE */ + MUX_VALUE(2, 0), }; /* Controls for t210 */ @@ -557,6 +600,8 @@ MUX_ENUM_CTRL_DECL(t210_mixer17_tx, 0x26); MUX_ENUM_CTRL_DECL(t210_mixer18_tx, 0x27); MUX_ENUM_CTRL_DECL(t210_mixer19_tx, 0x28); MUX_ENUM_CTRL_DECL(t210_mixer110_tx, 0x29); +MUX_ENUM_CTRL_DECL(t210_ope1_tx, 0x40); +MUX_ENUM_CTRL_DECL(t210_ope2_tx, 0x41); /* Controls for t186 */ MUX_ENUM_CTRL_DECL_186(t186_admaif1_tx, 0x00); @@ -623,6 +668,49 @@ MUX_ENUM_CTRL_DECL_186(t186_mixer17_tx, 0x26); MUX_ENUM_CTRL_DECL_186(t186_mixer18_tx, 0x27); MUX_ENUM_CTRL_DECL_186(t186_mixer19_tx, 0x28); MUX_ENUM_CTRL_DECL_186(t186_mixer110_tx, 0x29); +MUX_ENUM_CTRL_DECL_186(t186_asrc11_tx, 0x6c); +MUX_ENUM_CTRL_DECL_186(t186_asrc12_tx, 0x6d); +MUX_ENUM_CTRL_DECL_186(t186_asrc13_tx, 0x6e); +MUX_ENUM_CTRL_DECL_186(t186_asrc14_tx, 0x6f); +MUX_ENUM_CTRL_DECL_186(t186_asrc15_tx, 0x70); +MUX_ENUM_CTRL_DECL_186(t186_asrc16_tx, 0x71); +MUX_ENUM_CTRL_DECL_186(t186_asrc17_tx, 0x72); +MUX_ENUM_CTRL_DECL_186(t186_ope1_tx, 0x40); + +/* Controls for t234 */ +MUX_ENUM_CTRL_DECL_234(t234_mvc1_tx, 0x44); +MUX_ENUM_CTRL_DECL_234(t234_mvc2_tx, 0x45); +MUX_ENUM_CTRL_DECL_234(t234_amx11_tx, 0x48); +MUX_ENUM_CTRL_DECL_234(t234_amx12_tx, 0x49); +MUX_ENUM_CTRL_DECL_234(t234_amx13_tx, 0x4a); +MUX_ENUM_CTRL_DECL_234(t234_amx14_tx, 0x4b); +MUX_ENUM_CTRL_DECL_234(t234_amx21_tx, 0x4c); +MUX_ENUM_CTRL_DECL_234(t234_amx22_tx, 0x4d); +MUX_ENUM_CTRL_DECL_234(t234_amx23_tx, 0x4e); +MUX_ENUM_CTRL_DECL_234(t234_amx24_tx, 0x4f); +MUX_ENUM_CTRL_DECL_234(t234_amx31_tx, 0x50); +MUX_ENUM_CTRL_DECL_234(t234_amx32_tx, 0x51); +MUX_ENUM_CTRL_DECL_234(t234_amx33_tx, 0x52); +MUX_ENUM_CTRL_DECL_234(t234_amx34_tx, 0x53); +MUX_ENUM_CTRL_DECL_234(t234_adx1_tx, 0x58); +MUX_ENUM_CTRL_DECL_234(t234_adx2_tx, 0x59); +MUX_ENUM_CTRL_DECL_234(t234_adx3_tx, 0x5a); +MUX_ENUM_CTRL_DECL_234(t234_adx4_tx, 0x5b); +MUX_ENUM_CTRL_DECL_234(t234_amx41_tx, 0x5c); +MUX_ENUM_CTRL_DECL_234(t234_amx42_tx, 0x5d); +MUX_ENUM_CTRL_DECL_234(t234_amx43_tx, 0x5e); +MUX_ENUM_CTRL_DECL_234(t234_amx44_tx, 0x5f); +MUX_ENUM_CTRL_DECL_234(t234_admaif17_tx, 0x60); +MUX_ENUM_CTRL_DECL_234(t234_admaif18_tx, 0x61); +MUX_ENUM_CTRL_DECL_234(t234_admaif19_tx, 0x62); +MUX_ENUM_CTRL_DECL_234(t234_admaif20_tx, 0x63); +MUX_ENUM_CTRL_DECL_234(t234_asrc11_tx, 0x64); +MUX_ENUM_CTRL_DECL_234(t234_asrc12_tx, 0x65); +MUX_ENUM_CTRL_DECL_234(t234_asrc13_tx, 0x66); +MUX_ENUM_CTRL_DECL_234(t234_asrc14_tx, 0x67); +MUX_ENUM_CTRL_DECL_234(t234_asrc15_tx, 0x68); +MUX_ENUM_CTRL_DECL_234(t234_asrc16_tx, 0x69); +MUX_ENUM_CTRL_DECL_234(t234_asrc17_tx, 0x6a); /* * The number of entries in, and order of, this array is closely tied to the @@ -689,6 +777,8 @@ static const struct snd_soc_dapm_widget tegra210_ahub_widgets[] = { TX_WIDGETS("MIXER1 TX3"), TX_WIDGETS("MIXER1 TX4"), TX_WIDGETS("MIXER1 TX5"), + WIDGETS("OPE1", t210_ope1_tx), + WIDGETS("OPE2", t210_ope2_tx), }; static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { @@ -785,6 +875,130 @@ static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { TX_WIDGETS("MIXER1 TX3"), TX_WIDGETS("MIXER1 TX4"), TX_WIDGETS("MIXER1 TX5"), + WIDGETS("ASRC1 RX1", t186_asrc11_tx), + WIDGETS("ASRC1 RX2", t186_asrc12_tx), + WIDGETS("ASRC1 RX3", t186_asrc13_tx), + WIDGETS("ASRC1 RX4", t186_asrc14_tx), + WIDGETS("ASRC1 RX5", t186_asrc15_tx), + WIDGETS("ASRC1 RX6", t186_asrc16_tx), + WIDGETS("ASRC1 RX7", t186_asrc17_tx), + TX_WIDGETS("ASRC1 TX1"), + TX_WIDGETS("ASRC1 TX2"), + TX_WIDGETS("ASRC1 TX3"), + TX_WIDGETS("ASRC1 TX4"), + TX_WIDGETS("ASRC1 TX5"), + TX_WIDGETS("ASRC1 TX6"), + WIDGETS("OPE1", t186_ope1_tx), +}; + +static const struct snd_soc_dapm_widget tegra234_ahub_widgets[] = { + WIDGETS("ADMAIF1", t186_admaif1_tx), + WIDGETS("ADMAIF2", t186_admaif2_tx), + WIDGETS("ADMAIF3", t186_admaif3_tx), + WIDGETS("ADMAIF4", t186_admaif4_tx), + WIDGETS("ADMAIF5", t186_admaif5_tx), + WIDGETS("ADMAIF6", t186_admaif6_tx), + WIDGETS("ADMAIF7", t186_admaif7_tx), + WIDGETS("ADMAIF8", t186_admaif8_tx), + WIDGETS("ADMAIF9", t186_admaif9_tx), + WIDGETS("ADMAIF10", t186_admaif10_tx), + WIDGETS("ADMAIF11", t186_admaif11_tx), + WIDGETS("ADMAIF12", t186_admaif12_tx), + WIDGETS("ADMAIF13", t186_admaif13_tx), + WIDGETS("ADMAIF14", t186_admaif14_tx), + WIDGETS("ADMAIF15", t186_admaif15_tx), + WIDGETS("ADMAIF16", t186_admaif16_tx), + WIDGETS("ADMAIF17", t234_admaif17_tx), + WIDGETS("ADMAIF18", t234_admaif18_tx), + WIDGETS("ADMAIF19", t234_admaif19_tx), + WIDGETS("ADMAIF20", t234_admaif20_tx), + WIDGETS("I2S1", t186_i2s1_tx), + WIDGETS("I2S2", t186_i2s2_tx), + WIDGETS("I2S3", t186_i2s3_tx), + WIDGETS("I2S4", t186_i2s4_tx), + WIDGETS("I2S5", t186_i2s5_tx), + WIDGETS("I2S6", t186_i2s6_tx), + TX_WIDGETS("DMIC1"), + TX_WIDGETS("DMIC2"), + TX_WIDGETS("DMIC3"), + TX_WIDGETS("DMIC4"), + WIDGETS("DSPK1", t186_dspk1_tx), + WIDGETS("DSPK2", t186_dspk2_tx), + WIDGETS("SFC1", t186_sfc1_tx), + WIDGETS("SFC2", t186_sfc2_tx), + WIDGETS("SFC3", t186_sfc3_tx), + WIDGETS("SFC4", t186_sfc4_tx), + WIDGETS("MVC1", t234_mvc1_tx), + WIDGETS("MVC2", t234_mvc2_tx), + WIDGETS("AMX1 RX1", t234_amx11_tx), + WIDGETS("AMX1 RX2", t234_amx12_tx), + WIDGETS("AMX1 RX3", t234_amx13_tx), + WIDGETS("AMX1 RX4", t234_amx14_tx), + WIDGETS("AMX2 RX1", t234_amx21_tx), + WIDGETS("AMX2 RX2", t234_amx22_tx), + WIDGETS("AMX2 RX3", t234_amx23_tx), + WIDGETS("AMX2 RX4", t234_amx24_tx), + WIDGETS("AMX3 RX1", t234_amx31_tx), + WIDGETS("AMX3 RX2", t234_amx32_tx), + WIDGETS("AMX3 RX3", t234_amx33_tx), + WIDGETS("AMX3 RX4", t234_amx34_tx), + WIDGETS("AMX4 RX1", t234_amx41_tx), + WIDGETS("AMX4 RX2", t234_amx42_tx), + WIDGETS("AMX4 RX3", t234_amx43_tx), + WIDGETS("AMX4 RX4", t234_amx44_tx), + TX_WIDGETS("AMX1"), + TX_WIDGETS("AMX2"), + TX_WIDGETS("AMX3"), + TX_WIDGETS("AMX4"), + WIDGETS("ADX1", t234_adx1_tx), + WIDGETS("ADX2", t234_adx2_tx), + WIDGETS("ADX3", t234_adx3_tx), + WIDGETS("ADX4", t234_adx4_tx), + TX_WIDGETS("ADX1 TX1"), + TX_WIDGETS("ADX1 TX2"), + TX_WIDGETS("ADX1 TX3"), + TX_WIDGETS("ADX1 TX4"), + TX_WIDGETS("ADX2 TX1"), + TX_WIDGETS("ADX2 TX2"), + TX_WIDGETS("ADX2 TX3"), + TX_WIDGETS("ADX2 TX4"), + TX_WIDGETS("ADX3 TX1"), + TX_WIDGETS("ADX3 TX2"), + TX_WIDGETS("ADX3 TX3"), + TX_WIDGETS("ADX3 TX4"), + TX_WIDGETS("ADX4 TX1"), + TX_WIDGETS("ADX4 TX2"), + TX_WIDGETS("ADX4 TX3"), + TX_WIDGETS("ADX4 TX4"), + WIDGETS("MIXER1 RX1", t186_mixer11_tx), + WIDGETS("MIXER1 RX2", t186_mixer12_tx), + WIDGETS("MIXER1 RX3", t186_mixer13_tx), + WIDGETS("MIXER1 RX4", t186_mixer14_tx), + WIDGETS("MIXER1 RX5", t186_mixer15_tx), + WIDGETS("MIXER1 RX6", t186_mixer16_tx), + WIDGETS("MIXER1 RX7", t186_mixer17_tx), + WIDGETS("MIXER1 RX8", t186_mixer18_tx), + WIDGETS("MIXER1 RX9", t186_mixer19_tx), + WIDGETS("MIXER1 RX10", t186_mixer110_tx), + TX_WIDGETS("MIXER1 TX1"), + TX_WIDGETS("MIXER1 TX2"), + TX_WIDGETS("MIXER1 TX3"), + TX_WIDGETS("MIXER1 TX4"), + TX_WIDGETS("MIXER1 TX5"), + WIDGETS("ASRC1 RX1", t234_asrc11_tx), + WIDGETS("ASRC1 RX2", t234_asrc12_tx), + WIDGETS("ASRC1 RX3", t234_asrc13_tx), + WIDGETS("ASRC1 RX4", t234_asrc14_tx), + WIDGETS("ASRC1 RX5", t234_asrc15_tx), + WIDGETS("ASRC1 RX6", t234_asrc16_tx), + WIDGETS("ASRC1 RX7", t234_asrc17_tx), + TX_WIDGETS("ASRC1 TX1"), + TX_WIDGETS("ASRC1 TX2"), + TX_WIDGETS("ASRC1 TX3"), + TX_WIDGETS("ASRC1 TX4"), + TX_WIDGETS("ASRC1 TX5"), + TX_WIDGETS("ASRC1 TX6"), + WIDGETS("OPE1", t186_ope1_tx), }; #define TEGRA_COMMON_MUX_ROUTES(name) \ @@ -827,7 +1041,11 @@ static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { { name " Mux", "MIXER1 TX2", "MIXER1 TX2 XBAR-RX" }, \ { name " Mux", "MIXER1 TX3", "MIXER1 TX3 XBAR-RX" }, \ { name " Mux", "MIXER1 TX4", "MIXER1 TX4 XBAR-RX" }, \ - { name " Mux", "MIXER1 TX5", "MIXER1 TX5 XBAR-RX" }, + { name " Mux", "MIXER1 TX5", "MIXER1 TX5 XBAR-RX" }, \ + { name " Mux", "OPE1", "OPE1 XBAR-RX" }, + +#define TEGRA210_ONLY_MUX_ROUTES(name) \ + { name " Mux", "OPE2", "OPE2 XBAR-RX" }, #define TEGRA186_ONLY_MUX_ROUTES(name) \ { name " Mux", "ADMAIF11", "ADMAIF11 XBAR-RX" }, \ @@ -851,12 +1069,19 @@ static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { { name " Mux", "ADX4 TX1", "ADX4 TX1 XBAR-RX" }, \ { name " Mux", "ADX4 TX2", "ADX4 TX2 XBAR-RX" }, \ { name " Mux", "ADX4 TX3", "ADX4 TX3 XBAR-RX" }, \ - { name " Mux", "ADX4 TX4", "ADX4 TX4 XBAR-RX" }, + { name " Mux", "ADX4 TX4", "ADX4 TX4 XBAR-RX" }, \ + { name " Mux", "ASRC1 TX1", "ASRC1 TX1 XBAR-RX" }, \ + { name " Mux", "ASRC1 TX2", "ASRC1 TX2 XBAR-RX" }, \ + { name " Mux", "ASRC1 TX3", "ASRC1 TX3 XBAR-RX" }, \ + { name " Mux", "ASRC1 TX4", "ASRC1 TX4 XBAR-RX" }, \ + { name " Mux", "ASRC1 TX5", "ASRC1 TX5 XBAR-RX" }, \ + { name " Mux", "ASRC1 TX6", "ASRC1 TX6 XBAR-RX" }, -#define TEGRA210_MUX_ROUTES(name) \ - TEGRA_COMMON_MUX_ROUTES(name) +#define TEGRA210_MUX_ROUTES(name) \ + TEGRA_COMMON_MUX_ROUTES(name) \ + TEGRA210_ONLY_MUX_ROUTES(name) -#define TEGRA186_MUX_ROUTES(name) \ +#define TEGRA186_MUX_ROUTES(name) \ TEGRA_COMMON_MUX_ROUTES(name) \ TEGRA186_ONLY_MUX_ROUTES(name) @@ -924,6 +1149,8 @@ static const struct snd_soc_dapm_route tegra210_ahub_routes[] = { TEGRA210_MUX_ROUTES("MIXER1 RX8") TEGRA210_MUX_ROUTES("MIXER1 RX9") TEGRA210_MUX_ROUTES("MIXER1 RX10") + TEGRA210_MUX_ROUTES("OPE1") + TEGRA210_MUX_ROUTES("OPE2") }; static const struct snd_soc_dapm_route tegra186_ahub_routes[] = { @@ -1011,6 +1238,14 @@ static const struct snd_soc_dapm_route tegra186_ahub_routes[] = { TEGRA186_MUX_ROUTES("MIXER1 RX8") TEGRA186_MUX_ROUTES("MIXER1 RX9") TEGRA186_MUX_ROUTES("MIXER1 RX10") + TEGRA186_MUX_ROUTES("ASRC1 RX1") + TEGRA186_MUX_ROUTES("ASRC1 RX2") + TEGRA186_MUX_ROUTES("ASRC1 RX3") + TEGRA186_MUX_ROUTES("ASRC1 RX4") + TEGRA186_MUX_ROUTES("ASRC1 RX5") + TEGRA186_MUX_ROUTES("ASRC1 RX6") + TEGRA186_MUX_ROUTES("ASRC1 RX7") + TEGRA186_MUX_ROUTES("OPE1") }; static const struct snd_soc_component_driver tegra210_ahub_component = { @@ -1027,6 +1262,13 @@ static const struct snd_soc_component_driver tegra186_ahub_component = { .num_dapm_routes = ARRAY_SIZE(tegra186_ahub_routes), }; +static const struct snd_soc_component_driver tegra234_ahub_component = { + .dapm_widgets = tegra234_ahub_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra234_ahub_widgets), + .dapm_routes = tegra186_ahub_routes, + .num_dapm_routes = ARRAY_SIZE(tegra186_ahub_routes), +}; + static const struct regmap_config tegra210_ahub_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -1067,9 +1309,22 @@ static const struct tegra_ahub_soc_data soc_data_tegra186 = { .reg_count = TEGRA186_XBAR_UPDATE_MAX_REG, }; +static const struct tegra_ahub_soc_data soc_data_tegra234 = { + .cmpnt_drv = &tegra234_ahub_component, + .dai_drv = tegra186_ahub_dais, + .num_dais = ARRAY_SIZE(tegra186_ahub_dais), + .regmap_config = &tegra186_ahub_regmap_config, + .mask[0] = TEGRA186_XBAR_REG_MASK_0, + .mask[1] = TEGRA186_XBAR_REG_MASK_1, + .mask[2] = TEGRA186_XBAR_REG_MASK_2, + .mask[3] = TEGRA186_XBAR_REG_MASK_3, + .reg_count = TEGRA186_XBAR_UPDATE_MAX_REG, +}; + static const struct of_device_id tegra_ahub_of_match[] = { { .compatible = "nvidia,tegra210-ahub", .data = &soc_data_tegra210 }, { .compatible = "nvidia,tegra186-ahub", .data = &soc_data_tegra186 }, + { .compatible = "nvidia,tegra234-ahub", .data = &soc_data_tegra234 }, {}, }; MODULE_DEVICE_TABLE(of, tegra_ahub_of_match); diff --git a/sound/soc/tegra/tegra210_ahub.h b/sound/soc/tegra/tegra210_ahub.h index 47802bbe17a9..2728db4d24f2 100644 --- a/sound/soc/tegra/tegra210_ahub.h +++ b/sound/soc/tegra/tegra210_ahub.h @@ -2,7 +2,7 @@ /* * tegra210_ahub.h - TEGRA210 AHUB * - * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. * */ @@ -74,6 +74,8 @@ tegra_ahub_get_value_enum, \ tegra_ahub_put_value_enum) +#define MUX_ENUM_CTRL_DECL_234(ename, id) MUX_ENUM_CTRL_DECL_186(ename, id) + #define WIDGETS(sname, ename) \ SND_SOC_DAPM_AIF_IN(sname " XBAR-RX", NULL, 0, SND_SOC_NOPM, 0, 0), \ SND_SOC_DAPM_AIF_OUT(sname " XBAR-TX", NULL, 0, SND_SOC_NOPM, 0, 0), \ diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index 9552bbb939dd..39ffa4d76b59 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -214,11 +214,11 @@ static int tegra210_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int mask, val; mask = I2S_CTRL_MASTER_EN_MASK; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BC_FC: val = 0; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_BP_FP: val = I2S_CTRL_MASTER_EN; break; default: @@ -803,7 +803,6 @@ static const struct snd_soc_component_driver tegra210_i2s_cmpnt = { .num_dapm_routes = ARRAY_SIZE(tegra210_i2s_routes), .controls = tegra210_i2s_controls, .num_controls = ARRAY_SIZE(tegra210_i2s_controls), - .non_legacy_dai_naming = 1, }; static bool tegra210_i2s_wr_reg(struct device *dev, unsigned int reg) diff --git a/sound/soc/tegra/tegra210_mbdrc.c b/sound/soc/tegra/tegra210_mbdrc.c new file mode 100644 index 000000000000..d786daa6aba6 --- /dev/null +++ b/sound/soc/tegra/tegra210_mbdrc.c @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_mbdrc.c - Tegra210 MBDRC driver +// +// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "tegra210_mbdrc.h" +#include "tegra210_ope.h" + +#define MBDRC_FILTER_REG(reg, id) \ + ((reg) + ((id) * TEGRA210_MBDRC_FILTER_PARAM_STRIDE)) + +#define MBDRC_FILTER_REG_DEFAULTS(id) \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_IIR_CFG, id), 0x00000005}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_ATTACK, id), 0x3e48590c}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_RELEASE, id), 0x08414e9f}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_FAST_ATTACK, id), 0x7fffffff}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_THRESHOLD, id), 0x06145082}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_OUT_THRESHOLD, id), 0x060d379b}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_1ST, id), 0x0000a000}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_2ND, id), 0x00002000}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_3RD, id), 0x00000b33}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_4TH, id), 0x00000800}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_5TH, id), 0x0000019a}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_MAKEUP_GAIN, id), 0x00000002}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_INIT_GAIN, id), 0x00066666}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_GAIN_ATTACK, id), 0x00d9ba0e}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_GAIN_RELEASE, id), 0x3e48590c}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_FAST_RELEASE, id), 0x7ffff26a}, \ + { MBDRC_FILTER_REG(TEGRA210_MBDRC_CFG_RAM_CTRL, id), 0x4000} + +static const struct reg_default tegra210_mbdrc_reg_defaults[] = { + { TEGRA210_MBDRC_CFG, 0x0030de51}, + { TEGRA210_MBDRC_CHANNEL_MASK, 0x00000003}, + { TEGRA210_MBDRC_FAST_FACTOR, 0x30000800}, + + MBDRC_FILTER_REG_DEFAULTS(0), + MBDRC_FILTER_REG_DEFAULTS(1), + MBDRC_FILTER_REG_DEFAULTS(2), +}; + +/* Default MBDRC parameters */ +static const struct tegra210_mbdrc_config mbdrc_init_config = { + .mode = 0, /* Bypass */ + .rms_off = 48, + .peak_rms_mode = 1, /* PEAK */ + .fliter_structure = 0, /* All-pass tree */ + .shift_ctrl = 30, + .frame_size = 32, + .channel_mask = 0x3, + .fa_factor = 2048, + .fr_factor = 14747, + + .band_params[MBDRC_LOW_BAND] = { + .band = MBDRC_LOW_BAND, + .iir_stages = 5, + .in_attack_tc = 1044928780, + .in_release_tc = 138497695, + .fast_attack_tc = 2147483647, + .in_threshold = {130, 80, 20, 6}, + .out_threshold = {155, 55, 13, 6}, + .ratio = {40960, 8192, 2867, 2048, 410}, + .makeup_gain = 4, + .gain_init = 419430, + .gain_attack_tc = 14268942, + .gain_release_tc = 1440547090, + .fast_release_tc = 2147480170, + + .biquad_params = { + /* + * Gains: + * + * b0, b1, a0, + * a1, a2, + */ + + /* Band-0 */ + 961046798, -2030431983, 1073741824, + 2030431983, -961046798, + /* Band-1 */ + 1030244425, -2099481453, 1073741824, + 2099481453, -1030244425, + /* Band-2 */ + 1067169294, -2136327263, 1073741824, + 2136327263, -1067169294, + /* Band-3 */ + 434951949, -1306567134, 1073741824, + 1306567134, -434951949, + /* Band-4 */ + 780656019, -1605955641, 1073741824, + 1605955641, -780656019, + /* Band-5 */ + 1024497031, -1817128152, 1073741824, + 1817128152, -1024497031, + /* Band-6 */ + 1073741824, 0, 0, + 0, 0, + /* Band-7 */ + 1073741824, 0, 0, + 0, 0, + } + }, + + .band_params[MBDRC_MID_BAND] = { + .band = MBDRC_MID_BAND, + .iir_stages = 5, + .in_attack_tc = 1581413104, + .in_release_tc = 35494783, + .fast_attack_tc = 2147483647, + .in_threshold = {130, 50, 30, 6}, + .out_threshold = {106, 50, 30, 13}, + .ratio = {40960, 2867, 4096, 2867, 410}, + .makeup_gain = 6, + .gain_init = 419430, + .gain_attack_tc = 4766887, + .gain_release_tc = 1044928780, + .fast_release_tc = 2147480170, + + .biquad_params = { + /* + * Gains: + * + * b0, b1, a0, + * a1, a2, + */ + + /* Band-0 */ + -1005668963, 1073741824, 0, + 1005668963, 0, + /* Band-1 */ + 998437058, -2067742187, 1073741824, + 2067742187, -998437058, + /* Band-2 */ + 1051963422, -2121153948, 1073741824, + 2121153948, -1051963422, + /* Band-3 */ + 434951949, -1306567134, 1073741824, + 1306567134, -434951949, + /* Band-4 */ + 780656019, -1605955641, 1073741824, + 1605955641, -780656019, + /* Band-5 */ + 1024497031, -1817128152, 1073741824, + 1817128152, -1024497031, + /* Band-6 */ + 1073741824, 0, 0, + 0, 0, + /* Band-7 */ + 1073741824, 0, 0, + 0, 0, + } + }, + + .band_params[MBDRC_HIGH_BAND] = { + .band = MBDRC_HIGH_BAND, + .iir_stages = 5, + .in_attack_tc = 2144750688, + .in_release_tc = 70402888, + .fast_attack_tc = 2147483647, + .in_threshold = {130, 50, 30, 6}, + .out_threshold = {106, 50, 30, 13}, + .ratio = {40960, 2867, 4096, 2867, 410}, + .makeup_gain = 6, + .gain_init = 419430, + .gain_attack_tc = 4766887, + .gain_release_tc = 1044928780, + .fast_release_tc = 2147480170, + + .biquad_params = { + /* + * Gains: + * + * b0, b1, a0, + * a1, a2, + */ + + /* Band-0 */ + 1073741824, 0, 0, + 0, 0, + /* Band-1 */ + 1073741824, 0, 0, + 0, 0, + /* Band-2 */ + 1073741824, 0, 0, + 0, 0, + /* Band-3 */ + -619925131, 1073741824, 0, + 619925131, 0, + /* Band-4 */ + 606839335, -1455425976, 1073741824, + 1455425976, -606839335, + /* Band-5 */ + 917759617, -1724690840, 1073741824, + 1724690840, -917759617, + /* Band-6 */ + 1073741824, 0, 0, + 0, 0, + /* Band-7 */ + 1073741824, 0, 0, + 0, 0, + } + } +}; + +static void tegra210_mbdrc_write_ram(struct regmap *regmap, unsigned int reg_ctrl, + unsigned int reg_data, unsigned int ram_offset, + unsigned int *data, size_t size) +{ + unsigned int val; + unsigned int i; + + val = ram_offset & TEGRA210_MBDRC_RAM_CTRL_RAM_ADDR_MASK; + val |= TEGRA210_MBDRC_RAM_CTRL_ADDR_INIT_EN; + val |= TEGRA210_MBDRC_RAM_CTRL_SEQ_ACCESS_EN; + val |= TEGRA210_MBDRC_RAM_CTRL_RW_WRITE; + + regmap_write(regmap, reg_ctrl, val); + + for (i = 0; i < size; i++) + regmap_write(regmap, reg_data, data[i]); +} + +static int tegra210_mbdrc_get(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 *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + unsigned int val; + + regmap_read(ope->mbdrc_regmap, mc->reg, &val); + + ucontrol->value.integer.value[0] = (val >> mc->shift) & mc->max; + + return 0; +} + +static int tegra210_mbdrc_put(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 *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + unsigned int val = ucontrol->value.integer.value[0]; + bool change = false; + + val = val << mc->shift; + + regmap_update_bits_check(ope->mbdrc_regmap, mc->reg, + (mc->max << mc->shift), val, &change); + + return change ? 1 : 0; +} + +static int tegra210_mbdrc_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val; + + regmap_read(ope->mbdrc_regmap, e->reg, &val); + + ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & e->mask; + + return 0; +} + +static int tegra210_mbdrc_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + bool change = false; + unsigned int val; + unsigned int mask; + + if (ucontrol->value.enumerated.item[0] > e->items - 1) + return -EINVAL; + + val = ucontrol->value.enumerated.item[0] << e->shift_l; + mask = e->mask << e->shift_l; + + regmap_update_bits_check(ope->mbdrc_regmap, e->reg, mask, val, + &change); + + return change ? 1 : 0; +} + +static int tegra210_mbdrc_band_params_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 *data = (u32 *)ucontrol->value.bytes.data; + u32 regs = params->soc.base; + u32 mask = params->soc.mask; + u32 shift = params->shift; + unsigned int i; + + for (i = 0; i < params->soc.num_regs; i++, regs += cmpnt->val_bytes) { + regmap_read(ope->mbdrc_regmap, regs, &data[i]); + + data[i] = ((data[i] & mask) >> shift); + } + + return 0; +} + +static int tegra210_mbdrc_band_params_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 *data = (u32 *)ucontrol->value.bytes.data; + u32 regs = params->soc.base; + u32 mask = params->soc.mask; + u32 shift = params->shift; + bool change = false; + unsigned int i; + + for (i = 0; i < params->soc.num_regs; i++, regs += cmpnt->val_bytes) { + bool update = false; + + regmap_update_bits_check(ope->mbdrc_regmap, regs, mask, + data[i] << shift, &update); + + change |= update; + } + + return change ? 1 : 0; +} + +static int tegra210_mbdrc_threshold_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 *data = (u32 *)ucontrol->value.bytes.data; + u32 regs = params->soc.base; + u32 num_regs = params->soc.num_regs; + u32 val; + unsigned int i; + + for (i = 0; i < num_regs; i += 4, regs += cmpnt->val_bytes) { + regmap_read(ope->mbdrc_regmap, regs, &val); + + data[i] = (val & TEGRA210_MBDRC_THRESH_1ST_MASK) >> + TEGRA210_MBDRC_THRESH_1ST_SHIFT; + data[i + 1] = (val & TEGRA210_MBDRC_THRESH_2ND_MASK) >> + TEGRA210_MBDRC_THRESH_2ND_SHIFT; + data[i + 2] = (val & TEGRA210_MBDRC_THRESH_3RD_MASK) >> + TEGRA210_MBDRC_THRESH_3RD_SHIFT; + data[i + 3] = (val & TEGRA210_MBDRC_THRESH_4TH_MASK) >> + TEGRA210_MBDRC_THRESH_4TH_SHIFT; + } + + return 0; +} + +static int tegra210_mbdrc_threshold_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 *data = (u32 *)ucontrol->value.bytes.data; + u32 regs = params->soc.base; + u32 num_regs = params->soc.num_regs; + bool change = false; + unsigned int i; + + for (i = 0; i < num_regs; i += 4, regs += cmpnt->val_bytes) { + bool update = false; + + data[i] = (((data[i] >> TEGRA210_MBDRC_THRESH_1ST_SHIFT) & + TEGRA210_MBDRC_THRESH_1ST_MASK) | + ((data[i + 1] >> TEGRA210_MBDRC_THRESH_2ND_SHIFT) & + TEGRA210_MBDRC_THRESH_2ND_MASK) | + ((data[i + 2] >> TEGRA210_MBDRC_THRESH_3RD_SHIFT) & + TEGRA210_MBDRC_THRESH_3RD_MASK) | + ((data[i + 3] >> TEGRA210_MBDRC_THRESH_4TH_SHIFT) & + TEGRA210_MBDRC_THRESH_4TH_MASK)); + + regmap_update_bits_check(ope->mbdrc_regmap, regs, 0xffffffff, + data[i], &update); + + change |= update; + } + + return change ? 1 : 0; +} + +static int tegra210_mbdrc_biquad_coeffs_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + u32 *data = (u32 *)ucontrol->value.bytes.data; + + memset(data, 0, params->soc.num_regs * cmpnt->val_bytes); + + return 0; +} + +static int tegra210_mbdrc_biquad_coeffs_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 reg_ctrl = params->soc.base; + u32 reg_data = reg_ctrl + cmpnt->val_bytes; + u32 *data = (u32 *)ucontrol->value.bytes.data; + + tegra210_mbdrc_write_ram(ope->mbdrc_regmap, reg_ctrl, reg_data, + params->shift, data, params->soc.num_regs); + + return 1; +} + +static int tegra210_mbdrc_param_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_bytes *params = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = params->num_regs * sizeof(u32); + + return 0; +} + +static int tegra210_mbdrc_vol_get(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 *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val; + + regmap_read(ope->mbdrc_regmap, mc->reg, &val); + + ucontrol->value.integer.value[0] = + ((val >> mc->shift) - TEGRA210_MBDRC_MASTER_VOL_MIN); + + return 0; +} + +static int tegra210_mbdrc_vol_put(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 *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + int val = ucontrol->value.integer.value[0]; + bool change = false; + + val += TEGRA210_MBDRC_MASTER_VOL_MIN; + + regmap_update_bits_check(ope->mbdrc_regmap, mc->reg, + mc->max << mc->shift, val << mc->shift, + &change); + + regmap_read(ope->mbdrc_regmap, mc->reg, &val); + + return change ? 1 : 0; +} + +static const char * const tegra210_mbdrc_mode_text[] = { + "Bypass", "Fullband", "Dualband", "Multiband" +}; + +static const struct soc_enum tegra210_mbdrc_mode_enum = + SOC_ENUM_SINGLE(TEGRA210_MBDRC_CFG, TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT, + 4, tegra210_mbdrc_mode_text); + +static const char * const tegra210_mbdrc_peak_rms_text[] = { + "Peak", "RMS" +}; + +static const struct soc_enum tegra210_mbdrc_peak_rms_enum = + SOC_ENUM_SINGLE(TEGRA210_MBDRC_CFG, TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT, + 2, tegra210_mbdrc_peak_rms_text); + +static const char * const tegra210_mbdrc_filter_structure_text[] = { + "All-pass-tree", "Flexible" +}; + +static const struct soc_enum tegra210_mbdrc_filter_structure_enum = + SOC_ENUM_SINGLE(TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT, 2, + tegra210_mbdrc_filter_structure_text); + +static const char * const tegra210_mbdrc_frame_size_text[] = { + "N1", "N2", "N4", "N8", "N16", "N32", "N64" +}; + +static const struct soc_enum tegra210_mbdrc_frame_size_enum = + SOC_ENUM_SINGLE(TEGRA210_MBDRC_CFG, TEGRA210_MBDRC_CFG_FRAME_SIZE_SHIFT, + 7, tegra210_mbdrc_frame_size_text); + +#define TEGRA_MBDRC_BYTES_EXT(xname, xbase, xregs, xshift, xmask, xinfo) \ + TEGRA_SOC_BYTES_EXT(xname, xbase, xregs, xshift, xmask, \ + tegra210_mbdrc_band_params_get, \ + tegra210_mbdrc_band_params_put, \ + tegra210_mbdrc_param_info) + +#define TEGRA_MBDRC_BAND_BYTES_EXT(xname, xbase, xshift, xmask, xinfo) \ + TEGRA_MBDRC_BYTES_EXT(xname, xbase, TEGRA210_MBDRC_FILTER_COUNT, \ + xshift, xmask, xinfo) + +static const DECLARE_TLV_DB_MINMAX(mdbrc_vol_tlv, -25600, 25500); + +static const struct snd_kcontrol_new tegra210_mbdrc_controls[] = { + SOC_ENUM_EXT("MBDRC Peak RMS Mode", tegra210_mbdrc_peak_rms_enum, + tegra210_mbdrc_get_enum, tegra210_mbdrc_put_enum), + + SOC_ENUM_EXT("MBDRC Filter Structure", + tegra210_mbdrc_filter_structure_enum, + tegra210_mbdrc_get_enum, tegra210_mbdrc_put_enum), + + SOC_ENUM_EXT("MBDRC Frame Size", tegra210_mbdrc_frame_size_enum, + tegra210_mbdrc_get_enum, tegra210_mbdrc_put_enum), + + SOC_ENUM_EXT("MBDRC Mode", tegra210_mbdrc_mode_enum, + tegra210_mbdrc_get_enum, tegra210_mbdrc_put_enum), + + SOC_SINGLE_EXT("MBDRC RMS Offset", TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_RMS_OFFSET_SHIFT, 0x1ff, 0, + tegra210_mbdrc_get, tegra210_mbdrc_put), + + SOC_SINGLE_EXT("MBDRC Shift Control", TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_SHIFT_CTRL_SHIFT, 0x1f, 0, + tegra210_mbdrc_get, tegra210_mbdrc_put), + + SOC_SINGLE_EXT("MBDRC Fast Attack Factor", TEGRA210_MBDRC_FAST_FACTOR, + TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT, 0xffff, 0, + tegra210_mbdrc_get, tegra210_mbdrc_put), + + SOC_SINGLE_EXT("MBDRC Fast Release Factor", TEGRA210_MBDRC_FAST_FACTOR, + TEGRA210_MBDRC_FAST_FACTOR_RELEASE_SHIFT, 0xffff, 0, + tegra210_mbdrc_get, tegra210_mbdrc_put), + + SOC_SINGLE_RANGE_EXT_TLV("MBDRC Master Volume", + TEGRA210_MBDRC_MASTER_VOL, + TEGRA210_MBDRC_MASTER_VOL_SHIFT, + 0, 0x1ff, 0, + tegra210_mbdrc_vol_get, tegra210_mbdrc_vol_put, + mdbrc_vol_tlv), + + TEGRA_SOC_BYTES_EXT("MBDRC IIR Stages", TEGRA210_MBDRC_IIR_CFG, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_SHIFT, + TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC In Attack Time Const", TEGRA210_MBDRC_IN_ATTACK, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_IN_ATTACK_TC_SHIFT, + TEGRA210_MBDRC_IN_ATTACK_TC_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC In Release Time Const", TEGRA210_MBDRC_IN_RELEASE, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_IN_RELEASE_TC_SHIFT, + TEGRA210_MBDRC_IN_RELEASE_TC_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Fast Attack Time Const", TEGRA210_MBDRC_FAST_ATTACK, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_FAST_ATTACK_TC_SHIFT, + TEGRA210_MBDRC_FAST_ATTACK_TC_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC In Threshold", TEGRA210_MBDRC_IN_THRESHOLD, + TEGRA210_MBDRC_FILTER_COUNT * 4, 0, 0xffffffff, + tegra210_mbdrc_threshold_get, + tegra210_mbdrc_threshold_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Out Threshold", TEGRA210_MBDRC_OUT_THRESHOLD, + TEGRA210_MBDRC_FILTER_COUNT * 4, 0, 0xffffffff, + tegra210_mbdrc_threshold_get, + tegra210_mbdrc_threshold_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Ratio", TEGRA210_MBDRC_RATIO_1ST, + TEGRA210_MBDRC_FILTER_COUNT * 5, + TEGRA210_MBDRC_RATIO_1ST_SHIFT, TEGRA210_MBDRC_RATIO_1ST_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Makeup Gain", TEGRA210_MBDRC_MAKEUP_GAIN, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_MAKEUP_GAIN_SHIFT, + TEGRA210_MBDRC_MAKEUP_GAIN_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Init Gain", TEGRA210_MBDRC_INIT_GAIN, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_INIT_GAIN_SHIFT, + TEGRA210_MBDRC_INIT_GAIN_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Attack Gain", TEGRA210_MBDRC_GAIN_ATTACK, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_GAIN_ATTACK_SHIFT, + TEGRA210_MBDRC_GAIN_ATTACK_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Release Gain", TEGRA210_MBDRC_GAIN_RELEASE, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_GAIN_RELEASE_SHIFT, + TEGRA210_MBDRC_GAIN_RELEASE_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Fast Release Gain", + TEGRA210_MBDRC_FAST_RELEASE, + TEGRA210_MBDRC_FILTER_COUNT, + TEGRA210_MBDRC_FAST_RELEASE_SHIFT, + TEGRA210_MBDRC_FAST_RELEASE_MASK, + tegra210_mbdrc_band_params_get, + tegra210_mbdrc_band_params_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Low Band Biquad Coeffs", + TEGRA210_MBDRC_CFG_RAM_CTRL, + TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5, 0, 0xffffffff, + tegra210_mbdrc_biquad_coeffs_get, + tegra210_mbdrc_biquad_coeffs_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC Mid Band Biquad Coeffs", + TEGRA210_MBDRC_CFG_RAM_CTRL + + TEGRA210_MBDRC_FILTER_PARAM_STRIDE, + TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5, 0, 0xffffffff, + tegra210_mbdrc_biquad_coeffs_get, + tegra210_mbdrc_biquad_coeffs_put, + tegra210_mbdrc_param_info), + + TEGRA_SOC_BYTES_EXT("MBDRC High Band Biquad Coeffs", + TEGRA210_MBDRC_CFG_RAM_CTRL + + (TEGRA210_MBDRC_FILTER_PARAM_STRIDE * 2), + TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5, 0, 0xffffffff, + tegra210_mbdrc_biquad_coeffs_get, + tegra210_mbdrc_biquad_coeffs_put, + tegra210_mbdrc_param_info), +}; + +static bool tegra210_mbdrc_wr_reg(struct device *dev, unsigned int reg) +{ + if (reg >= TEGRA210_MBDRC_IIR_CFG) + reg -= ((reg - TEGRA210_MBDRC_IIR_CFG) % + (TEGRA210_MBDRC_FILTER_PARAM_STRIDE * + TEGRA210_MBDRC_FILTER_COUNT)); + + switch (reg) { + case TEGRA210_MBDRC_SOFT_RESET: + case TEGRA210_MBDRC_CG: + case TEGRA210_MBDRC_CFG ... TEGRA210_MBDRC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_mbdrc_rd_reg(struct device *dev, unsigned int reg) +{ + if (tegra210_mbdrc_wr_reg(dev, reg)) + return true; + + if (reg >= TEGRA210_MBDRC_IIR_CFG) + reg -= ((reg - TEGRA210_MBDRC_IIR_CFG) % + (TEGRA210_MBDRC_FILTER_PARAM_STRIDE * + TEGRA210_MBDRC_FILTER_COUNT)); + + switch (reg) { + case TEGRA210_MBDRC_STATUS: + return true; + default: + return false; + } +} + +static bool tegra210_mbdrc_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg >= TEGRA210_MBDRC_IIR_CFG) + reg -= ((reg - TEGRA210_MBDRC_IIR_CFG) % + (TEGRA210_MBDRC_FILTER_PARAM_STRIDE * + TEGRA210_MBDRC_FILTER_COUNT)); + + switch (reg) { + case TEGRA210_MBDRC_SOFT_RESET: + case TEGRA210_MBDRC_STATUS: + case TEGRA210_MBDRC_CFG_RAM_CTRL: + case TEGRA210_MBDRC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_mbdrc_precious_reg(struct device *dev, unsigned int reg) +{ + if (reg >= TEGRA210_MBDRC_IIR_CFG) + reg -= ((reg - TEGRA210_MBDRC_IIR_CFG) % + (TEGRA210_MBDRC_FILTER_PARAM_STRIDE * + TEGRA210_MBDRC_FILTER_COUNT)); + + switch (reg) { + case TEGRA210_MBDRC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra210_mbdrc_regmap_cfg = { + .name = "mbdrc", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_MBDRC_MAX_REG, + .writeable_reg = tegra210_mbdrc_wr_reg, + .readable_reg = tegra210_mbdrc_rd_reg, + .volatile_reg = tegra210_mbdrc_volatile_reg, + .precious_reg = tegra210_mbdrc_precious_reg, + .reg_defaults = tegra210_mbdrc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_mbdrc_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +int tegra210_mbdrc_hw_params(struct snd_soc_component *cmpnt) +{ + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + const struct tegra210_mbdrc_config *conf = &mbdrc_init_config; + u32 val = 0; + unsigned int i; + + regmap_read(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, &val); + + val &= TEGRA210_MBDRC_CFG_MBDRC_MODE_MASK; + + if (val == TEGRA210_MBDRC_CFG_MBDRC_MODE_BYPASS) + return 0; + + for (i = 0; i < MBDRC_NUM_BAND; i++) { + const struct tegra210_mbdrc_band_params *params = + &conf->band_params[i]; + + u32 reg_off = i * TEGRA210_MBDRC_FILTER_PARAM_STRIDE; + + tegra210_mbdrc_write_ram(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_CFG_RAM_CTRL, + reg_off + TEGRA210_MBDRC_CFG_RAM_DATA, + 0, (u32 *)¶ms->biquad_params[0], + TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5); + } + return 0; +} + +int tegra210_mbdrc_component_init(struct snd_soc_component *cmpnt) +{ + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + const struct tegra210_mbdrc_config *conf = &mbdrc_init_config; + unsigned int i; + u32 val; + + pm_runtime_get_sync(cmpnt->dev); + + /* Initialize MBDRC registers and AHUB RAM with default params */ + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_MBDRC_MODE_MASK, + conf->mode << TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_RMS_OFFSET_MASK, + conf->rms_off << TEGRA210_MBDRC_CFG_RMS_OFFSET_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_PEAK_RMS_MASK, + conf->peak_rms_mode << TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_MASK, + conf->fliter_structure << + TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_SHIFT_CTRL_MASK, + conf->shift_ctrl << TEGRA210_MBDRC_CFG_SHIFT_CTRL_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CFG, + TEGRA210_MBDRC_CFG_FRAME_SIZE_MASK, + __ffs(conf->frame_size) << + TEGRA210_MBDRC_CFG_FRAME_SIZE_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_CHANNEL_MASK, + TEGRA210_MBDRC_CHANNEL_MASK_MASK, + conf->channel_mask << TEGRA210_MBDRC_CHANNEL_MASK_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_FAST_FACTOR, + TEGRA210_MBDRC_FAST_FACTOR_ATTACK_MASK, + conf->fa_factor << TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, TEGRA210_MBDRC_FAST_FACTOR, + TEGRA210_MBDRC_FAST_FACTOR_ATTACK_MASK, + conf->fr_factor << TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT); + + for (i = 0; i < MBDRC_NUM_BAND; i++) { + const struct tegra210_mbdrc_band_params *params = + &conf->band_params[i]; + u32 reg_off = i * TEGRA210_MBDRC_FILTER_PARAM_STRIDE; + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_IIR_CFG, + TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_MASK, + params->iir_stages << + TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_IN_ATTACK, + TEGRA210_MBDRC_IN_ATTACK_TC_MASK, + params->in_attack_tc << + TEGRA210_MBDRC_IN_ATTACK_TC_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_IN_RELEASE, + TEGRA210_MBDRC_IN_RELEASE_TC_MASK, + params->in_release_tc << + TEGRA210_MBDRC_IN_RELEASE_TC_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_FAST_ATTACK, + TEGRA210_MBDRC_FAST_ATTACK_TC_MASK, + params->fast_attack_tc << + TEGRA210_MBDRC_FAST_ATTACK_TC_SHIFT); + + val = (((params->in_threshold[0] >> + TEGRA210_MBDRC_THRESH_1ST_SHIFT) & + TEGRA210_MBDRC_THRESH_1ST_MASK) | + ((params->in_threshold[1] >> + TEGRA210_MBDRC_THRESH_2ND_SHIFT) & + TEGRA210_MBDRC_THRESH_2ND_MASK) | + ((params->in_threshold[2] >> + TEGRA210_MBDRC_THRESH_3RD_SHIFT) & + TEGRA210_MBDRC_THRESH_3RD_MASK) | + ((params->in_threshold[3] >> + TEGRA210_MBDRC_THRESH_4TH_SHIFT) & + TEGRA210_MBDRC_THRESH_4TH_MASK)); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_IN_THRESHOLD, + 0xffffffff, val); + + val = (((params->out_threshold[0] >> + TEGRA210_MBDRC_THRESH_1ST_SHIFT) & + TEGRA210_MBDRC_THRESH_1ST_MASK) | + ((params->out_threshold[1] >> + TEGRA210_MBDRC_THRESH_2ND_SHIFT) & + TEGRA210_MBDRC_THRESH_2ND_MASK) | + ((params->out_threshold[2] >> + TEGRA210_MBDRC_THRESH_3RD_SHIFT) & + TEGRA210_MBDRC_THRESH_3RD_MASK) | + ((params->out_threshold[3] >> + TEGRA210_MBDRC_THRESH_4TH_SHIFT) & + TEGRA210_MBDRC_THRESH_4TH_MASK)); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_OUT_THRESHOLD, + 0xffffffff, val); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_RATIO_1ST, + TEGRA210_MBDRC_RATIO_1ST_MASK, + params->ratio[0] << TEGRA210_MBDRC_RATIO_1ST_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_RATIO_2ND, + TEGRA210_MBDRC_RATIO_2ND_MASK, + params->ratio[1] << TEGRA210_MBDRC_RATIO_2ND_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_RATIO_3RD, + TEGRA210_MBDRC_RATIO_3RD_MASK, + params->ratio[2] << TEGRA210_MBDRC_RATIO_3RD_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_RATIO_4TH, + TEGRA210_MBDRC_RATIO_4TH_MASK, + params->ratio[3] << TEGRA210_MBDRC_RATIO_4TH_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_RATIO_5TH, + TEGRA210_MBDRC_RATIO_5TH_MASK, + params->ratio[4] << TEGRA210_MBDRC_RATIO_5TH_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_MAKEUP_GAIN, + TEGRA210_MBDRC_MAKEUP_GAIN_MASK, + params->makeup_gain << + TEGRA210_MBDRC_MAKEUP_GAIN_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_INIT_GAIN, + TEGRA210_MBDRC_INIT_GAIN_MASK, + params->gain_init << + TEGRA210_MBDRC_INIT_GAIN_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_GAIN_ATTACK, + TEGRA210_MBDRC_GAIN_ATTACK_MASK, + params->gain_attack_tc << + TEGRA210_MBDRC_GAIN_ATTACK_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_GAIN_RELEASE, + TEGRA210_MBDRC_GAIN_RELEASE_MASK, + params->gain_release_tc << + TEGRA210_MBDRC_GAIN_RELEASE_SHIFT); + + regmap_update_bits(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_FAST_RELEASE, + TEGRA210_MBDRC_FAST_RELEASE_MASK, + params->fast_release_tc << + TEGRA210_MBDRC_FAST_RELEASE_SHIFT); + + tegra210_mbdrc_write_ram(ope->mbdrc_regmap, + reg_off + TEGRA210_MBDRC_CFG_RAM_CTRL, + reg_off + TEGRA210_MBDRC_CFG_RAM_DATA, 0, + (u32 *)¶ms->biquad_params[0], + TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5); + } + + pm_runtime_put_sync(cmpnt->dev); + + snd_soc_add_component_controls(cmpnt, tegra210_mbdrc_controls, + ARRAY_SIZE(tegra210_mbdrc_controls)); + + return 0; +} + +int tegra210_mbdrc_regmap_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_ope *ope = dev_get_drvdata(dev); + struct device_node *child; + struct resource mem; + void __iomem *regs; + int err; + + child = of_get_child_by_name(dev->of_node, "dynamic-range-compressor"); + if (!child) + return -ENODEV; + + err = of_address_to_resource(child, 0, &mem); + of_node_put(child); + if (err < 0) { + dev_err(dev, "fail to get MBDRC resource\n"); + return err; + } + + mem.flags = IORESOURCE_MEM; + regs = devm_ioremap_resource(dev, &mem); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + ope->mbdrc_regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_mbdrc_regmap_cfg); + if (IS_ERR(ope->mbdrc_regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(ope->mbdrc_regmap); + } + + regcache_cache_only(ope->mbdrc_regmap, true); + + return 0; +} diff --git a/sound/soc/tegra/tegra210_mbdrc.h b/sound/soc/tegra/tegra210_mbdrc.h new file mode 100644 index 000000000000..4c48da0e1dea --- /dev/null +++ b/sound/soc/tegra/tegra210_mbdrc.h @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_mbdrc.h - Definitions for Tegra210 MBDRC driver + * + * Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_MBDRC_H__ +#define __TEGRA210_MBDRC_H__ + +#include <linux/platform_device.h> +#include <sound/soc.h> + +/* Register offsets from TEGRA210_MBDRC*_BASE */ +#define TEGRA210_MBDRC_SOFT_RESET 0x4 +#define TEGRA210_MBDRC_CG 0x8 +#define TEGRA210_MBDRC_STATUS 0xc +#define TEGRA210_MBDRC_CFG 0x28 +#define TEGRA210_MBDRC_CHANNEL_MASK 0x2c +#define TEGRA210_MBDRC_MASTER_VOL 0x30 +#define TEGRA210_MBDRC_FAST_FACTOR 0x34 + +#define TEGRA210_MBDRC_FILTER_COUNT 3 +#define TEGRA210_MBDRC_FILTER_PARAM_STRIDE 0x4 + +#define TEGRA210_MBDRC_IIR_CFG 0x38 +#define TEGRA210_MBDRC_IN_ATTACK 0x44 +#define TEGRA210_MBDRC_IN_RELEASE 0x50 +#define TEGRA210_MBDRC_FAST_ATTACK 0x5c +#define TEGRA210_MBDRC_IN_THRESHOLD 0x68 +#define TEGRA210_MBDRC_OUT_THRESHOLD 0x74 +#define TEGRA210_MBDRC_RATIO_1ST 0x80 +#define TEGRA210_MBDRC_RATIO_2ND 0x8c +#define TEGRA210_MBDRC_RATIO_3RD 0x98 +#define TEGRA210_MBDRC_RATIO_4TH 0xa4 +#define TEGRA210_MBDRC_RATIO_5TH 0xb0 +#define TEGRA210_MBDRC_MAKEUP_GAIN 0xbc +#define TEGRA210_MBDRC_INIT_GAIN 0xc8 +#define TEGRA210_MBDRC_GAIN_ATTACK 0xd4 +#define TEGRA210_MBDRC_GAIN_RELEASE 0xe0 +#define TEGRA210_MBDRC_FAST_RELEASE 0xec +#define TEGRA210_MBDRC_CFG_RAM_CTRL 0xf8 +#define TEGRA210_MBDRC_CFG_RAM_DATA 0x104 + +#define TEGRA210_MBDRC_MAX_REG (TEGRA210_MBDRC_CFG_RAM_DATA + \ + (TEGRA210_MBDRC_FILTER_PARAM_STRIDE * \ + (TEGRA210_MBDRC_FILTER_COUNT - 1))) + +/* Fields for TEGRA210_MBDRC_CFG */ +#define TEGRA210_MBDRC_CFG_RMS_OFFSET_SHIFT 16 +#define TEGRA210_MBDRC_CFG_RMS_OFFSET_MASK (0x1ff << TEGRA210_MBDRC_CFG_RMS_OFFSET_SHIFT) + +#define TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT 14 +#define TEGRA210_MBDRC_CFG_PEAK_RMS_MASK (0x1 << TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT) +#define TEGRA210_MBDRC_CFG_PEAK (1 << TEGRA210_MBDRC_CFG_PEAK_RMS_SHIFT) + +#define TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT 13 +#define TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_MASK (0x1 << TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT) +#define TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_FLEX (1 << TEGRA210_MBDRC_CFG_FILTER_STRUCTURE_SHIFT) + +#define TEGRA210_MBDRC_CFG_SHIFT_CTRL_SHIFT 8 +#define TEGRA210_MBDRC_CFG_SHIFT_CTRL_MASK (0x1f << TEGRA210_MBDRC_CFG_SHIFT_CTRL_SHIFT) + +#define TEGRA210_MBDRC_CFG_FRAME_SIZE_SHIFT 4 +#define TEGRA210_MBDRC_CFG_FRAME_SIZE_MASK (0xf << TEGRA210_MBDRC_CFG_FRAME_SIZE_SHIFT) + +#define TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT 0 +#define TEGRA210_MBDRC_CFG_MBDRC_MODE_MASK (0x3 << TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT) +#define TEGRA210_MBDRC_CFG_MBDRC_MODE_BYPASS (0 << TEGRA210_MBDRC_CFG_MBDRC_MODE_SHIFT) + +/* Fields for TEGRA210_MBDRC_CHANNEL_MASK */ +#define TEGRA210_MBDRC_CHANNEL_MASK_SHIFT 0 +#define TEGRA210_MBDRC_CHANNEL_MASK_MASK (0xff << TEGRA210_MBDRC_CHANNEL_MASK_SHIFT) + +/* Fields for TEGRA210_MBDRC_MASTER_VOL */ +#define TEGRA210_MBDRC_MASTER_VOL_SHIFT 23 +#define TEGRA210_MBDRC_MASTER_VOL_MIN -256 +#define TEGRA210_MBDRC_MASTER_VOL_MAX 256 + +/* Fields for TEGRA210_MBDRC_FAST_FACTOR */ +#define TEGRA210_MBDRC_FAST_FACTOR_RELEASE_SHIFT 16 +#define TEGRA210_MBDRC_FAST_FACTOR_RELEASE_MASK (0xffff << TEGRA210_MBDRC_FAST_FACTOR_RELEASE_SHIFT) + +#define TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT 0 +#define TEGRA210_MBDRC_FAST_FACTOR_ATTACK_MASK (0xffff << TEGRA210_MBDRC_FAST_FACTOR_ATTACK_SHIFT) + +/* Fields for TEGRA210_MBDRC_IIR_CFG */ +#define TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_SHIFT 0 +#define TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_MASK (0xf << TEGRA210_MBDRC_IIR_CFG_NUM_STAGES_SHIFT) + +/* Fields for TEGRA210_MBDRC_IN_ATTACK */ +#define TEGRA210_MBDRC_IN_ATTACK_TC_SHIFT 0 +#define TEGRA210_MBDRC_IN_ATTACK_TC_MASK (0xffffffff << TEGRA210_MBDRC_IN_ATTACK_TC_SHIFT) + +/* Fields for TEGRA210_MBDRC_IN_RELEASE */ +#define TEGRA210_MBDRC_IN_RELEASE_TC_SHIFT 0 +#define TEGRA210_MBDRC_IN_RELEASE_TC_MASK (0xffffffff << TEGRA210_MBDRC_IN_RELEASE_TC_SHIFT) + +/* Fields for TEGRA210_MBDRC_FAST_ATTACK */ +#define TEGRA210_MBDRC_FAST_ATTACK_TC_SHIFT 0 +#define TEGRA210_MBDRC_FAST_ATTACK_TC_MASK (0xffffffff << TEGRA210_MBDRC_FAST_ATTACK_TC_SHIFT) + +/* Fields for TEGRA210_MBDRC_IN_THRESHOLD / TEGRA210_MBDRC_OUT_THRESHOLD */ +#define TEGRA210_MBDRC_THRESH_4TH_SHIFT 24 +#define TEGRA210_MBDRC_THRESH_4TH_MASK (0xff << TEGRA210_MBDRC_THRESH_4TH_SHIFT) + +#define TEGRA210_MBDRC_THRESH_3RD_SHIFT 16 +#define TEGRA210_MBDRC_THRESH_3RD_MASK (0xff << TEGRA210_MBDRC_THRESH_3RD_SHIFT) + +#define TEGRA210_MBDRC_THRESH_2ND_SHIFT 8 +#define TEGRA210_MBDRC_THRESH_2ND_MASK (0xff << TEGRA210_MBDRC_THRESH_2ND_SHIFT) + +#define TEGRA210_MBDRC_THRESH_1ST_SHIFT 0 +#define TEGRA210_MBDRC_THRESH_1ST_MASK (0xff << TEGRA210_MBDRC_THRESH_1ST_SHIFT) + +/* Fields for TEGRA210_MBDRC_RATIO_1ST */ +#define TEGRA210_MBDRC_RATIO_1ST_SHIFT 0 +#define TEGRA210_MBDRC_RATIO_1ST_MASK (0xffff << TEGRA210_MBDRC_RATIO_1ST_SHIFT) + +/* Fields for TEGRA210_MBDRC_RATIO_2ND */ +#define TEGRA210_MBDRC_RATIO_2ND_SHIFT 0 +#define TEGRA210_MBDRC_RATIO_2ND_MASK (0xffff << TEGRA210_MBDRC_RATIO_2ND_SHIFT) + +/* Fields for TEGRA210_MBDRC_RATIO_3RD */ +#define TEGRA210_MBDRC_RATIO_3RD_SHIFT 0 +#define TEGRA210_MBDRC_RATIO_3RD_MASK (0xffff << TEGRA210_MBDRC_RATIO_3RD_SHIFT) + +/* Fields for TEGRA210_MBDRC_RATIO_4TH */ +#define TEGRA210_MBDRC_RATIO_4TH_SHIFT 0 +#define TEGRA210_MBDRC_RATIO_4TH_MASK (0xffff << TEGRA210_MBDRC_RATIO_4TH_SHIFT) + +/* Fields for TEGRA210_MBDRC_RATIO_5TH */ +#define TEGRA210_MBDRC_RATIO_5TH_SHIFT 0 +#define TEGRA210_MBDRC_RATIO_5TH_MASK (0xffff << TEGRA210_MBDRC_RATIO_5TH_SHIFT) + +/* Fields for TEGRA210_MBDRC_MAKEUP_GAIN */ +#define TEGRA210_MBDRC_MAKEUP_GAIN_SHIFT 0 +#define TEGRA210_MBDRC_MAKEUP_GAIN_MASK (0x3f << TEGRA210_MBDRC_MAKEUP_GAIN_SHIFT) + +/* Fields for TEGRA210_MBDRC_INIT_GAIN */ +#define TEGRA210_MBDRC_INIT_GAIN_SHIFT 0 +#define TEGRA210_MBDRC_INIT_GAIN_MASK (0xffffffff << TEGRA210_MBDRC_INIT_GAIN_SHIFT) + +/* Fields for TEGRA210_MBDRC_GAIN_ATTACK */ +#define TEGRA210_MBDRC_GAIN_ATTACK_SHIFT 0 +#define TEGRA210_MBDRC_GAIN_ATTACK_MASK (0xffffffff << TEGRA210_MBDRC_GAIN_ATTACK_SHIFT) + +/* Fields for TEGRA210_MBDRC_GAIN_RELEASE */ +#define TEGRA210_MBDRC_GAIN_RELEASE_SHIFT 0 +#define TEGRA210_MBDRC_GAIN_RELEASE_MASK (0xffffffff << TEGRA210_MBDRC_GAIN_RELEASE_SHIFT) + +/* Fields for TEGRA210_MBDRC_FAST_RELEASE */ +#define TEGRA210_MBDRC_FAST_RELEASE_SHIFT 0 +#define TEGRA210_MBDRC_FAST_RELEASE_MASK (0xffffffff << TEGRA210_MBDRC_FAST_RELEASE_SHIFT) + +#define TEGRA210_MBDRC_RAM_CTRL_RW_READ 0 +#define TEGRA210_MBDRC_RAM_CTRL_RW_WRITE (1 << 14) +#define TEGRA210_MBDRC_RAM_CTRL_ADDR_INIT_EN (1 << 13) +#define TEGRA210_MBDRC_RAM_CTRL_SEQ_ACCESS_EN (1 << 12) +#define TEGRA210_MBDRC_RAM_CTRL_RAM_ADDR_MASK 0x1ff + +/* + * Order and size of each structure element for following structures should not + * be altered size order of elements and their size are based on PEQ co-eff ram + * and shift ram layout. + */ +#define TEGRA210_MBDRC_THRESHOLD_NUM 4 +#define TEGRA210_MBDRC_RATIO_NUM (TEGRA210_MBDRC_THRESHOLD_NUM + 1) +#define TEGRA210_MBDRC_MAX_BIQUAD_STAGES 8 + +/* Order of these enums are same as the order of band specific hw registers */ +enum { + MBDRC_LOW_BAND, + MBDRC_MID_BAND, + MBDRC_HIGH_BAND, + MBDRC_NUM_BAND, +}; + +struct tegra210_mbdrc_band_params { + u32 band; + u32 iir_stages; + u32 in_attack_tc; + u32 in_release_tc; + u32 fast_attack_tc; + u32 in_threshold[TEGRA210_MBDRC_THRESHOLD_NUM]; + u32 out_threshold[TEGRA210_MBDRC_THRESHOLD_NUM]; + u32 ratio[TEGRA210_MBDRC_RATIO_NUM]; + u32 makeup_gain; + u32 gain_init; + u32 gain_attack_tc; + u32 gain_release_tc; + u32 fast_release_tc; + /* For biquad_params[][5] order of coeff is b0, b1, a0, a1, a2 */ + u32 biquad_params[TEGRA210_MBDRC_MAX_BIQUAD_STAGES * 5]; +}; + +struct tegra210_mbdrc_config { + unsigned int mode; + unsigned int rms_off; + unsigned int peak_rms_mode; + unsigned int fliter_structure; + unsigned int shift_ctrl; + unsigned int frame_size; + unsigned int channel_mask; + unsigned int fa_factor; /* Fast attack factor */ + unsigned int fr_factor; /* Fast release factor */ + struct tegra210_mbdrc_band_params band_params[MBDRC_NUM_BAND]; +}; + +int tegra210_mbdrc_regmap_init(struct platform_device *pdev); +int tegra210_mbdrc_component_init(struct snd_soc_component *cmpnt); +int tegra210_mbdrc_hw_params(struct snd_soc_component *cmpnt); + +#endif diff --git a/sound/soc/tegra/tegra210_mvc.c b/sound/soc/tegra/tegra210_mvc.c index acf59328dcb6..725385e17d84 100644 --- a/sound/soc/tegra/tegra210_mvc.c +++ b/sound/soc/tegra/tegra210_mvc.c @@ -108,67 +108,152 @@ static void tegra210_mvc_conv_vol(struct tegra210_mvc *mvc, u8 chan, s32 val) } } -static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static u32 tegra210_mvc_get_ctrl_reg(struct snd_kcontrol *kcontrol) { struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); - u8 mute_mask; u32 val; pm_runtime_get_sync(cmpnt->dev); regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &val); pm_runtime_put(cmpnt->dev); - mute_mask = (val >> TEGRA210_MVC_MUTE_SHIFT) & - TEGRA210_MUTE_MASK_EN; + return val; +} + +static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 val = tegra210_mvc_get_ctrl_reg(kcontrol); + u8 mute_mask = TEGRA210_GET_MUTE_VAL(val); - ucontrol->value.integer.value[0] = mute_mask; + /* + * If per channel control is enabled, then return + * exact mute/unmute setting of all channels. + * + * Else report setting based on CH0 bit to reflect + * the correct HW state. + */ + if (val & TEGRA210_MVC_PER_CHAN_CTRL_EN) { + ucontrol->value.integer.value[0] = mute_mask; + } else { + if (mute_mask & TEGRA210_MVC_CH0_MUTE_EN) + ucontrol->value.integer.value[0] = + TEGRA210_MUTE_MASK_EN; + else + ucontrol->value.integer.value[0] = 0; + } return 0; } -static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int tegra210_mvc_get_master_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 val = tegra210_mvc_get_ctrl_reg(kcontrol); + u8 mute_mask = TEGRA210_GET_MUTE_VAL(val); + + /* + * If per channel control is disabled, then return + * master mute/unmute setting based on CH0 bit. + * + * Else report settings based on state of all + * channels. + */ + if (!(val & TEGRA210_MVC_PER_CHAN_CTRL_EN)) { + ucontrol->value.integer.value[0] = + mute_mask & TEGRA210_MVC_CH0_MUTE_EN; + } else { + if (mute_mask == TEGRA210_MUTE_MASK_EN) + ucontrol->value.integer.value[0] = + TEGRA210_MVC_CH0_MUTE_EN; + else + ucontrol->value.integer.value[0] = 0; + } + + return 0; +} + +static int tegra210_mvc_volume_switch_timeout(struct snd_soc_component *cmpnt) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); - unsigned int value; - u8 new_mask, old_mask; + u32 value; int err; - pm_runtime_get_sync(cmpnt->dev); - - /* Check if VOLUME_SWITCH is triggered */ err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH, value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK), 10, 10000); if (err < 0) - goto end; + dev_err(cmpnt->dev, + "Volume switch trigger is still active, err = %d\n", + err); - regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &value); + return err; +} - old_mask = (value >> TEGRA210_MVC_MUTE_SHIFT) & TEGRA210_MUTE_MASK_EN; - new_mask = ucontrol->value.integer.value[0]; +static int tegra210_mvc_update_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + bool per_chan_ctrl) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + u32 mute_val = ucontrol->value.integer.value[0]; + u32 per_ch_ctrl_val; + bool change = false; + int err; - if (new_mask == old_mask) { - err = 0; + pm_runtime_get_sync(cmpnt->dev); + + err = tegra210_mvc_volume_switch_timeout(cmpnt); + if (err < 0) goto end; + + if (per_chan_ctrl) { + per_ch_ctrl_val = TEGRA210_MVC_PER_CHAN_CTRL_EN; + } else { + per_ch_ctrl_val = 0; + + if (mute_val) + mute_val = TEGRA210_MUTE_MASK_EN; } - err = regmap_update_bits(mvc->regmap, mc->reg, + regmap_update_bits_check(mvc->regmap, TEGRA210_MVC_CTRL, TEGRA210_MVC_MUTE_MASK, - new_mask << TEGRA210_MVC_MUTE_SHIFT); - if (err < 0) - goto end; + mute_val << TEGRA210_MVC_MUTE_SHIFT, + &change); - err = 1; + if (change) { + regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL, + TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, + per_ch_ctrl_val); + + regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH, + TEGRA210_MVC_VOLUME_SWITCH_MASK, + TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); + } end: pm_runtime_put(cmpnt->dev); - return err; + + if (err < 0) + return err; + + if (change) + return 1; + + return 0; +} + +static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return tegra210_mvc_update_mute(kcontrol, ucontrol, true); +} + +static int tegra210_mvc_put_master_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return tegra210_mvc_update_mute(kcontrol, ucontrol, false); } static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol, @@ -178,7 +263,7 @@ static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); - u8 chan = (mc->reg - TEGRA210_MVC_TARGET_VOL) / REG_SIZE; + u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL); s32 val = mvc->volume[chan]; if (mvc->curve_type == CURVE_POLY) { @@ -193,44 +278,55 @@ static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol, return 0; } -static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int tegra210_mvc_get_master_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return tegra210_mvc_get_vol(kcontrol, ucontrol); +} + +static int tegra210_mvc_update_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + bool per_ch_enable) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); - unsigned int reg = mc->reg; - unsigned int value; - u8 chan; - int err, old_volume; + u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL); + int old_volume = mvc->volume[chan]; + int err, i; pm_runtime_get_sync(cmpnt->dev); - /* Check if VOLUME_SWITCH is triggered */ - err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH, - value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK), - 10, 10000); + err = tegra210_mvc_volume_switch_timeout(cmpnt); if (err < 0) goto end; - chan = (reg - TEGRA210_MVC_TARGET_VOL) / REG_SIZE; - old_volume = mvc->volume[chan]; - - tegra210_mvc_conv_vol(mvc, chan, - ucontrol->value.integer.value[0]); + tegra210_mvc_conv_vol(mvc, chan, ucontrol->value.integer.value[0]); if (mvc->volume[chan] == old_volume) { err = 0; goto end; } + if (per_ch_enable) { + regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL, + TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, + TEGRA210_MVC_PER_CHAN_CTRL_EN); + } else { + regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL, + TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, 0); + + for (i = 1; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) + mvc->volume[i] = mvc->volume[chan]; + } + /* Configure init volume same as target volume */ regmap_write(mvc->regmap, TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, chan), mvc->volume[chan]); - regmap_write(mvc->regmap, reg, mvc->volume[chan]); + regmap_write(mvc->regmap, mc->reg, mvc->volume[chan]); regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH, TEGRA210_MVC_VOLUME_SWITCH_MASK, @@ -240,9 +336,22 @@ static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol, end: pm_runtime_put(cmpnt->dev); + return err; } +static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return tegra210_mvc_update_vol(kcontrol, ucontrol, true); +} + +static int tegra210_mvc_put_master_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return tegra210_mvc_update_vol(kcontrol, ucontrol, false); +} + static void tegra210_mvc_reset_vol_settings(struct tegra210_mvc *mvc, struct device *dev) { @@ -436,6 +545,16 @@ static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = { TEGRA210_MVC_CTRL, 0, TEGRA210_MUTE_MASK_EN, 0, tegra210_mvc_get_mute, tegra210_mvc_put_mute), + /* Master volume */ + SOC_SINGLE_EXT("Volume", TEGRA210_MVC_TARGET_VOL, 0, 16000, 0, + tegra210_mvc_get_master_vol, + tegra210_mvc_put_master_vol), + + /* Master mute */ + SOC_SINGLE_EXT("Mute", TEGRA210_MVC_CTRL, 0, 1, 0, + tegra210_mvc_get_master_mute, + tegra210_mvc_put_master_mute), + SOC_ENUM_EXT("Curve Type", tegra210_mvc_curve_type_ctrl, tegra210_mvc_get_curve_type, tegra210_mvc_put_curve_type), }; diff --git a/sound/soc/tegra/tegra210_mvc.h b/sound/soc/tegra/tegra210_mvc.h index def29c4c7257..d775335dc60b 100644 --- a/sound/soc/tegra/tegra210_mvc.h +++ b/sound/soc/tegra/tegra210_mvc.h @@ -59,6 +59,7 @@ #define TEGRA210_MUTE_MASK_EN 0xff #define TEGRA210_MVC_MUTE_MASK (TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT) #define TEGRA210_MVC_MUTE_EN (TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT) +#define TEGRA210_MVC_CH0_MUTE_EN 1 #define TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT 30 #define TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK (1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT) @@ -92,6 +93,10 @@ #define TEGRA210_MVC_MAX_CHAN_COUNT 8 #define TEGRA210_MVC_REG_OFFSET(reg, i) (reg + (REG_SIZE * i)) +#define TEGRA210_MVC_GET_CHAN(reg, base) (((reg) - (base)) / REG_SIZE) + +#define TEGRA210_GET_MUTE_VAL(val) (((val) >> TEGRA210_MVC_MUTE_SHIFT) & TEGRA210_MUTE_MASK_EN) + #define NUM_GAIN_POLY_COEFFS 9 enum { diff --git a/sound/soc/tegra/tegra210_ope.c b/sound/soc/tegra/tegra210_ope.c new file mode 100644 index 000000000000..3dd2bdec657b --- /dev/null +++ b/sound/soc/tegra/tegra210_ope.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_ope.c - Tegra210 OPE driver +// +// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_mbdrc.h" +#include "tegra210_ope.h" +#include "tegra210_peq.h" +#include "tegra_cif.h" + +static const struct reg_default tegra210_ope_reg_defaults[] = { + { TEGRA210_OPE_RX_INT_MASK, 0x00000001}, + { TEGRA210_OPE_RX_CIF_CTRL, 0x00007700}, + { TEGRA210_OPE_TX_INT_MASK, 0x00000001}, + { TEGRA210_OPE_TX_CIF_CTRL, 0x00007700}, + { TEGRA210_OPE_CG, 0x1}, +}; + +static int tegra210_ope_set_audio_cif(struct tegra210_ope *ope, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + int channels, audio_bits; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + if (channels < 2) + return -EINVAL; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + + tegra_set_cif(ope->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_ope_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra210_ope *ope = snd_soc_dai_get_drvdata(dai); + int err; + + /* Set RX and TX CIF */ + err = tegra210_ope_set_audio_cif(ope, params, + TEGRA210_OPE_RX_CIF_CTRL); + if (err) { + dev_err(dev, "Can't set OPE RX CIF: %d\n", err); + return err; + } + + err = tegra210_ope_set_audio_cif(ope, params, + TEGRA210_OPE_TX_CIF_CTRL); + if (err) { + dev_err(dev, "Can't set OPE TX CIF: %d\n", err); + return err; + } + + tegra210_mbdrc_hw_params(dai->component); + + return err; +} + +static int tegra210_ope_component_probe(struct snd_soc_component *cmpnt) +{ + struct tegra210_ope *ope = dev_get_drvdata(cmpnt->dev); + + tegra210_peq_component_init(cmpnt); + tegra210_mbdrc_component_init(cmpnt); + + /* + * The OPE, PEQ and MBDRC functionalities are combined under one + * device registered by OPE driver. In fact OPE HW block includes + * sub blocks PEQ and MBDRC. However driver registers separate + * regmap interfaces for each of these. ASoC core depends on + * dev_get_regmap() to populate the regmap field for a given ASoC + * component. A component can have one regmap reference and since + * the DAPM routes depend on OPE regmap only, below explicit + * assignment is done to highlight this. This is needed for ASoC + * core to access correct regmap during DAPM path setup. + */ + snd_soc_component_init_regmap(cmpnt, ope->regmap); + + return 0; +} + +static const struct snd_soc_dai_ops tegra210_ope_dai_ops = { + .hw_params = tegra210_ope_hw_params, +}; + +static struct snd_soc_dai_driver tegra210_ope_dais[] = { + { + .name = "OPE-RX-CIF", + .playback = { + .stream_name = "RX-CIF-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "RX-CIF-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, + { + .name = "OPE-TX-CIF", + .playback = { + .stream_name = "TX-CIF-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "TX-CIF-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra210_ope_dai_ops, + } +}; + +static const struct snd_soc_dapm_widget tegra210_ope_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_OPE_ENABLE, + TEGRA210_OPE_EN_SHIFT, 0), +}; + +#define OPE_ROUTES(sname) \ + { "RX XBAR-" sname, NULL, "XBAR-TX" }, \ + { "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \ + { "RX", NULL, "RX-CIF-" sname }, \ + { "TX-CIF-" sname, NULL, "TX" }, \ + { "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \ + { "XBAR-RX", NULL, "TX XBAR-" sname } + +static const struct snd_soc_dapm_route tegra210_ope_routes[] = { + { "TX", NULL, "RX" }, + OPE_ROUTES("Playback"), + OPE_ROUTES("Capture"), +}; + +static const char * const tegra210_ope_data_dir_text[] = { + "MBDRC to PEQ", + "PEQ to MBDRC" +}; + +static const struct soc_enum tegra210_ope_data_dir_enum = + SOC_ENUM_SINGLE(TEGRA210_OPE_DIR, TEGRA210_OPE_DIR_SHIFT, + 2, tegra210_ope_data_dir_text); + +static int tegra210_ope_get_data_dir(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + + ucontrol->value.enumerated.item[0] = ope->data_dir; + + return 0; +} + +static int tegra210_ope_put_data_dir(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + unsigned int value = ucontrol->value.enumerated.item[0]; + + if (value == ope->data_dir) + return 0; + + ope->data_dir = value; + + return 1; +} + +static const struct snd_kcontrol_new tegra210_ope_controls[] = { + SOC_ENUM_EXT("Data Flow Direction", tegra210_ope_data_dir_enum, + tegra210_ope_get_data_dir, tegra210_ope_put_data_dir), +}; + +static const struct snd_soc_component_driver tegra210_ope_cmpnt = { + .probe = tegra210_ope_component_probe, + .dapm_widgets = tegra210_ope_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_ope_widgets), + .dapm_routes = tegra210_ope_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_ope_routes), + .controls = tegra210_ope_controls, + .num_controls = ARRAY_SIZE(tegra210_ope_controls), +}; + +static bool tegra210_ope_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_OPE_RX_INT_MASK ... TEGRA210_OPE_RX_CIF_CTRL: + case TEGRA210_OPE_TX_INT_MASK ... TEGRA210_OPE_TX_CIF_CTRL: + case TEGRA210_OPE_ENABLE ... TEGRA210_OPE_CG: + case TEGRA210_OPE_DIR: + return true; + default: + return false; + } +} + +static bool tegra210_ope_rd_reg(struct device *dev, unsigned int reg) +{ + if (tegra210_ope_wr_reg(dev, reg)) + return true; + + switch (reg) { + case TEGRA210_OPE_RX_STATUS: + case TEGRA210_OPE_RX_INT_STATUS: + case TEGRA210_OPE_TX_STATUS: + case TEGRA210_OPE_TX_INT_STATUS: + case TEGRA210_OPE_STATUS: + case TEGRA210_OPE_INT_STATUS: + return true; + default: + return false; + } +} + +static bool tegra210_ope_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_OPE_RX_STATUS: + case TEGRA210_OPE_RX_INT_STATUS: + case TEGRA210_OPE_TX_STATUS: + case TEGRA210_OPE_TX_INT_STATUS: + case TEGRA210_OPE_SOFT_RESET: + case TEGRA210_OPE_STATUS: + case TEGRA210_OPE_INT_STATUS: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra210_ope_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_OPE_DIR, + .writeable_reg = tegra210_ope_wr_reg, + .readable_reg = tegra210_ope_rd_reg, + .volatile_reg = tegra210_ope_volatile_reg, + .reg_defaults = tegra210_ope_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_ope_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static int tegra210_ope_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_ope *ope; + void __iomem *regs; + int err; + + ope = devm_kzalloc(dev, sizeof(*ope), GFP_KERNEL); + if (!ope) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + ope->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_ope_regmap_config); + if (IS_ERR(ope->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(ope->regmap); + } + + regcache_cache_only(ope->regmap, true); + + dev_set_drvdata(dev, ope); + + err = tegra210_peq_regmap_init(pdev); + if (err < 0) { + dev_err(dev, "PEQ init failed\n"); + return err; + } + + err = tegra210_mbdrc_regmap_init(pdev); + if (err < 0) { + dev_err(dev, "MBDRC init failed\n"); + return err; + } + + err = devm_snd_soc_register_component(dev, &tegra210_ope_cmpnt, + tegra210_ope_dais, + ARRAY_SIZE(tegra210_ope_dais)); + if (err) { + dev_err(dev, "can't register OPE component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra210_ope_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused tegra210_ope_runtime_suspend(struct device *dev) +{ + struct tegra210_ope *ope = dev_get_drvdata(dev); + + tegra210_peq_save(ope->peq_regmap, ope->peq_biquad_gains, + ope->peq_biquad_shifts); + + regcache_cache_only(ope->mbdrc_regmap, true); + regcache_cache_only(ope->peq_regmap, true); + regcache_cache_only(ope->regmap, true); + + regcache_mark_dirty(ope->regmap); + regcache_mark_dirty(ope->peq_regmap); + regcache_mark_dirty(ope->mbdrc_regmap); + + return 0; +} + +static int __maybe_unused tegra210_ope_runtime_resume(struct device *dev) +{ + struct tegra210_ope *ope = dev_get_drvdata(dev); + + regcache_cache_only(ope->regmap, false); + regcache_cache_only(ope->peq_regmap, false); + regcache_cache_only(ope->mbdrc_regmap, false); + + regcache_sync(ope->regmap); + regcache_sync(ope->peq_regmap); + regcache_sync(ope->mbdrc_regmap); + + tegra210_peq_restore(ope->peq_regmap, ope->peq_biquad_gains, + ope->peq_biquad_shifts); + + return 0; +} + +static const struct dev_pm_ops tegra210_ope_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_ope_runtime_suspend, + tegra210_ope_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct of_device_id tegra210_ope_of_match[] = { + { .compatible = "nvidia,tegra210-ope" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_ope_of_match); + +static struct platform_driver tegra210_ope_driver = { + .driver = { + .name = "tegra210-ope", + .of_match_table = tegra210_ope_of_match, + .pm = &tegra210_ope_pm_ops, + }, + .probe = tegra210_ope_probe, + .remove = tegra210_ope_remove, +}; +module_platform_driver(tegra210_ope_driver) + +MODULE_AUTHOR("Sumit Bhattacharya <sumitb@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 OPE ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra210_ope.h b/sound/soc/tegra/tegra210_ope.h new file mode 100644 index 000000000000..2835af6ce631 --- /dev/null +++ b/sound/soc/tegra/tegra210_ope.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_ope.h - Definitions for Tegra210 OPE driver + * + * Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_OPE_H__ +#define __TEGRA210_OPE_H__ + +#include <linux/regmap.h> +#include <sound/soc.h> + +#include "tegra210_peq.h" + +/* + * OPE_RX registers are with respect to XBAR. + * The data comes from XBAR to OPE + */ +#define TEGRA210_OPE_RX_STATUS 0xc +#define TEGRA210_OPE_RX_INT_STATUS 0x10 +#define TEGRA210_OPE_RX_INT_MASK 0x14 +#define TEGRA210_OPE_RX_INT_SET 0x18 +#define TEGRA210_OPE_RX_INT_CLEAR 0x1c +#define TEGRA210_OPE_RX_CIF_CTRL 0x20 + +/* + * OPE_TX registers are with respect to XBAR. + * The data goes out from OPE to XBAR + */ +#define TEGRA210_OPE_TX_STATUS 0x4c +#define TEGRA210_OPE_TX_INT_STATUS 0x50 +#define TEGRA210_OPE_TX_INT_MASK 0x54 +#define TEGRA210_OPE_TX_INT_SET 0x58 +#define TEGRA210_OPE_TX_INT_CLEAR 0x5c +#define TEGRA210_OPE_TX_CIF_CTRL 0x60 + +/* OPE Gloabal registers */ +#define TEGRA210_OPE_ENABLE 0x80 +#define TEGRA210_OPE_SOFT_RESET 0x84 +#define TEGRA210_OPE_CG 0x88 +#define TEGRA210_OPE_STATUS 0x8c +#define TEGRA210_OPE_INT_STATUS 0x90 +#define TEGRA210_OPE_DIR 0x94 + +/* Fields for TEGRA210_OPE_ENABLE */ +#define TEGRA210_OPE_EN_SHIFT 0 +#define TEGRA210_OPE_EN (1 << TEGRA210_OPE_EN_SHIFT) + +/* Fields for TEGRA210_OPE_SOFT_RESET */ +#define TEGRA210_OPE_SOFT_RESET_SHIFT 0 +#define TEGRA210_OPE_SOFT_RESET_EN (1 << TEGRA210_OPE_SOFT_RESET_SHIFT) + +#define TEGRA210_OPE_DIR_SHIFT 0 + +struct tegra210_ope { + struct regmap *regmap; + struct regmap *peq_regmap; + struct regmap *mbdrc_regmap; + u32 peq_biquad_gains[TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH]; + u32 peq_biquad_shifts[TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH]; + unsigned int data_dir; +}; + +/* Extension of soc_bytes structure defined in sound/soc.h */ +struct tegra_soc_bytes { + struct soc_bytes soc; + u32 shift; /* Used as offset for AHUB RAM related programing */ +}; + +/* Utility structures for using mixer control of type snd_soc_bytes */ +#define TEGRA_SOC_BYTES_EXT(xname, xbase, xregs, xshift, xmask, \ + xhandler_get, xhandler_put, xinfo) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = xinfo, \ + .get = xhandler_get, \ + .put = xhandler_put, \ + .private_value = ((unsigned long)&(struct tegra_soc_bytes) \ + { \ + .soc.base = xbase, \ + .soc.num_regs = xregs, \ + .soc.mask = xmask, \ + .shift = xshift \ + }) \ +} + +#endif diff --git a/sound/soc/tegra/tegra210_peq.c b/sound/soc/tegra/tegra210_peq.c new file mode 100644 index 000000000000..205d956abb42 --- /dev/null +++ b/sound/soc/tegra/tegra210_peq.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_peq.c - Tegra210 PEQ driver +// +// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_ope.h" +#include "tegra210_peq.h" + +static const struct reg_default tegra210_peq_reg_defaults[] = { + { TEGRA210_PEQ_CFG, 0x00000013}, + { TEGRA210_PEQ_CFG_RAM_CTRL, 0x00004000}, + { TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, 0x00004000}, +}; + +static const u32 biquad_init_gains[TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH] = { + 1495012349, /* Pre-gain */ + + /* Gains : b0, b1, a0, a1, a2 */ + 536870912, -1073741824, 536870912, 2143508246, -1069773768, /* Band-0 */ + 134217728, -265414508, 131766272, 2140402222, -1071252997, /* Band-1 */ + 268435456, -233515765, -33935948, 1839817267, -773826124, /* Band-2 */ + 536870912, -672537913, 139851540, 1886437554, -824433167, /* Band-3 */ + 268435456, -114439279, 173723964, 205743566, 278809729, /* Band-4 */ + 1, 0, 0, 0, 0, /* Band-5 */ + 1, 0, 0, 0, 0, /* Band-6 */ + 1, 0, 0, 0, 0, /* Band-7 */ + 1, 0, 0, 0, 0, /* Band-8 */ + 1, 0, 0, 0, 0, /* Band-9 */ + 1, 0, 0, 0, 0, /* Band-10 */ + 1, 0, 0, 0, 0, /* Band-11 */ + + 963423114, /* Post-gain */ +}; + +static const u32 biquad_init_shifts[TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH] = { + 23, /* Pre-shift */ + 30, 30, 30, 30, 30, 0, 0, 0, 0, 0, 0, 0, /* Shift for bands */ + 28, /* Post-shift */ +}; + +static s32 biquad_coeff_buffer[TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH]; + +static void tegra210_peq_read_ram(struct regmap *regmap, unsigned int reg_ctrl, + unsigned int reg_data, unsigned int ram_offset, + unsigned int *data, size_t size) +{ + unsigned int val; + unsigned int i; + + val = ram_offset & TEGRA210_PEQ_RAM_CTRL_RAM_ADDR_MASK; + val |= TEGRA210_PEQ_RAM_CTRL_ADDR_INIT_EN; + val |= TEGRA210_PEQ_RAM_CTRL_SEQ_ACCESS_EN; + val |= TEGRA210_PEQ_RAM_CTRL_RW_READ; + + regmap_write(regmap, reg_ctrl, val); + + /* + * Since all ahub non-io modules work under same ahub clock it is not + * necessary to check ahub read busy bit after every read. + */ + for (i = 0; i < size; i++) + regmap_read(regmap, reg_data, &data[i]); +} + +static void tegra210_peq_write_ram(struct regmap *regmap, unsigned int reg_ctrl, + unsigned int reg_data, unsigned int ram_offset, + unsigned int *data, size_t size) +{ + unsigned int val; + unsigned int i; + + val = ram_offset & TEGRA210_PEQ_RAM_CTRL_RAM_ADDR_MASK; + val |= TEGRA210_PEQ_RAM_CTRL_ADDR_INIT_EN; + val |= TEGRA210_PEQ_RAM_CTRL_SEQ_ACCESS_EN; + val |= TEGRA210_PEQ_RAM_CTRL_RW_WRITE; + + regmap_write(regmap, reg_ctrl, val); + + for (i = 0; i < size; i++) + regmap_write(regmap, reg_data, data[i]); +} + +static int tegra210_peq_get(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 *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + unsigned int mask = (1 << fls(mc->max)) - 1; + unsigned int val; + + regmap_read(ope->peq_regmap, mc->reg, &val); + + ucontrol->value.integer.value[0] = (val >> mc->shift) & mask; + + if (!mc->invert) + return 0; + + ucontrol->value.integer.value[0] = + mc->max - ucontrol->value.integer.value[0]; + + return 0; +} + +static int tegra210_peq_put(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 *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + unsigned int mask = (1 << fls(mc->max)) - 1; + bool change = false; + unsigned int val; + + val = (ucontrol->value.integer.value[0] & mask); + + if (mc->invert) + val = mc->max - val; + + val = val << mc->shift; + + regmap_update_bits_check(ope->peq_regmap, mc->reg, (mask << mc->shift), + val, &change); + + return change ? 1 : 0; +} + +static int tegra210_peq_ram_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 i, reg_ctrl = params->soc.base; + u32 reg_data = reg_ctrl + cmpnt->val_bytes; + s32 *data = (s32 *)biquad_coeff_buffer; + + pm_runtime_get_sync(cmpnt->dev); + + tegra210_peq_read_ram(ope->peq_regmap, reg_ctrl, reg_data, + params->shift, data, params->soc.num_regs); + + pm_runtime_put_sync(cmpnt->dev); + + for (i = 0; i < params->soc.num_regs; i++) + ucontrol->value.integer.value[i] = (long)data[i]; + + return 0; +} + +static int tegra210_peq_ram_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tegra_soc_bytes *params = (void *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + u32 i, reg_ctrl = params->soc.base; + u32 reg_data = reg_ctrl + cmpnt->val_bytes; + s32 *data = (s32 *)biquad_coeff_buffer; + + for (i = 0; i < params->soc.num_regs; i++) + data[i] = (s32)ucontrol->value.integer.value[i]; + + pm_runtime_get_sync(cmpnt->dev); + + tegra210_peq_write_ram(ope->peq_regmap, reg_ctrl, reg_data, + params->shift, data, params->soc.num_regs); + + pm_runtime_put_sync(cmpnt->dev); + + return 1; +} + +static int tegra210_peq_param_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_bytes *params = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = INT_MIN; + uinfo->value.integer.max = INT_MAX; + uinfo->count = params->num_regs; + + return 0; +} + +#define TEGRA210_PEQ_GAIN_PARAMS_CTRL(chan) \ + TEGRA_SOC_BYTES_EXT("PEQ Channel-" #chan " Biquad Gain Params", \ + TEGRA210_PEQ_CFG_RAM_CTRL, \ + TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH, \ + (TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH * chan), 0xffffffff, \ + tegra210_peq_ram_get, tegra210_peq_ram_put, \ + tegra210_peq_param_info) + +#define TEGRA210_PEQ_SHIFT_PARAMS_CTRL(chan) \ + TEGRA_SOC_BYTES_EXT("PEQ Channel-" #chan " Biquad Shift Params", \ + TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, \ + TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH, \ + (TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH * chan), 0x1f, \ + tegra210_peq_ram_get, tegra210_peq_ram_put, \ + tegra210_peq_param_info) + +static const struct snd_kcontrol_new tegra210_peq_controls[] = { + SOC_SINGLE_EXT("PEQ Active", TEGRA210_PEQ_CFG, + TEGRA210_PEQ_CFG_MODE_SHIFT, 1, 0, + tegra210_peq_get, tegra210_peq_put), + + SOC_SINGLE_EXT("PEQ Biquad Stages", TEGRA210_PEQ_CFG, + TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT, + TEGRA210_PEQ_MAX_BIQUAD_STAGES - 1, 0, + tegra210_peq_get, tegra210_peq_put), + + TEGRA210_PEQ_GAIN_PARAMS_CTRL(0), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(1), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(2), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(3), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(4), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(5), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(6), + TEGRA210_PEQ_GAIN_PARAMS_CTRL(7), + + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(0), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(1), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(2), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(3), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(4), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(5), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(6), + TEGRA210_PEQ_SHIFT_PARAMS_CTRL(7), +}; + +static bool tegra210_peq_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_PEQ_SOFT_RESET: + case TEGRA210_PEQ_CG: + case TEGRA210_PEQ_CFG ... TEGRA210_PEQ_CFG_RAM_SHIFT_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_peq_rd_reg(struct device *dev, unsigned int reg) +{ + if (tegra210_peq_wr_reg(dev, reg)) + return true; + + switch (reg) { + case TEGRA210_PEQ_STATUS: + return true; + default: + return false; + } +} + +static bool tegra210_peq_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_PEQ_SOFT_RESET: + case TEGRA210_PEQ_STATUS: + case TEGRA210_PEQ_CFG_RAM_CTRL ... TEGRA210_PEQ_CFG_RAM_SHIFT_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_peq_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_PEQ_CFG_RAM_DATA: + case TEGRA210_PEQ_CFG_RAM_SHIFT_DATA: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra210_peq_regmap_config = { + .name = "peq", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_PEQ_CFG_RAM_SHIFT_DATA, + .writeable_reg = tegra210_peq_wr_reg, + .readable_reg = tegra210_peq_rd_reg, + .volatile_reg = tegra210_peq_volatile_reg, + .precious_reg = tegra210_peq_precious_reg, + .reg_defaults = tegra210_peq_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_peq_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +void tegra210_peq_restore(struct regmap *regmap, u32 *biquad_gains, + u32 *biquad_shifts) +{ + unsigned int i; + + for (i = 0; i < TEGRA210_PEQ_MAX_CHANNELS; i++) { + tegra210_peq_write_ram(regmap, TEGRA210_PEQ_CFG_RAM_CTRL, + TEGRA210_PEQ_CFG_RAM_DATA, + (i * TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH), + biquad_gains, + TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH); + + tegra210_peq_write_ram(regmap, + TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, + TEGRA210_PEQ_CFG_RAM_SHIFT_DATA, + (i * TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH), + biquad_shifts, + TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH); + + } +} + +void tegra210_peq_save(struct regmap *regmap, u32 *biquad_gains, + u32 *biquad_shifts) +{ + unsigned int i; + + for (i = 0; i < TEGRA210_PEQ_MAX_CHANNELS; i++) { + tegra210_peq_read_ram(regmap, + TEGRA210_PEQ_CFG_RAM_CTRL, + TEGRA210_PEQ_CFG_RAM_DATA, + (i * TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH), + biquad_gains, + TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH); + + tegra210_peq_read_ram(regmap, + TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, + TEGRA210_PEQ_CFG_RAM_SHIFT_DATA, + (i * TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH), + biquad_shifts, + TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH); + } +} + +int tegra210_peq_component_init(struct snd_soc_component *cmpnt) +{ + struct tegra210_ope *ope = snd_soc_component_get_drvdata(cmpnt); + unsigned int i; + + pm_runtime_get_sync(cmpnt->dev); + regmap_update_bits(ope->peq_regmap, TEGRA210_PEQ_CFG, + TEGRA210_PEQ_CFG_MODE_MASK, + 0 << TEGRA210_PEQ_CFG_MODE_SHIFT); + regmap_update_bits(ope->peq_regmap, TEGRA210_PEQ_CFG, + TEGRA210_PEQ_CFG_BIQUAD_STAGES_MASK, + (TEGRA210_PEQ_BIQUAD_INIT_STAGE - 1) << + TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT); + + /* Initialize PEQ AHUB RAM with default params */ + for (i = 0; i < TEGRA210_PEQ_MAX_CHANNELS; i++) { + + /* Set default gain params */ + tegra210_peq_write_ram(ope->peq_regmap, + TEGRA210_PEQ_CFG_RAM_CTRL, + TEGRA210_PEQ_CFG_RAM_DATA, + (i * TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH), + (u32 *)&biquad_init_gains, + TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH); + + /* Set default shift params */ + tegra210_peq_write_ram(ope->peq_regmap, + TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL, + TEGRA210_PEQ_CFG_RAM_SHIFT_DATA, + (i * TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH), + (u32 *)&biquad_init_shifts, + TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH); + + } + + pm_runtime_put_sync(cmpnt->dev); + + snd_soc_add_component_controls(cmpnt, tegra210_peq_controls, + ARRAY_SIZE(tegra210_peq_controls)); + + return 0; +} + +int tegra210_peq_regmap_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_ope *ope = dev_get_drvdata(dev); + struct device_node *child; + struct resource mem; + void __iomem *regs; + int err; + + child = of_get_child_by_name(dev->of_node, "equalizer"); + if (!child) + return -ENODEV; + + err = of_address_to_resource(child, 0, &mem); + of_node_put(child); + if (err < 0) { + dev_err(dev, "fail to get PEQ resource\n"); + return err; + } + + mem.flags = IORESOURCE_MEM; + regs = devm_ioremap_resource(dev, &mem); + if (IS_ERR(regs)) + return PTR_ERR(regs); + ope->peq_regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_peq_regmap_config); + if (IS_ERR(ope->peq_regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(ope->peq_regmap); + } + + regcache_cache_only(ope->peq_regmap, true); + + return 0; +} diff --git a/sound/soc/tegra/tegra210_peq.h b/sound/soc/tegra/tegra210_peq.h new file mode 100644 index 000000000000..6d3de4ff05cc --- /dev/null +++ b/sound/soc/tegra/tegra210_peq.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_peq.h - Definitions for Tegra210 PEQ driver + * + * Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_PEQ_H__ +#define __TEGRA210_PEQ_H__ + +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <sound/soc.h> + +/* Register offsets from PEQ base */ +#define TEGRA210_PEQ_SOFT_RESET 0x0 +#define TEGRA210_PEQ_CG 0x4 +#define TEGRA210_PEQ_STATUS 0x8 +#define TEGRA210_PEQ_CFG 0xc +#define TEGRA210_PEQ_CFG_RAM_CTRL 0x10 +#define TEGRA210_PEQ_CFG_RAM_DATA 0x14 +#define TEGRA210_PEQ_CFG_RAM_SHIFT_CTRL 0x18 +#define TEGRA210_PEQ_CFG_RAM_SHIFT_DATA 0x1c + +/* Fields in TEGRA210_PEQ_CFG */ +#define TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT 2 +#define TEGRA210_PEQ_CFG_BIQUAD_STAGES_MASK (0xf << TEGRA210_PEQ_CFG_BIQUAD_STAGES_SHIFT) + +#define TEGRA210_PEQ_CFG_MODE_SHIFT 0 +#define TEGRA210_PEQ_CFG_MODE_MASK (0x1 << TEGRA210_PEQ_CFG_MODE_SHIFT) + +#define TEGRA210_PEQ_RAM_CTRL_RW_READ 0 +#define TEGRA210_PEQ_RAM_CTRL_RW_WRITE (1 << 14) +#define TEGRA210_PEQ_RAM_CTRL_ADDR_INIT_EN (1 << 13) +#define TEGRA210_PEQ_RAM_CTRL_SEQ_ACCESS_EN (1 << 12) +#define TEGRA210_PEQ_RAM_CTRL_RAM_ADDR_MASK 0x1ff + +/* PEQ register definition ends here */ +#define TEGRA210_PEQ_MAX_BIQUAD_STAGES 12 + +#define TEGRA210_PEQ_MAX_CHANNELS 8 + +#define TEGRA210_PEQ_BIQUAD_INIT_STAGE 5 + +#define TEGRA210_PEQ_GAIN_PARAM_SIZE_PER_CH (2 + TEGRA210_PEQ_MAX_BIQUAD_STAGES * 5) +#define TEGRA210_PEQ_SHIFT_PARAM_SIZE_PER_CH (2 + TEGRA210_PEQ_MAX_BIQUAD_STAGES) + +int tegra210_peq_regmap_init(struct platform_device *pdev); +int tegra210_peq_component_init(struct snd_soc_component *cmpnt); +void tegra210_peq_restore(struct regmap *regmap, u32 *biquad_gains, + u32 *biquad_shifts); +void tegra210_peq_save(struct regmap *regmap, u32 *biquad_gains, + u32 *biquad_shifts); + +#endif diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 084a533bf4f2..10cd37096fb3 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -87,11 +87,11 @@ static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, } mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: val |= TEGRA30_I2S_CTRL_MASTER_ENABLE; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_BC_FC: break; default: return -EINVAL; @@ -331,7 +331,8 @@ static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { }; static const struct snd_soc_component_driver tegra30_i2s_component = { - .name = DRV_NAME, + .name = DRV_NAME, + .legacy_dai_naming = 1, }; static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg) diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index b95438c3dbf7..78faa8bcae27 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -116,20 +116,28 @@ static const struct snd_kcontrol_new tegra_machine_controls[] = { SOC_DAPM_PIN_SWITCH("Headset Mic"), SOC_DAPM_PIN_SWITCH("Internal Mic 1"), SOC_DAPM_PIN_SWITCH("Internal Mic 2"), + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), }; int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; struct tegra_machine *machine = snd_soc_card_get_drvdata(card); + const char *jack_name; int err; if (machine->gpiod_hp_det && machine->asoc->add_hp_jack) { - err = snd_soc_card_jack_new(card, "Headphones Jack", - SND_JACK_HEADPHONE, - &tegra_machine_hp_jack, - tegra_machine_hp_jack_pins, - ARRAY_SIZE(tegra_machine_hp_jack_pins)); + if (machine->asoc->hp_jack_name) + jack_name = machine->asoc->hp_jack_name; + else + jack_name = "Headphones Jack"; + + err = snd_soc_card_jack_new_pins(card, jack_name, + SND_JACK_HEADPHONE, + &tegra_machine_hp_jack, + tegra_machine_hp_jack_pins, + ARRAY_SIZE(tegra_machine_hp_jack_pins)); if (err) { dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", err); @@ -145,11 +153,11 @@ int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) } if (machine->gpiod_hp_det && machine->asoc->add_headset_jack) { - err = snd_soc_card_jack_new(card, "Headset Jack", - SND_JACK_HEADSET, - &tegra_machine_headset_jack, - tegra_machine_headset_jack_pins, - ARRAY_SIZE(tegra_machine_headset_jack_pins)); + err = snd_soc_card_jack_new_pins(card, "Headset Jack", + SND_JACK_HEADSET, + &tegra_machine_headset_jack, + tegra_machine_headset_jack_pins, + ARRAY_SIZE(tegra_machine_headset_jack_pins)); if (err) { dev_err(rtd->dev, "Headset Jack creation failed: %d\n", err); @@ -165,11 +173,11 @@ int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) } if (machine->gpiod_mic_det && machine->asoc->add_mic_jack) { - err = snd_soc_card_jack_new(rtd->card, "Mic Jack", - SND_JACK_MICROPHONE, - &tegra_machine_mic_jack, - tegra_machine_mic_jack_pins, - ARRAY_SIZE(tegra_machine_mic_jack_pins)); + err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + &tegra_machine_mic_jack, + tegra_machine_mic_jack_pins, + ARRAY_SIZE(tegra_machine_mic_jack_pins)); if (err) { dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); return err; @@ -658,6 +666,7 @@ static struct snd_soc_card snd_soc_tegra_max98090 = { static const struct tegra_asoc_data tegra_max98090_data = { .mclk_rate = tegra_machine_mclk_rate_12mhz, .card = &snd_soc_tegra_max98090, + .hp_jack_name = "Headphones", .add_common_dapm_widgets = true, .add_common_controls = true, .add_common_snd_ops = true, diff --git a/sound/soc/tegra/tegra_asoc_machine.h b/sound/soc/tegra/tegra_asoc_machine.h index d6a8d1320551..6f795d7dff7c 100644 --- a/sound/soc/tegra/tegra_asoc_machine.h +++ b/sound/soc/tegra/tegra_asoc_machine.h @@ -14,6 +14,7 @@ struct snd_soc_pcm_runtime; struct tegra_asoc_data { unsigned int (*mclk_rate)(unsigned int srate); const char *codec_dev_name; + const char *hp_jack_name; struct snd_soc_card *card; unsigned int mclk_id; bool hp_jack_gpio_active_low; diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c index ef1e74d95236..468c8e77de21 100644 --- a/sound/soc/tegra/tegra_pcm.c +++ b/sound/soc/tegra/tegra_pcm.c @@ -48,6 +48,12 @@ int tegra_pcm_platform_register(struct device *dev) } EXPORT_SYMBOL_GPL(tegra_pcm_platform_register); +int devm_tegra_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL_GPL(devm_tegra_pcm_platform_register); + int tegra_pcm_platform_register_with_chan_names(struct device *dev, struct snd_dmaengine_pcm_config *config, char *txdmachan, char *rxdmachan) diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h index d602126c65b7..2a36eea1740d 100644 --- a/sound/soc/tegra/tegra_pcm.h +++ b/sound/soc/tegra/tegra_pcm.h @@ -32,6 +32,7 @@ int tegra_pcm_hw_params(struct snd_soc_component *component, snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream); int tegra_pcm_platform_register(struct device *dev); +int devm_tegra_pcm_platform_register(struct device *dev); int tegra_pcm_platform_register_with_chan_names(struct device *dev, struct snd_dmaengine_pcm_config *config, char *txdmachan, char *rxdmachan); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index 5751fb398c1a..b3cd0a34da63 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -79,11 +79,11 @@ static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_component *component = codec_dai->component; int shrt = 0; - err = snd_soc_card_jack_new(rtd->card, "Mic Jack", - SND_JACK_MICROPHONE, - machine->mic_jack, - tegra_wm8903_mic_jack_pins, - ARRAY_SIZE(tegra_wm8903_mic_jack_pins)); + err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + machine->mic_jack, + tegra_wm8903_mic_jack_pins, + ARRAY_SIZE(tegra_wm8903_mic_jack_pins)); if (err) { dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); return err; |